确定最佳线程数

时间:2016-11-22 03:01:56

标签: c++ multithreading c++11

因此,作为学校作业的一部分,我们被要求通过构建玩具程序来确定我们的个人计算机的最佳线程数。

首先,我们要创建一个需要20到30秒才能运行的任务。我选择进行抛硬币模拟,累计然后显示头尾的总数。在我的机器上,一个线程上的300,000,000次投掷以25秒结束。在那之后,我去了2个线程,然后是4个线程,然后是8个,16个,32个,并且,为了好玩,我去了100个。 结果如下:

* Thread Tosses per thread time(seconds) * ------------------------------------------ * 1 300,000,000 25 * 2 150,000,000 13 * 4 75,000,000 13 * 8 37,500,000 13 * 16 18,750,000 14 * 32 9,375,000 14 * 100 3,000,000 14

以下是我正在使用的代码:

void toss()
{
    int heads = 0, tails = 0;
    default_random_engine gen;
    uniform_int_distribution<int> dist(0,1);
    int max =3000000;                          //tosses per thread
    for(int x = 0; x < max; ++x){(dist(gen))?++heads:++tails;}
    cout<<heads<<" "<<tails<<endl;
}

int main()
{
    vector<thread>thr;
    time_t st, fin;
    st = time(0);

    for(int i = 0;i < 100;++i){thr.push_back(thread(toss));} //thread count
    for(auto& thread: thr){thread.join();}

    fin = time(0);
    cout<<fin-st<<" seconds\n";
    return 0;
}

现在主要问题是:

过去某一点,我预计随着更多线程的添加,计算速度会有相当大的下降,但结果似乎并没有显示出来。

我的代码是否会产生根本性的错误,会产生这些结果,或者这种行为是否正常?我对多线程很新,所以我觉得它是前者......

谢谢!

编辑:我在带有2.16 GHz Core 2 Duo(T7400)处理器的macbook上运行它

2 个答案:

答案 0 :(得分:9)

你的结果对我来说似乎很正常。虽然线程创建有成本,但 非常多(特别是与测试的每秒粒度相比)。额外的100个线程创建,破坏和可能的上下文切换不会改变你的时间超过我打赌的几毫秒。

在我的Intel i7-4790 @ 3.60 GHz上运行我得到这些数字:

threads - seconds
-----------------
1       -  6.021
2       -  3.205
4       -  1.825
8       -  1.062
16      -  1.128
32      -  1.138
100     -  1.213
1000    -  2.312
10000   - 23.319

需要很多很多线程才能达到额外线程产生明显差异的程度。只有当我达到1000个线程时才能看到线程管理已经产生了显着的差异,而在10,000时,它使循环相形见绌(循环在此时只进行了30,000次抛弃)。

至于你的任务,看到你的系统的最佳线程数应该与可以一次执行的可用线程相同应该是相当简单的。执行另一个线程之前没有任何处理能力,直到完成或产生一个线程,这无助于你完成更快。而且,任何更少的线程,你没有使用所有可用的资源。我的CPU有8个线程,图表反映了这一点。

答案 1 :(得分:4)

编辑2 - 进一步阐述&#34;缺乏性能损失&#34;部分原因是受欢迎的需求:

  

......我预计计算会有相当大的下降   速度随着更多线程的增加而增加,但结果似乎并未表现出来   这一点。

我制作了这张巨大的图表,以便更好地说明缩放比例。

enter image description here

解释结果:

蓝色条表示完成所有投掷的总时间。尽管该时间一直减少到256个线程,但是线程数加倍的收益会变得越来越小。我运行此测试的CPU有4个物理内核和8个逻辑内核。从4个内核到8个内核,扩展性非常好,然后它一落千丈。管道饱和度允许一直到256的微小增益,但它根本不值得。

红色条表示每次折腾的时间。它对于1和2个线程几乎相同,因为CPU管道还没有达到完全饱和。它在4个线程中受到轻微打击,它仍然可以正常运行,但现在管道已经饱和,并且在8个线程上它确实表明逻辑线程与物理线程不同,在8个线程之上逐渐变得更糟。

绿色条显示了开销,或者实际性能相对于线程加倍所带来的预期双重提升的降低程度。推动可用的逻辑核心,导致开销急剧上升。请注意,这主要是线程同步,实际线程调度开销可能在给定点之后保持不变,线程必须接收的活动时间最小,这解释了为什么线程切换没有达到压倒工作量。实际上,在4k线程中没有严重的性能下降,这是预期的,因为现代系统必须能够并且通常并行运行超过一千个线程。而且,大多数下降是由于线程同步,而不是线程切换。

黑色轮廓条表示相对于最低时间的时差。在没有管道过饱和的情况下,在8个螺纹中我们只能失去约14%的绝对性能,这是一件好事,因为大多数情况下并不是真的值得对整个系统施加太大压力。它还显示1个线程仅比CPU可以拉出的最大值慢约6倍。这给出了一个如何将良好的逻辑内核与物理内核进行比较的数字,100%额外的逻辑内核可以提高50%的性能,在这个用例中,逻辑线程的性能比物理线程好50%,这也与我们看到从47到4的增强率为47%。请注意,这是一个非常简单的工作负载,在更苛刻的情况下,这个特定CPU的接近20-25%,在某些边缘情况下实际上有性能击中。

编辑1 - 我愚蠢地忘记将计算工作量与线程同步工作负载隔离开来。

运行测试几乎没有工作表明,对于高线程计数,线程管理部分占用大部分时间。因此,线程切换损失确实非常小,并且可能在某个点之后是常数。

如果你把自己置于线程调度程序制造商的角度,这将是很有意义的。可以很容易地保护调度程序免受不合理的高切换到工作比率的阻塞,因此调度程序在切换到另一个线程之前可能给予线程的最小时间窗口,而其余的被搁置。这确保了切换到工作比率永远不会超过合理的限制。除了疯狂的线程切换之外,停止其他线程会好得多,因为CPU主要是切换并且做很少的实际工作。

最佳线程数是逻辑CPU核心的可用数量。这实现了最佳的流水线饱和度。

如果使用更多,则由于线程上下文切换的成本而导致性能下降。线程越多,惩罚越多。

如果使用less,则不会充分利用硬件潜力。

还存在工作负载粒度问题,这在使用互斥(例如互斥锁)时非常重要。如果您的并发性太细,即使在8线程机器上从1到2个线程,您也可能会遇到性能下降。您希望尽可能减少同步,在同步之间尽可能多地完成工作,否则您可能会遇到巨大的性能下降。

注意物理和逻辑CPU核心之间的区别。具有超线程的处理器每个物理核心可以具有多个逻辑核心。 &#34;二次&#34;逻辑核心与&#34; primary&#34;具有相同的计算能力。因为它们仅用于利用处理器管道使用中的空缺。

例如,如果你有一个4核的8线程CPU,在完全扩展的工作负载的情况下,你会看到从1到4个线程的性能提高4倍,但从4线程到8个线程的性能要少得多,从vu1p3n0x的回答中可以看出。

您可以查看here以了解确定可用CPU核心数的方法。