手动编写多线程循环 - 次优可扩展性

Posted

技术标签:

【中文标题】手动编写多线程循环 - 次优可扩展性【英文标题】:Manually writing a multithreaded loop - suboptimal scalability 【发布时间】:2012-10-11 11:43:46 【问题描述】:

我编写了这个测试应用程序:它经历了从 0 到 9999 的迭代,对于范围内的每个整数,它都会计算一些无用但计算密集型的函数。结果,程序输出函数值的总和。为了让它在多个线程上运行,我使用了 InterlockedIncrement - 如果递增后迭代次数为

我想知道为什么它没有像我希望的那样缩放。使用 5 个线程,它运行 8 秒,而单线程运行 36 秒。这提供了约 4.5 的可扩展性。在我对 OpenMP 的实验中(在稍微不同的问题上),我获得了更好的可扩展性。

源代码如下所示。

我在 Phenom II X6 桌面上运行 Windows7 操作系统。不知道还有哪些其他参数可能是相关的。

您能帮我解释一下这种次优的可扩展性吗? 非常感谢。

#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <vector>
#include <windows.h>
#include <iostream>
#include <cmath>

using namespace std;
using namespace boost;

struct sThreadData

  sThreadData() : iterCount(0), value( 0.0 ) 
  unsigned iterCount;
  double value;
;

volatile LONG g_globalCounter;
const LONG g_maxIter = 10000;

void ThreadProc( shared_ptr<sThreadData> data )

  double threadValue = 0.0;
  unsigned threadCount = 0;

  while( true )
  
    LONG iterIndex = InterlockedIncrement( &g_globalCounter );
    if( iterIndex >= g_maxIter )
      break;

    ++threadCount;

    double value = iterIndex * 0.12345777;
    for( unsigned i = 0; i < 100000; ++i )
      value = sqrt( value * log(1.0 + value) );

    threadValue += value;
  

  data->value = threadValue;
  data->iterCount = threadCount;


int main()

  const unsigned threadCount = 1;

  vector< shared_ptr<sThreadData> > threadData;
  for( unsigned i = 0; i < threadCount; ++i )
    threadData.push_back( make_shared<sThreadData>() );

  g_globalCounter = 0;

  DWORD t1 = GetTickCount();
  vector< shared_ptr<thread> > threads;
  for( unsigned i = 0; i < threadCount; ++i )
    threads.push_back( make_shared<thread>( &ThreadProc, threadData[i] ) );

  double sum = 0.0;
  for( unsigned i = 0; i < threadData.size(); ++i )
  
    threads[i]->join();
    sum += threadData[i]->value;
  

  DWORD t2 = GetTickCount();
  cout << "T=" << static_cast<double>(t2 - t1) / 1000.0 << "s\n";

  cout << "Sum= " << sum << "\n";
  for( unsigned i = 0; i < threadData.size(); ++i )
    cout << threadData[i]->iterCount << "\n";

  return 0;

编辑: 附加此测试程序的示例输出(1 和 5 个线程):

【问题讨论】:

您是否尝试过预先拆分任务而不是让线程访问共享状态? 感谢您阅读本文。共享状态是指 g_globalCounter 变量吗?不,我没试过。我的假设是先到先服务将提供最佳负载平衡。我尝试将 value = sqrt( value * log(1.0 + value) ); 迭代次数增加 10 次(这应该会减少迭代计数器的争用)。结果是 80.43 秒对 358 秒 - 所以我不认为共享状态是造成这种情况的原因。 下一个不好的猜测 sqrt/log 是如何实现的?可能是 FPU 争用? @j_random_hacker,我已将函数接口更改为 'void ThreadProc( shared_ptr data, unsigned iStart, unsigned iEnd )'。没有加速,同样的旧 36s vs 8s。 谢谢(我假设你也摆脱了InterlockedIncrement())。我认为将其作为可能的原因消除是值得的。但在这种情况下,我很困惑! AFAIK 每个 CPU 都有自己的 FPU(和 SSE 寄存器),所以我看不到 Martin 建议的 FP 争用情况。你有其他程序在后台运行吗?如果您同时启动 5 个单线程程序实例,该程序只执行 g_maxIter / 5 迭代,它们每个实例所用的时间是否比您只启动 1 个要长? 【参考方案1】:

结果可以用我的CPU支持AMD Turbo Core技术来解释。

在 Turbo CORE 模式下,AMD Phenom™ II X6 1090T 会改变频率 速度从六核 3.2GHz 到三核 3.6GHz

所以单线程模式和多线程模式下的时钟频率是不一样的。我习惯于在不支持 TurboCore 的 CPU 上玩多线程。下面是显示结果的图像

AMD OverDrive 实用程序窗口(允许打开/关闭 TurboCore) TurboCore 开启时运行 1 个线程 TurboCore 关闭时运行 1 个线程 运行 5 个线程

非常感谢那些试图提供帮助的人。

【讨论】:

以上是关于手动编写多线程循环 - 次优可扩展性的主要内容,如果未能解决你的问题,请参考以下文章

新手入门Python核心笔记五:多线程图形用户界面web数据库扩展Python

多线程向量求和的可扩展性

为啥多线程应用程序通常会扩展不好?

PHP开启多线程扩展

Java 多线程学习扩展

在多核机器上扩展多线程应用程序