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:为什么会这样

4

2 回答 2

5

您的代码本质上是错误的。

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

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

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

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

于 2013-03-28T03:05:45.100 回答
3

正如您最初问题的一些评论者指出的那样,您存在正确性和性能问题。首先,您的所有线程都在同时访问计数。这意味着无法保证线程实际上将全部计数到 3 亿。您可以通过在便便函数中声明计数来解决此正确性错误

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

请注意,这对t来说不是问题,因为它只能由线程读取而不是写入。然而,这是cout的一个问题,因为您还从多个线程写入。

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

当您使用 Windows 时,您可能需要考虑为您的代码使用更高级别的抽象,而不是 Win32 线程函数。并行模式库适合这里的账单(英特尔的线程构建模块也是如此

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

这允许 PPL 将您的任务安排在线程池上,而不是您的应用程序必须显式创建线程。

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

于 2013-03-28T03:31:18.283 回答