多线程:为什么两个程序比一个程序好

Multithreading: Why two programs is better than one?

本文关键字:程序 一个 为什么 多线程 两个      更新时间:2023-10-16

关于我的问题:

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

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

如何使一个程序版本与两个程序一样快?


更多详情:

我有大量的任务,想要完全加载系统的所有 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 中的&命令将它们并排运行:

program1 & program2 &

这些程序中的每一个都实现了~900个任务/秒的速度。总共是>1800 个任务/秒,比一个程序版本多 15%。

我错过了什么?

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

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

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

您可能会遇到错误共享缓存的糟糕情况:http://en.wikipedia.org/wiki/False_sharing

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

或者,也许我需要查看其余代码才能更好地理解您正在做什么。

相关文章: