【问题标题】:Multithreading: Why two programs is better than one?多线程:为什么两个程序比一个程序好?
【发布时间】:2013-11-25 20:10:39
【问题描述】:

简单说说我的问题:

我有一台带有 2 个 AMD Opteron 6272 插槽和 64GB RAM 的计算机。

我在所有 32 个内核上运行一个多线程程序,与在一个 16 内核插槽上运行 2 个程序的情况相比,速度降低了 15%。

如何让单程序版本和双程序版本一样快?


更多细节:

我有大量任务,想要完全加载系统的所有 32 个内核。 所以我将任务按 1000 个分组打包。这样一个组需要大约 120Mb 的输入数据,在一个核心上完成大约需要 10 秒。为了使测试更理想,我将这些组复制了 32 次,并使用 ITBB 的 parallel_for 循环在 32 个内核之间分配任务。

我使用pthread_setaffinity_np 来确保系统不会让我的线程在内核之间跳转。并确保依次使用所有内核。

我使用mlockall(MCL_FUTURE) 来确保系统不会让我的内存在套接字之间跳转。

所以代码看起来像这样:

  void operator()(const blocked_range<size_t> &range) const
  {
    for(unsigned int i = range.begin(); i != range.end(); ++i){

      pthread_t I = pthread_self();
      int s;
      cpu_set_t cpuset;
      pthread_t thread = I;
      CPU_ZERO(&cpuset);
      CPU_SET(threadNumberToCpuMap[i], &cpuset);
      s = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);

      mlockall(MCL_FUTURE); // lock virtual memory to stay at physical address where it was allocated

      TaskManager manager;
      for (int j = 0; j < fNTasksPerThr; j++){
        manager.SetData( &(InpData->fInput[j]) );
        manager.Run();
      }
    }
  }

只有计算时间对我来说很重要,因此我在单独的 parallel_for 循环中准备输入数据。并且不要在时间测量中包括准备时间。

  void operator()(const blocked_range<size_t> &range) const
  {
    for(unsigned int i = range.begin(); i != range.end(); ++i){

      pthread_t I = pthread_self();
      int s;
      cpu_set_t cpuset;
      pthread_t thread = I;
      CPU_ZERO(&cpuset);
      CPU_SET(threadNumberToCpuMap[i], &cpuset);
      s = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);

      mlockall(MCL_FUTURE); // lock virtual memory to stay at physical address where it was allocated
      InpData[i].fInput = new ProgramInputData[fNTasksPerThr];

      for(int j=0; j<fNTasksPerThr; j++){
        InpData[i].fInput[j] = InpDataPerThread.fInput[j];
      }
    }
  }

现在我在 32 个内核上运行所有这些,发现速度约为每秒 1600 个任务。

然后我创建了两个版本的程序,并使用tasksetpthread 确保首先在第一个套接字的 16 个内核上运行,第二个在第二个套接字上运行。我在 shell 中使用简单的&amp; 命令将它们并排运行:

program1 & program2 &

这些程序中的每一个都可以达到约 900 个任务/秒的速度。总计 >1800 个任务/秒,比单程序版本多 15%。

我错过了什么?

我认为问题可能出在库中,我仅将其加载到集合线程的内存中。这会是个问题吗?我可以复制库数据以便在两个套接字上独立使用吗?

【问题讨论】:

  • 你试过32个单线程程序吗?
  • 32 个单线程程序无法处理可能在错误 numa 节点中分配内存的问题。他只有 2 个节点,所以他只需要 2 个程序,每个程序都绑定到一个节点。
  • Numa 节点??我不知道那是什么,但听起来太好了,我要去看看。
  • @Dennis,是的,numa。如果你愿意,我可以展示拓扑和可扩展性测试。
  • 有趣的东西。我以前不必研究它,但我可以看到它在大型数据集的并行处理中可以带来多少好处。

标签: c++ multithreading pthreads numa


【解决方案1】:

您可能正在遭受错误共享缓存的严重情况:http://en.wikipedia.org/wiki/False_sharing

您的线程可能通过 block_range 引用共享对相同数据结构的访问。如果您只需要速度,您可能希望将副本传递给每个线程。如果您的数据太大而无法放入调用堆栈,您可以在不同的缓存段中动态分配每个范围的副本(即确保它们足够远)。

或者我可能需要查看其余代码以了解您在哪些方面做得更好。

【讨论】:

  • 我不确定我是否理解你。你说的是什么数据? block_range 是一个非常小的结构,它不在程序(TaskManger)内部使用。我已经复制并动态分配了所有使用的数据。
  • 好的。你说的对。我误解了 block_range 的目的和性质。我认为这是您正在操纵的一些常见数据。我现在明白它是整数间隔的 TBB 模板。我的错。 InpData 是如何定义的?
【解决方案2】:

我猜是 STL/boost 内存分配在 numa 节点上为您的集合等分配内存,因为它们不知道 numa 并且您在每个节点上运行的程序中有线程。

您使用的所有 STL/boost 事物的自定义分配器可能会有所帮助(但可能是一项艰巨的工作)。

【讨论】:

  • 不应该 mlockall(MCL_FUTURE) 帮助吗?这个linux.die.net/man/2/mlock 表示它必须帮助所有未来的内存分配。
  • 我希望库在此之前分配内存,而且由于它们没有 numa,它们可能会在集合之间或内部重用内存。恕我直言,使用自定义容器可能是可行的方法。
  • 你似乎是对的。尽量减少 std::vector::reserve 的使用,我设法将时间差减少到 2%。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-14
  • 2016-10-29
  • 1970-01-01
  • 2016-05-21
相关资源
最近更新 更多