2016-09-14 91 views
2

全部。为什么设置CPU亲和力会使线程运行速度变慢?

我写了一个小案例来测试多线程生产者/消费者模型。我的测试平台是一款低性能PC(8G内存,J1900四核CPU)。我隔离了Linux内核的核心0,核心1-3没有使用。生产者线程在核心1上运行,分配5000000个小对象,将它们放到全局队列中。消费者线程在核心2上运行,并从队列中释放对象。但是我发现如果我没有设置它们的CPU亲和性(即它们运行在相同的核心0上),那么时间性能会比设置CPU亲和性(8.76s VS 14.66s)好。测试结果保持相似。有人能解释我的原因吗?如果我的前提不正确(“设置CPU关联性可以提高多线程进程的性能”),则至少不应该变得更糟。我的代码片段如下:

void producer() { 
    Timestamp begin; 

    for (int i = 0; i<data_nb; ++i) { 
    Test* test = new Test(i, i+1); 
    queue.enqueue(test); 
    } 

    Timestamp end; 
    TimeDuration td = end-begin; 
    printf("producer: %ldms(%.6fs)\n", td.asMicroSecond(), td.asSecond()); 
} 

void consumer() { 
    Timestamp begin; 

    do { 
    Test* test = queue.dequeue(); 
    if (test) { 
     nb.add(1); // nb is an atomic counter 
     delete test; 
     test = nullptr; 
    } 
    } while (nb.get() < data_nb); 

    Timestamp end; 
    TimeDuration td = end-begin; 
    //printf("%d data consumed\n", nb.get()); 
    printf("consumer: %ldms(%.6fs)\n", td.asMicroSecond(), td.asSecond()); 
} 
+1

未能设置线程关联并不能保证线程必须使用核心0. –

+1

请参阅http://stackoverflow.com/questions/39141897/setting-the-affinity-causes-increase-in-execution-time – UmNyobe

+1

人们在理论上通常会认为设置线程关联会有所帮助。他们很少检查。很少有人做检查会感到惊讶。 – Slava

回答

3

从CPU亲和力获得性能也不如压入螺纹1至芯1和线2芯2这是一个复杂而沉重的话题研究的那么简单,我会触摸亮点。

首先我们需要定义'性能'。通常,我们对throughput,latency和/或scalability感兴趣。三者结合是一个棘手的架构问题,在电信,金融和其他行业受到严密审查。

您的情况似乎是由吞吐量度量驱动的。我们希望跨线程的挂钟时间总和最小。陈述您的问题的另一种方式可能是“影响多线程流程吞吐量的因素是什么?”

这里有一些因素很多:

  1. 算法复杂性具有最大的影响力。 Big O,theta,little O在复杂情况下都非常有用。这个例子是微不足道的,但这仍然很重要。表面上,问题是O(n)。根据要分配/释放元素的数量,时间将是线性的。你的问题是这件事的核心,因为它表明,物理计算机不能完美地模拟理想的计算机。
  2. CPU资源。如果问题可以并行处理,让CPU解决问题可以提供帮助。你的问题有一个潜在的假设,即两个线程会比一个线程好。如果是这样,也许四个会在两个之间。再一次,你的实际结果与理论模型相矛盾。
  3. 排队模型。如果要实现性能增益,理解Queuing模型至关重要。示例问题似乎是经典的单一生产者/单一消费者模型。
  4. 其他资源。根据问题,各种其他资源可能会限制性能。一些因素包括磁盘空间,磁盘吞吐量,磁盘延迟,网络容量,套接字可用性。这个例子似乎并没有受到影响。
  5. 内核依赖关系。移至较低级别时,性能可能会受到内核交互量的巨大影响。通常,内核调用需要上下文切换,如果不断完成,这可能会很昂贵。你的例子可能通过调用new/delete来解决这个问题。
  6. 串行访问。如果资源需要串行访问,那么它将在并行算法中出现瓶颈。你的例子似乎有两个这样的问题,新/删除和入队/出队。
  7. CPU缓存。评论提到CPU caching是一种可能性。 L2/L3缓存可能是缓存未命中的来源,也可能是false sharing。我怀疑这是你的例子中的主要问题,但它可能是一个因素。

将这些想法应用到您的示例中,我看到一些问题。我假设你有两个独立的线程并行运行。一个线程产生(新)和另一个消耗(删除)。

堆是串行的。在不同的线程中调用new和delete是已知的性能瓶颈。有几个小块并行分配器,包括Hoard

队列可能是串行的。没有显示实现,但入队/出队可能是两个线程之间的序列化点。 lock free ring buffers有很多可以在多个线程之间使用的例子。

线程挨饿。在这个例子中,如果生产者比消费者慢,那么消费者在很多时候都会空转。这是构建高性能算法时必须考虑的队列理论的一部分。有了这些背景知识,我们现在可以得出结论,直到序列化和饥饿问题得到解决,线程亲和才可能不重要。实际上,两个线程可能会因共享资源互相竞争而慢速运行,或者只是浪费CPU空闲时间。结果,整体吞吐量下降,因此挂钟时间增加。

工业界对理解这些算法的工程师有着巨大的需求。教育自己很可能是一个有利可图的事业。

相关问题