我希望看到虚假共享的效果。为此,我尝试设计一个小实验,但结果出乎意料。
我有一个包含100 m个整数的数组。将其视为m x n矩阵。一个线程更改了奇数索引行,其他线程更改了偶数索引行。
实验A:列数为16。因此,每行为64字节,这恰好是我的缓存行大小。由于每个线程一次仅处理1条缓存行,因此不应存在任何错误共享。因此,我希望能达到100%左右的加速比。
实验B:列数为8。每个线程一次更改32个字节,这是高速缓存行的一半。例如,如果线程1处理行33,则应从线程0传输数据,因为线程1已经处理了同一高速缓存行中的行32。 (反之亦然,顺序无关紧要)。由于这种通信,因此加速应该较低。
#include <iostream>
#include <omp.h>
using namespace std;
int main(int argc, char** argv) {
if(argc != 3) {
cout << "Usage: " << argv[0] << " <iteration> <col_count>" << endl;
return 1;
}
int thread_count = omp_get_max_threads();
int iteration = atoi(argv[1]);
int col_count = atoi(argv[2]);
int arr_size = 100000000;
int* A = (int*) aligned_alloc(16 * sizeof(int), arr_size * sizeof(int));
int row_count = arr_size / col_count;
int row_count_per_thread = row_count / thread_count;
#pragma omp parallel
{
int thread_id = omp_get_thread_num();
long long total = 1ll * iteration * row_count_per_thread * col_count;
printf("%lld\n", total);
for(int t = 0; t < iteration; t++) {
for(int i = 0; i < row_count_per_thread; i++) {
int start = (i * thread_count + thread_id) * col_count;
for(int j = start; j < start + col_count; j++) {
if(A[j] % 2 == 0)
A[j] += 3;
else
A[j] += 1;
}
}
}
}
return 0;
}
我通过以下方式以不同的配置运行此代码:
time taskset -c 0-1 ./run 100 16
这是100次迭代的结果:
Thread Column Optimization Time (secs)
_______________________________________________________
1 16 O3 7.6
1 8 O3 7.7
2 16 O3 7.7
2 8 O3 7.7
1 16 O0 35.9
1 8 O0 34.3
2 16 O0 19.3
2 8 O0 18.2
如您所见,尽管O3优化可提供最佳结果,但它们却很奇怪,因为增加线程数并不能提高速度。对我来说,O0优化结果更具解释性。
真正的问题:看最后两行。对于这两种情况,我的加速都快100%,但是我希望实验B的执行时间会更长,因为它存在错误共享的问题。我的实验或理解有什么问题?
我用
g++ -std=c++11 -Wall -fopenmp -O0 -o run -Iinc $(SOURCE)
和
g++ -std=c++11 -Wall -fopenmp -O3 -o run -Iinc $(SOURCE)
如果我的问题不清楚或需要更多细节,请告诉我。
更新:规格:
MemTotal: 8080796 kB
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 71
Model name: Intel(R) Core(TM) i7-5700HQ CPU @ 2.70GHz
Stepping: 1
CPU MHz: 2622.241
CPU max MHz: 3500,0000
CPU min MHz: 800,0000
BogoMIPS: 5387.47
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 6144K
NUMA node0 CPU(s): 0-7
更新2:我尝试了不同的iteration_count
和arr_size
参数,以便使数组适合L2,L1缓存,同时使元素更改总数保持不变。但是结果还是一样。
谢谢。
答案 0 :(得分:5)
您的-O3计时似乎与your processor的1通道内存访问速度一致。使用2通道内存配置可能会使速度提高2倍,但不太可能给结果带来任何其他差异。请记住,在处理器上,内核之间共享一个L3高速缓存,因此任何错误共享都很可能在L3高速缓存级别得到解决,并且不会导致外部存储器总线上的额外负载。
您的代码有更多的问题(不仅仅是“缓慢的”内存访问),可能会阻止您看到错误共享的影响。
首先,鉴于线程调度所涉及的时间不可预测性,两个线程几乎不可能竞争完全相同的缓存行。
第二,即使它们确实存在冲突,也将是暂时的,因为导致不对称减速的任何因素都将导致“较慢”线程延迟其扫描,直到其超出冲突的内存范围为止。 / p>
第三,如果它们恰好在同一内核的两个硬件线程上运行,它们将访问完全相同的缓存实例,并且不会有缓存冲突。
要“修复”所有这些,您需要更多的线程(或绑定到特定内核的线程)和更紧密的内存区域以防止可能的冲突。 “最好”的结果是,如果您的线程只竞争一条内存缓存行。