VS 7.1 Release 编译和多线程

Posted

技术标签:

【中文标题】VS 7.1 Release 编译和多线程【英文标题】:VS 7.1 Release compile and multiple threads 【发布时间】:2009-06-12 15:12:02 【问题描述】:

VS 7.1 发布模式似乎无法正确并行化线程,而调试模式却可以。以下是正在发生的事情的摘要。

首先,就其价值而言,这是并行化的主要代码,但我认为这不是问题:

       // parallelize the search

       CWinThread* thread[THREADS];
       for ( i = 0; i < THREADS; i++ ) 
           thread[i] = AfxBeginThread( game_search, &parallel_params[i],
                                       THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED );
           thread[i]->m_bAutoDelete = FALSE;
           thread[i]->ResumeThread();
       
       for ( i = 0; i < THREADS; i++ ) 
           WaitForSingleObject(thread[i]->m_hThread, INFINITE);
           delete(thread[i]);
       

THREADS 是我设置的全局变量,如果我想更改线程数,我会重新编译。为了提供一些背景信息,这是一个搜索游戏位置的游戏程序。

下面是发生的对我来说没有意义的事情。

首先,在调试模式下编译。如果我将 THREADS 设置为 1,则一个线程可以搜索大约 13,000 个位置。如果我将 THREADS 设置为 2,每个 线程将搜索大约 13,000 个位置。太好了!

如果我在发布模式下编译并将 THREADS 设置为 1,线程会设法搜索大约 30,000 个位置,这是我在从调试转移到发布时经常看到的典型加速。但这里是踢球者。当我用 THREADS = 2 编译时,每个线程只搜索大约 15,000 个位置。显然是 THREADS = 1 的一半,所以有效的发布编译没有给我任何有效的加速。 :(

当这些东西运行时观察任务管理器,当 THREADS = 1 我看到我的双核机器上的 CPU 使用率为 50% 时,当 THREADS = 2 我看到 100% 的 CPU 使用率。但是发布编译似乎给了我 50% 的有效 CPU 使用率。还是什么?!

有什么想法吗?我应该在属性页中设置什么吗?


更新:下面也发布了以下内容,但建议我更新此帖子。也有人建议我发布代码,但这是一个相当大的项目。我希望其他人过去自己也遇到过这种行为,并且可以对正在发生的事情有所了解。


我在四核系统上运行程序,得到了一致但仍然令人困惑的结果。我知道我即将摆脱特定的编程问题并变得有点抽象,但我真的很想听听您可能需要帮助解释我所看到的数字的任何 cmets。对于所有这些测试,我运行了 30 秒,根据任务管理器,所有线程在整个 30 秒内都在全力运行。

在调试模式下运行时,如果我使用 1 个线程运行,它会完成 X 量的工作。如果我运行 2 个线程,每个线程都会完成 X 量的工作。与 3 和 4 线程类似。缩放是完美的。

在发布模式下运行时,会发生以下情况:

使用 1 个线程:它完成了 Y 量的工作,其中 Y 几乎是 X 的两倍。

使用 2 个线程:每个线程完成 Y 量的工作。再次,完美缩放。

使用 3 个线程:1 个线程完成 Y 量的工作,其他 2 个线程完成 2/3 Y 量的工作。我已经损失了大约 2/3 的 CPU,即使一个可能完全空闲。任务管理器显示 75% 的 CPU 使用率。

使用 4 个线程:1 个线程完成 Y 量的工作。其他 3 个线程完成了 1/2 Y 的工作量。现在我损失了大约 1.5 个 CPU 的计算量。任务管理器显示 100% 的 CPU 使用率。

明显的问题是:

(1) 重复前面的问题,Debug 模式是否可以很好地扩展,但不是 Release?

(2) 为什么一个核心总是能得到充分利用,而其他核心似乎脱落了?这种缺乏对称性令人不安。

(3) 为什么其他的都掉下来了?内存带宽之前建议过,但这似乎是一个非常高昂的价格。

欢迎任何 cmet 或见解。而且,一如既往,谢谢!

【问题讨论】:

【参考方案1】:

我认为你应该使用 WaitForMultipleObjects()。

【讨论】:

这很好。我可能应该切换。但是这似乎不太可能导致发布模式而不是调试模式下的缓慢行为,你不觉得吗? 不,正如其他人指出的那样,发布模式会进行各种优化,这些优化可能会导致您看到的内容。您在编译/链接中使用 /O* 或 /G* 吗?【参考方案2】:

多线程的问题在于它是不确定的。

首先,DEBUG 目标不会优化代码。它还为运行时检查添加了额外的代码(例如,断言、MFC 中的跟踪等)。

RELEASE 目标已优化。因此,在发布模式下,二进制文件可能与调试模式下的情况略有不同。

线程执行的工作是什么也很重要。例如,如果您的线程正在使用一些 IO 操作,它们将有一些空闲时间,等待这些 IO 操作完成。由于在 RELEASE 模式下要执行的代码预计会更高效,因此空闲时间和执行时间之间的比率可能与 DEBUG 模式下不同。

鉴于提供的信息,我只是猜测可能的解释。

稍后更新: 您可以使用 WaitForMultipleObjects 等待所有线程完成:

DWORD result = WaitForMultipleObjects( 
  numberOfThreads,  // Number of thread handles in the array
  threadHandleArray,  // the array of thread handles
  true, // true means wait for all the threads to finish
  INFINITE); // wait indefinetly
if( result == WAIT_FAILED)
  // Some error handling here

【讨论】:

【参考方案3】:

我不确定我是否理解为什么在 Debug 与 Release 中搜索的职位数量不同。您正在等待线程完成,所以我希望发布版本能够更快地完成,但两个版本都会生成相同的结果。

您是否设置了每个线程的时间限制?如果是这样,它的机制是什么?

在没有逻辑错误的情况下,在单线程和双线程版本中,对于 Debug 情况,您的处理似乎受到 CPU 限制。在发布案例中,您没有获得任何有效的加速,这意味着处理效率更高,并且处理现在受到其他因素(例如 IO 或内存带宽)的限制,或者您获得的任何收益都被频繁的上下文所抵消如果线程之间的同步策略不佳,可能会在线程之间切换。

了解每个线程的具体处理内容、它们拥有哪些共享数据以及它们需要多久相互同步会很有帮助。

【讨论】:

【参考方案4】:

正如 Charles Bailey 所说,从您的描述来看,您似乎在施加每个线程的时间限制。

您使用的计时机制可能会在调试模式下参考挂钟时间,而在发布模式下参考 CPU 时间(所有正在使用的处理器/内核的总和)。因此,在发布模式下,当 THREADS = 2 时,您使用的 CPU 时间总分配速度提高了一倍,每个内核上的工作量减半。

只是一个想法。您能否详细介绍一下您的计时机制?

【讨论】:

你是对的。我对此不是很清楚。是的,我有时间限制,每个线程使用相同的时间。这是调用:SetTimer(params->hView, WM_TIMER_EXPIRED, params->nMoveTimeout * 1000, NULL);我通常将其设置为 30 秒。在 2 线程模式下,每个线程都运行 30 秒,正如我所期望的那样,所以我很确定这部分工作正常。每个线程完全独​​立于其他线程,因此不存在同步问题。此外,I/O 非常少,每隔几秒就会有一点点输出到窗口。 很难理解或相信无论问题是什么:(1) 在发布模式下运行时间几乎翻倍,(2) 在调试模式下没有明显影响。我非常感谢您对此的所有想法! 再次,措辞不当。我的意思是,令我惊讶的是,在调试模式下,一起运行的 2 个线程中的每一个都完成了与单独运行 1 个线程完全相同的工作(正如人们所希望的那样,基本上使总计算量增加了一倍),但在发布模式下2 个线程中的每一个都只完成了一半的工作,因此即使两个 CPU 都在全力倾斜,也根本不会增加总计算量。 鉴于您的计时器机制(基于挂钟,而不是基于 cpu 周期计数),我之前的评论是一个红鲱鱼。但是我想知道在调试模式下它是否会被序列化,也就是说,调试器一次只允许一个线程运行?只是一个疯狂的猜测。您是在调试器中实际运行调试模式程序,还是独立运行? 那不会产生相反的效果吗?如果 Debug 被序列化,那么在 Debug 模式下运行 2 个线程 30 秒将产生与在 30 秒内运行 1 个线程相同的计算量。但是在 Debug 中,我确实得到了 2 个线程的双倍计算。为了回答您的问题(我希望如此!),我通过按 F5 键从 IDE 运行调试器。今天晚些时候,我将靠近四核机器。我将在那里进行一些测试,以便获得更多数据,但无论如何我仍然认为这里发生了一些奇怪的事情。【参考方案5】:

我从 1 和 2 线程中获得 30k 个位置的事实看起来很可疑。该限制可能来自系统中的另一个组件吗?您提到每个线程都是完全独立的,但是您是否有机会使用任何 Interlocked* 功能?它们看起来很无辜,但实际上它们强制同步所有 CPU 缓存,这在试图充分利用 CPU 时可能会很痛苦。

我会做的是让每个线程执行一些虚拟操作,例如(字符串操作等),只是为了浪费一些时间。如果可以很好地扩展,将线程的一部分真实代码添加到虚拟操作中,然后再次测试。重复直到性能停止扩展,这意味着最近添加的代码是瓶颈。

我要研究的另一个方向是确保两个线程实际上在不同的 CPU 上同时运行。试试bounding each thread to a single CPU。 这不是我在生产中留下的东西,但是如果您的系统被其他进程加载,您可能无法从双 CPU 中获得预期的收益。毕竟,在单 CPU 机器上,使用两个线程获得的吞吐量可能低于使用一个线程获得的吞吐量。

【讨论】:

【参考方案6】:

我在四核系统上运行该程序,得到了一致但仍然令人困惑的结果。我知道我即将摆脱特定的编程问题并变得有点抽象,但我真的很想听听您可能需要帮助解释我所看到的数字的任何 cmets。对于所有这些测试,我运行了 30 秒,根据任务管理器的说法,所有线程在整个 30 秒内都在全力运行。

在调试模式下运行时,如果我使用 1 个线程运行,它会完成 X 量的工作。如果我运行 2 个线程 每个 线程可以完成 X 量的工作。与 3 和 4 线程类似。缩放是完美的。

在发布模式下运行时,会发生以下情况:

使用 1 个线程:它完成了 Y 量的工作,其中 Y 几乎是 X 的两倍。

有 2 个线程:每个 线程完成 Y 量的工作。再次,完美缩放。

使用 3 个线程:1 个线程完成 Y 量的工作,其他 2 个线程完成 2/3 Y 量的工作。我已经损失了大约 2/3 的 CPU,即使一个可能完全空闲。任务管理器显示 75% 的 CPU 使用率。

使用 4 个线程:1 个线程完成 Y 量的工作。其他 3 个线程完成了 1/2 Y 的工作量。现在我损失了大约 1.5 个 CPU 的计算量。任务管理器显示 100% 的 CPU 使用率。

明显的问题是:

(1) 重复前面的问题,Debug 模式是否可以很好地扩展,但 Release 不是?

(2) 为什么一个核心总是能够得到充分利用,而其他核心似乎脱落了?这种缺乏对称性令人不安。

(3) 为什么其他的都掉下来了?内存带宽之前建议过,但这似乎是一个非常高昂的价格。

欢迎任何 cmet 或见解。而且,一如既往,谢谢!

【讨论】:

我认为您需要使用此信息更新您的原始问题,并可能发布工作线程的代码。【参考方案7】:

有很多事情可能会妨碍你的表现。

一个问题可能是缓存行的错误共享。

当你有类似的东西时:

struct data

   int cnt_parsed_thread[THREADS];
   // ...
;
static data;

在线程本身:

threadFunc( int threadNum )

   while( !end )
   
      // ...
      // do something
      ++data.cnt_parsed_thread[num];
   

您强制两个处理器在每次递增后将缓存线发送到另一个处理器,从而极大地拖延了计算。

可以通过将错误共享的数据分散到单独的缓存行来解决此问题。

例如:

struct data

   int cnt_parsed_thread[THREADS*CACHELINESIZE];
   // ...

   int& at( int k )  return cnt_parsed_thread[k*CACHELINESIZE; 
;

(CACHELINE 的大小应该是 64 字节(我认为),也许可以尝试一下。)

【讨论】:

以上是关于VS 7.1 Release 编译和多线程的主要内容,如果未能解决你的问题,请参考以下文章

进程 vs. 线程

Python多进程vs多线程

(王道408考研操作系统)第二章进程管理-第一节5:线程概念和多线程模型

Python 子进程、通信和多处理/多线程

6Java并发性和多线程-并发性与并行性

python多线程-Semaphore(信号对象)