C++独立数据的多线程性能

C++ Multi-threading performance of independent data

本文关键字:多线程 性能 数据 独立 C++      更新时间:2023-10-16

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

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

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

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

完整的代码列表可以在Pastebin 上找到

根据CoreInfo,我的CPU(英特尔酷睿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. 有人能向我解释一下原因吗?这是虚假的分享吗?如果是这样,如果线程NOT共享任何数据,并且所有内核都有自己的L1/L2缓存和缓存线,这怎么可能呢
  2. 在多个线程中处理独立数据时,是否有可能实现(或接近)线性加速效率

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

@user2079303:元素只包含一个双数据元素。sizeof(Element)=8。有关完整的源代码,请参阅Pastebin。

@bkudryt: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)

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