为什么设置 CPU 关联会使线程运行速度变慢



all.

我写了一个小案例来测试多线程生产者/消费者模型。我的测试平台是一台低性能PC(8G RAM,J1900 CPU与4核(。我隔离了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 consumedn", nb.get());
  printf("consumer: %ldms(%.6fs)n", td.asMicroSecond(), td.asSecond());
}

从 CPU 关联中获得性能并不像将线程 1 推送到核心 1 和线程 2 推送到核心 2 那么简单。这是一个复杂且经过大量研究的话题,我将介绍亮点。

首先,我们需要定义"性能"。 通常,我们对吞吐量、延迟和/或可扩展性感兴趣。将这三者结合起来是一个棘手的架构问题,在电信、金融和其他行业受到了严格的审查。

您的案例似乎由吞吐量指标驱动。我们希望跨线程的挂钟时间总和最小。 陈述问题的另一种方式可能是,"影响多线程进程中吞吐量的因素是什么?

以下是众多因素中的一些:

  1. 算法复杂性的影响最大。 大O,θ,小O在复杂情况下都非常有用。这个例子是微不足道的,但这仍然很重要。 从表面上看,问题是O(n(。时间将根据要分配/解除分配的元素数呈线性关系。 你的问题击中了这个问题的核心,因为它表明物理计算机并不能完美地模拟理想的计算机。
  2. CPU 资源。 如果问题可以并行化,让 CPU 解决问题会有所帮助。 您的问题具有两个线程比一个线程更好的基本假设。 如果是这样,也许四个将比两个之间。 同样,您的实际结果与理论模型相矛盾。
  3. 排队模型。 如果要实现性能提升,了解队列模型至关重要。示例问题似乎是经典的单一生产者/单一消费者模型。
  4. 其他资源。 根据问题的不同,各种其他资源可能会限制性能。一些因素包括磁盘空间、磁盘吞吐量、磁盘延迟、网络容量、套接字可用性。 这个例子在这里似乎没有受到影响。
  5. 内核依赖项。 移动到较低级别,内核交互量可能会对性能产生巨大影响。 通常,内核调用需要上下文切换,如果不断进行,可能会很昂贵。您的示例可能会通过调用新建/删除而遇到此问题。
  6. 串行访问。 如果资源需要串行访问,那么它将成为并行算法的瓶颈。 您的示例似乎有两个这样的问题,新建/删除和排队/取消排队。
  7. 中央处理器缓存。 评论提到CPU缓存是一种可能性。L2/L3 缓存可能是缓存未命中和错误共享的来源。 我怀疑这是您示例中的主要问题,但这可能是一个因素。

将这些想法应用于您的示例,我看到了一些问题。我假设您有两个单独的线程热运行并并行运行。 一个线程生成(新(,另一个线程消耗(删除(。

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

队列可能是串行的。 未显示实现,但排队/取消排队可能是两个线程之间的序列化点。有许多可以在多个线程之间使用的无锁环形缓冲区示例。

线程不足。在该示例中,如果生产者比消费者慢,则消费者将在大部分时间处于空闲状态。这是在制作高性能算法时必须考虑的队列理论的一部分。

有了所有这些背景,我们现在可以得出结论,在序列化和饥饿问题得到解决之前,线程亲和力不太可能重要。 实际上,这两个线程可能会运行得更慢,因为它们相互争夺共享资源,或者只是浪费 CPU 空闲时间。 结果,整体吞吐量下降,因此挂钟时间增加。

工业界对了解这些算法的工程师有巨大的需求。 教育自己可能是一项有利可图的冒险。

最新更新