在使用 OpenMP 线程时,
每个线程都可以声明自己的一组私有变量。假设获取每个线程私有的数据比获取所有线程可见的数据具有更低的延迟是否正确。换句话说,线程局部变量是否被缓存?
说每个线程,都想使用一个线程私有的 STL 数据容器,比如
std::vector
. 在单线程 C++ 代码中,数据std::vector
存储在堆中。那么多线程案例呢?线程私有 std::vectors 的数据是否仍存储在堆上?
在使用 OpenMP 线程时,
每个线程都可以声明自己的一组私有变量。假设获取每个线程私有的数据比获取所有线程可见的数据具有更低的延迟是否正确。换句话说,线程局部变量是否被缓存?
说每个线程,都想使用一个线程私有的 STL 数据容器,比如std::vector
. 在单线程 C++ 代码中,数据std::vector
存储在堆中。那么多线程案例呢?线程私有 std::vectors 的数据是否仍存储在堆上?
除非您使用的是 NUMA 机器,否则内存是统一的。
假设获取每个线程私有的数据比获取所有线程可见的数据具有更低的延迟是否正确。
线程本地存储本质上并不比所有线程可见的内存“更快”。但是,仅由一个线程使用的内存不太可能受到缓存一致性影响 - 因为它仅由单个线程访问。
换句话说,线程局部变量是否被缓存?
不必要。如果它不适合 CPU 缓存,则绝对不会出现这种情况。共享数据也可能同时位于多个内核的缓存中。
那么多线程案例呢?线程私有 std::vectors 的数据是否仍存储在堆上?
是的,无论线程数如何,它们都将在堆中。
在几乎所有广泛使用的 OpenMP 运行时中,私有变量和共享变量的实现方式都不同。
private
自动变量驻留在每个执行线程的堆栈中,threadprivate
变量驻留在 TLS 中。自动私有变量也可以优化为像往常一样注册。
shared
并行区域的变量通常实现为一个结构,该结构通过地址作为参数传递给每个线程函数,然后使用额外的指针取消引用来访问每个共享变量。除了一些编译器将共享变量视为隐式volatile
并发出加载/更新/存储指令的全部范围,尽管 OpenMP 提供了一个宽松的内存模型,允许不同线程中共享变量的可见值之间存在某种程度的不一致,直到某些同步要点,其中一个要点是显式flush
指令(仍然flush
是最广泛误解的 OpenMP 功能,甚至语言制造商也无法在标准文档中获得关于其使用正确的示例)。
至于在多线程情况下在堆上分配数据,堆操作本质上是序列化的,因为大多数堆实现使用链表或类似的数据结构。除了通常的分配器不关心由不同线程分配的数据是否最终可能共享一个缓存行,以及这是否可能导致错误共享和相关的性能损失。有专门的多线程分配器,如hoard
, ptmalloc
, umem
,tcmalloc
等,它们试图以使用更多内存为代价来解决这些问题。其中一些(例如tcmalloc
)也支持NUMA。tcmalloc
文档声称它做了某种“魔术”来使 STL 容器使用其分配器而不是默认分配器,但我不能同意,因为我不是tcmalloc
C++ 和 C++ 的重度用户。
在 NUMA 系统上运行时要考虑的一件事是线程绑定。一些 OpenMP 运行时已经包含控制线程与内核绑定的规定,即将到来的 OpenMP 标准很可能包括用于指定绑定属性的标准框架,因为它现在正在语言委员会中进行讨论。