如何在C++中将指针指定为"thread_local"存储?
How to specify a pointer as a "thread_local" storage in C++?
我目前正在优化一段数学运算代码,其中它循环访问指针存储并将结果保存在原地。 我注意到在每个赋值中,编译器都会发出一条内存存储指令,如下所示(要注意vmovaps
):
114 [1] top_data_c[pc] += w1 * bottom_data_hwc[o1 + pc];
0x55555558bf70 <+ 2624> c4 c1 78 10 0c 02 vmovups (%r10,%rax,1),%xmm1
0x55555558bf76 <+ 2630> 48 83 c1 01 add $0x1,%rcx
0x55555558bf7a <+ 2634> c4 c3 75 18 4c 02 10 01 vinsertf128 $0x1,0x10(%r10,%rax,1),%ymm1,%ymm1
0x55555558bf82 <+ 2642> c4 c2 25 a8 0c 04 vfmadd213ps (%r12,%rax,1),%ymm11,%ymm1
0x55555558bf88 <+ 2648> c4 c1 7c 29 0c 04 vmovaps %ymm1,(%r12,%rax,1)
115 [1] top_data_c[pc] += w2 * bottom_data_hwc[o2 + pc];
0x55555558bf8e <+ 2654> c4 c1 78 10 04 01 vmovups (%r9,%rax,1),%xmm0
0x55555558bf94 <+ 2660> c4 c3 7d 18 44 01 10 01 vinsertf128 $0x1,0x10(%r9,%rax,1),%ymm0,%ymm0
0x55555558bf9c <+ 2668> c4 c2 7d b8 ca vfmadd231ps %ymm10,%ymm0,%ymm1
0x55555558bfa1 <+ 2673> c4 c1 7c 29 0c 04 vmovaps %ymm1,(%r12,%rax,1)
116 [1] top_data_c[pc] += w3 * bottom_data_hwc[o3 + pc];
0x55555558bfa7 <+ 2679> c4 c1 78 10 04 00 vmovups (%r8,%rax,1),%xmm0
0x55555558bfad <+ 2685> c4 c3 7d 18 44 00 10 01 vinsertf128 $0x1,0x10(%r8,%rax,1),%ymm0,%ymm0
0x55555558bfb5 <+ 2693> c4 c2 75 98 c1 vfmadd132ps %ymm9,%ymm1,%ymm0
0x55555558bfba <+ 2698> c4 c1 7c 29 04 04 vmovaps %ymm0,(%r12,%rax,1)
117 [1] top_data_c[pc] += w4 * bottom_data_hwc[o4 + pc];
0x55555558bfc0 <+ 2704> c5 f8 10 0c 07 vmovups (%rdi,%rax,1),%xmm1
0x55555558bfc5 <+ 2709> c4 e3 75 18 4c 07 10 01 vinsertf128 $0x1,0x10(%rdi,%rax,1),%ymm1,%ymm1
0x55555558bfcd <+ 2717> c4 c2 75 b8 c0 vfmadd231ps %ymm8,%ymm1,%ymm0
0x55555558bfd2 <+ 2722> c4 c1 7c 29 04 04 vmovaps %ymm0,(%r12,%rax,1)
0x55555558bfd8 <+ 2728> 48 83 c0 20 add $0x20,%rax
0x55555558bfdc <+ 2732> 48 39 4d c0 cmp %rcx,-0x40(%rbp)
0x55555558bfe0 <+ 2736> 77 8e ja 0x55555558bf70
但是,当我将指针更改为本地"堆栈数组"变量时,即T top_data_c[1024]
,存储指令仅出现在循环的末尾:
114 [1] top_data_c[pc] += w1 * bottom_data_hwc[o1 + pc];
0x55555558bbe0 <+ 1712> c5 f8 10 0c 03 vmovups (%rbx,%rax,1),%xmm1
0x55555558bbe5 <+ 1717> 48 83 c1 01 add $0x1,%rcx
0x55555558bbe9 <+ 1721> c4 e3 75 18 4c 03 10 01 vinsertf128 $0x1,0x10(%rbx,%rax,1),%ymm1,%ymm1
0x55555558bbf1 <+ 1729> c4 c2 25 a8 0c 04 vfmadd213ps (%r12,%rax,1),%ymm11,%ymm1
0x55555558bbf7 <+ 1735> c5 fc 28 c1 vmovaps %ymm1,%ymm0
115 [1] top_data_c[pc] += w2 * bottom_data_hwc[o2 + pc];
0x55555558bbfb <+ 1739> c4 c1 78 10 0c 03 vmovups (%r11,%rax,1),%xmm1
0x55555558bc01 <+ 1745> c4 c3 75 18 4c 03 10 01 vinsertf128 $0x1,0x10(%r11,%rax,1),%ymm1,%ymm1
0x55555558bc09 <+ 1753> c4 c2 7d 98 ca vfmadd132ps %ymm10,%ymm0,%ymm1
116 [1] top_data_c[pc] += w3 * bottom_data_hwc[o3 + pc];
0x55555558bc0e <+ 1758> c4 c1 78 10 04 02 vmovups (%r10,%rax,1),%xmm0
0x55555558bc14 <+ 1764> c4 c3 7d 18 44 02 10 01 vinsertf128 $0x1,0x10(%r10,%rax,1),%ymm0,%ymm0
0x55555558bc1c <+ 1772> c4 e2 35 b8 c8 vfmadd231ps %ymm0,%ymm9,%ymm1
117 [1] top_data_c[pc] += w4 * bottom_data_hwc[o4 + pc];
0x55555558bc21 <+ 1777> c4 c1 78 10 04 01 vmovups (%r9,%rax,1),%xmm0
0x55555558bc27 <+ 1783> c4 c3 7d 18 44 01 10 01 vinsertf128 $0x1,0x10(%r9,%rax,1),%ymm0,%ymm0
0x55555558bc2f <+ 1791> c4 c2 75 98 c0 vfmadd132ps %ymm8,%ymm1,%ymm0
0x55555558bc34 <+ 1796> c4 c1 7c 29 04 04 vmovaps %ymm0,(%r12,%rax,1)
0x55555558bc3a <+ 1802> 48 83 c0 20 add $0x20,%rax
0x55555558bc3e <+ 1806> 48 3b 8d c8 fb ff ff cmp -0x438(%rbp),%rcx
0x55555558bc45 <+ 1813> 72 99 jb 0x55555558bbe0
由于线程不安全,编译器使指针存储操作远离优化。
在这样的实现中,声明堆栈数组或临时变量复制看起来很脏,有没有办法使这样的指针存储对编译器的意义是线程安全的?当然,这种计算是完全线程安全的(它的工作方式与GPU非常相似)。
除非你出于某种原因将top_data_c
指针声明为volatile
,否则编译器可以自由地假设它写入的数据没有发生外部(包括并发)修改,所以缺乏优化不应该归咎于线程(而不是通过线程局部语义解决)。
这里真正的问题是别名 - 编译器不能假设top_data_c[pc]
不在bottom_data_hwc
内部的某个地方(它可能与bottom_data_hwc[o4 + pc]
相同,谁知道呢?),所以它必须做存储。在"堆栈数组"情况下缓解此问题的是(如果我没记错的话)top_data_c
是一个数组而不是指针(不是它在堆栈上或它是线程本地的)。
对严格混叠规则的简短游览:编译器可以假定指向不兼容类型的指针(例如int
和double
) 不能指向同一位置。如果你在void foo(int* x, double* y)
那么,在严格的混叠规则下,写入x
不能改变你从y
读取的内容(反之亦然),所以编译器可以自由地重新排序或删除存储,并读取到/读x
和y
在这个函数中。
但在void foo(double* x, double* y)
内部,这种保证消失了。如果x == y
(或x == y + n
),则写入和读取x
和y
可能会相互交互,编译器别无选择,只能执行每个加载/存储。
您应该查看与restrict
相关的关键字,该关键字向编译器发出信号"仅通过此函数中的x
(及其副本)访问x
发生"。一些材料:
限制关键字在C++中是什么意思?
https://cellperformance.beyond3d.com/articles/2006/05/demystifying-the-restrict-keyword.html
http://assemblyrequired.crashworks.org/load-hit-stores-and-the-__restrict-keyword/
- 在std::thread中,joinable()然后join()线程安全吗
- 对于MacOS上的G++,如何添加默认的include目录/usr/local/include和默认的库搜索路径/usr
- <Windows>为什么 std::thread::native_handle 返回类型为"long long unsigned int"的值,而不是 void*(又名 HANDLE)?
- 分离一个静态常量 std::thread?
- 尝试使用 std::vector<std::thread时出现静态断言失败错误>
- 使用 thread 类在 C++ 中构造线程的动态数组时出错
- 当指向对象的指针作为参数传递给 std::thread 时,内存可见性
- 如何从 std::thread 返回值
- 在C++中使用并行化的预期速度是多少(不是 OpenMp,而是 <thread>)
- "local scope"中的 C++ 初始化静态变量
- 将 std::thread by 值推送到列表中
- 转发变量参数列表以模拟 std::thread
- 嵌入式设备 -> std::thread -> FreeRTOS?
- 对 'std::thread::_M_start_thread CMake 的未定义引用进行基准测试
- 在 /usr/local/lib 下找不到库
- std::thread 增加 DLL 引用计数,从而防止卸载 DLL
- C++编译器错误"cannot be thread-local because it has non-POD type""
- 消除警告"construction of local static object is not thread-safe"
- TLS 变量上的"illegal thread-local reference to regular symbol"错误
- ActiveMQ-CPP 在发送时抛出"Thread local storage limit"达到