2013-03-28 138 views
5

我在8核心处理器上运行64位Windows 7。我跑了以下几点:为什么单线程比多线程更快,即使它们本质上具有相同的开销?

#include "stdafx.h" 
    #include <iostream> 
    #include <Windows.h> 
    #include <process.h> 
    #include <ctime> 

    using namespace std; 

    int count = 0; 
    int t = time(NULL); 

    //poop() loops incrementing count until it is 300 million. 
    void poop(void* params) { 
     while(count < 300000000) { 
      count++; 
     } 


     cout<< time(NULL) - t <<" \n"; 
    } 

    int _tmain(int argc, _TCHAR* argv[]) 
    { 
     //_beginthread(poop, 0, NULL);  
     //_beginthread(poop, 0, NULL); 
     poop(NULL); 

     cout<<"done"<<endl; 

     while(1); 

     return 0; 
    } 

我比较了结果,当我取消注释beginThread的。事实证明,单线程版本能够以最快的速度实现这一目标!实际上,添加更多线程会使得该过程花费更长时间。使计数达到3亿使得这个过程花费了8+秒,我认为这足以排除函数调用beginThread +其他小的开销。

我做了一些研究,多线程进程的总体结论是较慢的是开销。但在这种情况下,无论我是运行多个线程还是单个线程,访问的变量数(存在于数据段中,因为它是预分配变量afaik)的次数是相等的。所以基本上,开销(如果是开销问题)并不是来自访问全局变量比局部变量花费更多的事实。

看看我的任务管理器,使用单线程的进程使用13%cpu(大约1/8核心),并且添加线程以1/8的增量增加CPU使用率。所以就CPU的能力而言,假设任务管理器准确地描述了这一点,添加线程使用更多的CPU。哪一个让我更加困惑......我是如何使用更多的整体CPU,拥有不同的核心,但总体来说需要更长时间才能完成任务?

TLDR:为什么会这样

+1

这看起来像是多个线程同时修改变量的雷区。 – chris 2013-03-28 03:05:04

+1

是。缓存线路争用。 – 2013-03-28 03:06:09

+2

多线程修改同一对象而不同步:未定义的行为。 – 2013-03-28 12:13:27

回答

5

你的代码本身就是错误的。

count++是一个三步操作,读取值,递增值,然后将其存回变量。
如果两个线程同时在同一个变量上运行count++,它们中的一个将覆盖另一个的更改。

因此,多线程版本将最终做额外的工作,因为每个线程都会破坏其他线程的进度。

如果你制作count一个局部变量,时间看起来应该更正常一些。

或者,您可以使用互锁增量,这是线程安全的,但是在跨线程同步时有额外开销。

+0

啊谢谢你明智的先生。 你会说锁定增量并使用多线程比单线程更快吗? – lululoo 2013-03-28 03:17:45

+4

@lululoo:不,因为只有一个线程会一次更新'count'。这就是同步的全部内容。递增整数需要加载,增加和存储。这不是一个原子操作。这不是多线程解决方案的好选择。你应该想到一个更真实的世界问题,即可以分解为独立和分离任务的任务。 – 2013-03-28 03:20:34

3

由于您原始问题的一些评论者指出您有正确性和性能问题。首先你所有的线程同时访问计数。这意味着没有保证线程实际上全部计数-3亿。您可以通过声明计数船尾内功能

void poop(void* params) { 
    int count = 0; 
    while(count < 300000000) { 
     count++; 
    } 
    cout<< time(NULL) - t <<" \n"; 
} 

注意,这不是因为它是只读的,不写,由线程牛逼问题解决了这个正确性错误。然而,这是一个cout的问题,因为您也在从多个线程写入该问题。

此外,正如评论中指出的那样,所有线程都访问单个内存位置。这意味着当一个线程更新计数时,保存它的缓存行必须刷新并重新加载。这是非常低效的内存访问。通常情况下,当你访问数组中的连续元素时,会发生这种情况,而不是单个变量(坏主意,参见上文)。解决这个问题的方法是填充阵列以确保每个条目都是L1高速缓存行大小的确切倍数,这对于目标处理器来说显然是特定的。另一个选择是重构你的算法,每个线程处理大块连续的元素,或者每个线程访问元素的方式使得线程不访问相邻的位置。

在使用Windows时,您可能需要考虑对代码使用更高级别的抽象,而不是Win32线程函数。 Parallel Patterns Library符合这里的法案(如Intel's Threaded Building Blocks)。

concurrency::parallel_invoke(
     [=] { poop(nullptr); }, 
     [=] { poop(nullptr); } 
    ); 

这使得PPL安排在一个线程池的任务,而不是明确创建线程的应用程序。

您可能还会认为,对于非常小的任务,启动附加线程的开销可能会超过并行运行的收益。

相关问题