【问题标题】:C++ Multi-threading performance of independent data独立数据的C++多线程性能
【发布时间】:2016-02-17 10:41:52
【问题描述】:

让我们有一个只有一个数据成员的非常简单的 C++ 类:

class Container {
public:
    std::vector<Element> elements;
    Container(int elemCount);
};

现在创建 N 个线程来完成一个非常简单的任务:

  1. 创建具有一定向量大小的本地容器
  2. 遍历向量并简单地增加每个元素的值
  3. 重复第 2 步 10.000 次(以秒为单位而不是毫秒)

完整的代码清单可以在Pastebin找到

根据CoreInfo,我的 CPU(Intel Core i5 2400)有 4 个核心,每个核心都有自己的 L1/L2 缓存:

Logical to Physical Processor Map:
*---  Physical Processor 0
-*--  Physical Processor 1
--*-  Physical Processor 2

Logical Processor to Cache Map:
*---  Data Cache          0, Level 1,   32 KB, Assoc   8, LineSize  64
*---  Instruction Cache   0, Level 1,   32 KB, Assoc   8, LineSize  64
*---  Unified Cache       0, Level 2,  256 KB, Assoc   8, LineSize  64
-*--  Data Cache          1, Level 1,   32 KB, Assoc   8, LineSize  64
-*--  Instruction Cache   1, Level 1,   32 KB, Assoc   8, LineSize  64
-*--  Unified Cache       1, Level 2,  256 KB, Assoc   8, LineSize  64
--*-  Data Cache          2, Level 1,   32 KB, Assoc   8, LineSize  64
--*-  Instruction Cache   2, Level 1,   32 KB, Assoc   8, LineSize  64
--*-  Unified Cache       2, Level 2,  256 KB, Assoc   8, LineSize  64
---*  Data Cache          3, Level 1,   32 KB, Assoc   8, LineSize  64
---*  Instruction Cache   3, Level 1,   32 KB, Assoc   8, LineSize  64
---*  Unified Cache       3, Level 2,  256 KB, Assoc   8, LineSize  64
****  Unified Cache       4, Level 3,    6 MB, Assoc  12, LineSize  64
---*  Physical Processor 3

对于高达 100.000 个元素的向量大小,时间完全符合预期:

Elements count: 100.000

Threads: 1
loops: 10000 ms: 650

Threads: 4
loops: 2500 ms: 168
loops: 2500 ms: 169
loops: 2500 ms: 169
loops: 2500 ms: 171

但是,对于更大的向量大小,多核的性能是:

Elements count: 300.000

Threads: 1
loops: 10000 ms: 1968

Threads: 4
loops: 2500 ms: 3817
loops: 2500 ms: 3864
loops: 2500 ms: 3927
loops: 2500 ms: 4008

我的问题:

  1. 谁能给我解释一下这个原因?这是虚假分享吗?如果是这样,如果线程确实共享任何数据并且所有内核都有自己的 L1/L2 缓存和缓存线,这怎么可能??
  2. 在多线程中处理独立数据时,是否可以实现(或接近)线性加速效率?

编辑:感谢所有答案,到目前为止。关于您的问题:

@user2079303:元素只包含一个双数据成员。大小(元素)= 8。完整源代码请见Pastebin

@bku_drytt:resize() 是正确的。我的目的是在每个线程中创建一个包含 elemCount 元素的向量(不管它们的初始值)。

@Jorge González Lorenzo:您在共享 L3 缓存方面是绝对正确的。我进行了另一组测试,仅单线程:

Elements count: 50.000
Threads: 1
loops: 50000 ms: 1615

Elements count: 200.000 (4 times bigger)
Threads: 1
loops: 50000 ms: 1615 (slightly more than 4 time bigger)

Elements count: 800.000 (even 4 times bigger)
Threads: 1
loops: 50000 ms: 42181 (MUCH more than 4 time bigger)

【问题讨论】:

  • Element 有多大?你还记得在启用优化的情况下进行编译吗?
  • 您是否故意在构造函数中调用resize() 而不是reserve()
  • 我的猜测是您正在使用 4 个线程填充 L3 共享缓存(需要 x4 存储,因为每个线程有一个向量),因此导致许多缓存未命中,而在单线程执行中矢量适合它。 L1 和 L2 是每个内核,但 L3 不是。一个公平的比较是使用比 4 个线程执行更大的 x4 向量来运行单线程执行。
  • 您的 CPU 有 6 MB 三级缓存。 100k * 8 字节 = 0.76MB。乘以 4 即 3 MB。三倍(300k 元素)是 9 MB,不适合 L3 缓存。当你的带宽受限时获得线性加速是不可能的,但如果你真的在做真正的工作,这看起来会有所不同。问题是你所有的“工作”很容易被一个缓存未命中所掩盖(你将比单线程版本多 10k * 4)
  • 这真的取决于你的实际问题和访问模式。没有适用于每种访问模式的灵丹妙药(尽管您可能想查找缓存遗忘算法,如果它适合您的算法,那么这些算法非常酷)。而且,如果您真的只是带宽有限,那么巧妙地玩弄线程将永远不会改变这一事实。

标签: c++ multithreading performance


【解决方案1】:

您正在使用 4 个线程填充 L3 共享缓存(需要 x4 存储,因为每个线程有一个向量),因此会导致许多缓存未命中,而在单线程执行中,向量适合它。 L1 和 L2 是每个内核,但 L3 不是。一个公平的比较是使用比 4 个线程执行更大的 x4 向量来运行单线程执行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多