2017-05-24 2 views
1

J'ai fait un test pour comparer les performances de la file d'attente concurrente du canal golang et C++ tbb, j'ai installé 8 écrivain et 1 lecteur qui sont dans des threads différents. montre que golang est beaucoup plus rapide que la version C++ (quelle que soit la latence et la vitesse globale d'envoi/recv), est-ce vrai? ou une erreur dans mon code?pourquoi golang canal est beaucoup plus rapide que intel tbb concurrent_queue lors du test avec 8 producteur 1 consommateur

résultat golang, l'unité est microseconde

latence max: 1505, avg: 1073 envoi commencent: 1495593677683232, fin recv: 1495593677901854, temps: 218622

package main 

import (
    "flag" 
    "time" 
    "fmt" 
    "sync" 
    "runtime" 
) 

var (
    producer = flag.Int("producer", 8, "producer") 
    consumer = flag.Int("consumer", 1, "consumer") 
    start_signal sync.WaitGroup 
) 

const (
    TEST_NUM = 1000000 
) 

type Item struct { 
    id int 
    sendtime int64 
    recvtime int64 
} 

var g_vec[TEST_NUM] Item 

func sender(out chan int, begin int, end int) { 
    start_signal.Wait() 
    runtime.LockOSThread() 
    println("i am in sender", begin, end) 
    for i:=begin; i < end; i++ { 
     item := &g_vec[i] 
     item.id = i 
     item.sendtime = time.Now().UnixNano()/1000 
     out<- i 
    } 
    println("sender finish") 
} 

func reader(out chan int, total int) { 
    //runtime.LockOSThread() 
    start_signal.Done() 
    for i:=0; i<total;i++ { 
     tmp :=<- out 
     item := &g_vec[tmp] 
     item.recvtime = time.Now().UnixNano()/1000 
    } 
    var lsum int64 = 0 
    var lavg int64 = 0 
    var lmax int64 = 0 
    var lstart int64 = 0 
    var lend int64 = 0 
    for _, item:= range g_vec { 
     if lstart > item.sendtime || lstart == 0 { 
      lstart = item.sendtime 
     } 
     if lend < item.recvtime { 
      lend = item.recvtime 
     } 

     ltmp := item.recvtime - item.sendtime 
     lsum += ltmp 
     if ltmp > lmax { 
      lmax = ltmp 
     } 
    } 
    lavg = lsum/TEST_NUM 
    fmt.Printf("latency max:%v,avg:%v\n", lmax, lavg) 
    fmt.Printf("send begin:%v,recv end:%v, time:%v", lstart, lend, lend-lstart) 
} 

func main() { 
    runtime.GOMAXPROCS(10) 
    out := make (chan int,5000) 

    start_signal.Add(1) 
    for i:=0 ;i<*producer;i++ { 
     go sender(out,i*TEST_NUM/(*producer), (i+1)*TEST_NUM/(*producer)) 
    } 
    reader(out, TEST_NUM) 
} 

C++, seule une partie principale

concurrent_bounded_queue g_queue; max: 558.301, min: 3, avg: 403741 (unité est microseconde) départ: 1495594232068580, fin: 1495594233497618, longueur: 1429038

static void sender(int start, int end) 
{ 
    for (int i=start; i < end; i++) 
    { 
     using namespace std::chrono; 
     auto now = system_clock::now(); 
     auto now_ms = time_point_cast<microseconds>(now); 
     auto value = now_ms.time_since_epoch(); 
     int64_t duration = value.count(); 

     Item &item = g_pvec->at(i); 
     item.id = i; 
     item.sendTime = duration; 
     //std::cout << "sending " << i << "\n"; 
     g_queue.push(i); 
    } 
} 


static void reader(int num) 
{ 
    barrier.set_value();  
    for (int i=0;i<num;i++) 
    { 
     int v; 
     g_queue.pop(v); 
     Item &el = g_pvec->at(v); 

     using namespace std::chrono; 
     auto now = system_clock::now(); 
     auto now_ms = time_point_cast<microseconds>(now); 
     auto value = now_ms.time_since_epoch(); 
     int64_t duration = value.count(); 

     el.recvTime = duration; 
     //std::cout << "recv " << item.id << ":" << duration << "\n"; 
    } 
    // caculate the result. 
    int64_t lmax = 0; 
    int64_t lmin = 100000000; 
    int64_t lavg = 0; 
    int64_t lsum = 0; 
    int64_t lbegin = 0; 
    int64_t lend = 0; 
    for (auto &item : *g_pvec) 
    { 
     if (item.sendTime<lbegin || lbegin==0) 
     { 
      lbegin = item.sendTime; 
     } 
     if (item.recvTime>lend) 
     { 
      lend = item.recvTime; 
     } 

     lsum += item.recvTime - item.sendTime; 
     lmax = max(item.recvTime - item.sendTime, lmax); 
     lmin = min(item.recvTime - item.sendTime, lmin); 
    } 
    lavg = lsum/num; 
    std::cout << "max:" << lmax << ",min:" << lmin << ",avg:" << lavg << "\n"; 
    std::cout << "start:" << lbegin << ",end:" << lend << ",length:" << lend-lbegin << "\n"; 
} 

DEFINE_CODE_TEST(plain_queue_test) 
{ 
    g_pvec = new std::vector<Item>(); 
    g_pvec->resize(TEST_NUM);  

    auto sf = barrier.get_future().share(); 

    std::vector<std::thread> vt; 
    for (int i = 0; i < SENDER_NUM; i++) 
    { 
     vt.emplace_back([sf, i]{  
      sf.wait(); 
      sender(i*TEST_NUM/SENDER_NUM, (i + 1)*TEST_NUM/SENDER_NUM); 
     }); 
    } 


    std::cout << "create reader\n"; 
    std::thread rt(bind(reader, TEST_NUM)); 
    for (auto& t : vt) 
    { 
     t.join(); 
    }  
    rt.join(); 
} 

golang cpu usage

cpp cpu usage

(moyens de couleur rouge cpu spin/overhead, le vert est inactif) du graphique de cpu de vtune j'ai senti que le canal de golang a un mutex plus efficace (par exemple a-t-il besoin d'un appel de système pour dormir un goroutine contre mutex de C++?) enter image description here

enter image description here

+0

Je vous suggère d'utiliser d'abord un véritable outil d'analyse comparative, comme celui intégré dans go. –

+0

Lors de l'inspection, vous ne faites pas les mêmes choses dans les deux programmes. Pour n'en nommer que quelques-uns, la version C++ a des divisions entières et ce n'est pas le cas, et les variables n'ont pas le même niveau d'indirection. Ceux-ci peuvent finalement s'avérer non pertinents, mais jusqu'à ce que vous les référenciez réellement, nous ne le saurons pas –

+0

Essayez également d'ajouter le travail de calcul réel/simulé dans les producteurs/consommateurs. Il peut arriver qu'à cause de goroutines, il soit exécuté dans un seul thread, ainsi la vraie synchronisation n'a pas lieu pour la version Go. En outre, la file d'attente de TBB est MPMC, tandis que Go pourrait l'optimiser pour le modèle MPSC de manière significative. – Anton

Répondre

2

Des traces de vtune je peux conclure que la file d'attente TBB ne dort pas, dépenser un temps à filer beaucoup, tandis que la version Go a des espaces verts lumineux indiquant la synchronisation des fils de couchage OS. Pourquoi est-ce mieux? Habituellement, cela indique que vous avez une sursouscription sur votre machine, donc la communication via OS est payante. Alors, vous surinscrivez-vous? Si oui, je dirais que c'est un comportement plutôt attendu qui s'inscrit dans la philosophie des bibliothèques correspondantes. TBB est conçu pour le parallélisme de calcul, il ne gère pas bien les tâches IO tout en jouant contre les sursouscriptions. Go est conçu exactement pour les tâches d'E/S, donc la concurrence intégrée avec la politique FIFO du planificateur qui est hostile au calcul parallèle des nombres. La surinscription est plutôt recommandée pour les tâches d'E/S alors qu'elle affecte ou même tue le parallélisme informatique.