OpenMP动态调度的使用及其对性能的影响——如何查找详情
The use of OpenMP dynamic scheduling and its impact on performance - how to find detail
当 运行 一个不使用调度的算法和使用调度的算法时,性能差异是巨大的 - 使用调度,算法在 4 秒内完成,而不是在 14 秒内完成。我认为 perf 会提供一些关于为什么会发生这种情况的见解,但统计数据非常相似。
可以安全地假设通过处理动态调度我已经解决了负载平衡的一些问题吗?我希望在性能细节中找到一些东西。以下是有帮助的详细信息。还有用于使用调度的 Pagerank 的代码...
# pragma omp parallel for schedule(dynamic, 64)
for (int u = 0; u < vertex_count; u++) {
int* in_edge = G.getAdjList(u);
double sum = 0.0;
for (int j = 0; j < in_edge_counts[u]; j++) {
int v = in_edge[j];
sum += conntrib[v];
}
pr_temp[u] = sum * damp + adj;
}
使用调度
107470.977295 task-clock (msec) # 1.743 CPUs utilized
1,187 context-switches # 0.011 K/sec
44 cpu-migrations # 0.000 K/sec
2,279,522 page-faults # 0.021 M/sec
255,920,277,205 cycles # 2.381 GHz (20.00%)
17,116,048,117 stalled-cycles-frontend # 6.69% frontend cycles idle (20.02%)
153,944,352,418 stalled-cycles-backend # 60.15% backend cycles idle (20.02%)
148,412,677,859 instructions # 0.58 insn per cycle
# 1.04 stalled cycles per insn (30.01%)
27,479,936,585 branches # 255.696 M/sec (40.01%)
321,470,463 branch-misses # 1.17% of all branches (50.01%)
78,562,370,506 L1-dcache-loads # 731.010 M/sec (50.00%)
2,075,635,902 L1-dcache-load-misses # 2.64% of all L1-dcache hits (49.99%)
3,100,740,665 LLC-loads # 28.852 M/sec (50.00%)
964,981,918 LLC-load-misses # 31.12% of all LL-cache hits (50.00%)
不使用调度
106872.881349 task-clock (msec) # 1.421 CPUs utilized
1,237 context-switches # 0.012 K/sec
69 cpu-migrations # 0.001 K/sec
2,262,865 page-faults # 0.021 M/sec
254,236,425,448 cycles # 2.379 GHz (20.01%)
14,384,218,171 stalled-cycles-frontend # 5.66% frontend cycles idle (20.04%)
163,855,226,466 stalled-cycles-backend # 64.45% backend cycles idle (20.03%)
149,318,162,762 instructions # 0.59 insn per cycle
# 1.10 stalled cycles per insn (30.03%)
27,627,422,078 branches # 258.507 M/sec (40.03%)
213,805,935 branch-misses # 0.77% of all branches (50.03%)
78,495,942,802 L1-dcache-loads # 734.480 M/sec (50.00%)
2,089,837,393 L1-dcache-load-misses # 2.66% of all L1-dcache hits (49.99%)
3,166,900,999 LLC-loads # 29.632 M/sec (49.98%)
929,170,535 LLC-load-misses # 29.34% of all LL-cache hits (49.98%)
perf stat
不是了解多线程程序中动态调度的性能影响的正确工具。为此,您需要一个允许您在分析期间区分线程和时间的工具。
perf timechart
may be worth a shot, but it is not aware of OpenMP specifically. You will get the best insight by using a analysis tool that is made specifically for OpenMP. Furthermore to get the dynamics, I recommend using a tracing / timeline tool as opposed to a profiling tool that only shows you a summary. Example of such a tool would be Intel VTune or Score-P (tracing) / Vampir(可视化)。
schedule(dynamic, 64)
告诉 OpenMP 不要假设每个内循环迭代花费相同的时间,IIRC。
因此静态调度将工作分解为 u 值的范围,但是您的一个线程最终必须完成比其余线程更多的总工作(或由于某种原因需要很长时间),从而延迟了所有线程的总时间完成。
It was running on a compute server with Four AMD Opterons. The machine was mostly idle at the time. The only difference between the two is the use of scheduling. I left out the time because the time is inclusive of a pre processing step that both incur but it still differs by 10 seconds.
在那种情况下,1.4 与 1.7 CPU 的总体利用率可以用您所说的 4 / 14 秒期间的更多利用率来解释。您可以通过使程序在并行部分之前退出并对其进行分析来近似有趣部分的结果。从总计数中减去这些计数以获得非常粗略的近似值。
IDK为什么工作不平衡;这是你的代码+数据; G.getAdjList(u)
可能需要更多时间,或者更有可能 in_edge_counts[u]
对于某些线程来说平均而言更大。
conntrib[in_edge[j]]
的位置差异可能会产生很大差异,当不同的 in_edge
元素接近先前值时,会导致分散读取或缓存命中的缓存未命中。
当您有不同的内核竞争内存 + 最后一级缓存带宽时,延迟为 in_edge
值的缓存行请求提供服务比延迟响应 conntrib[]
,因为 CPU 需要 in_edge[]
数据才能知道它需要哪些缓存行来获取更多 conntrib
数据。因此 in_edge
缓存未命中会降低内存并行性,可能会使该线程获得更少的内存带宽份额。
不知道这些影响会平衡多少。您的数据更有可能分布不均。
太糟糕了,AMD 没有高效的 AVX2 收集,因为您的数据非常适合 vpgatherdd ymm
。并不是说它对公平性或任何事情都有帮助,只是(在 Skylake 上)相对于标量收集提供可能较小的加速。
当 运行 一个不使用调度的算法和使用调度的算法时,性能差异是巨大的 - 使用调度,算法在 4 秒内完成,而不是在 14 秒内完成。我认为 perf 会提供一些关于为什么会发生这种情况的见解,但统计数据非常相似。
可以安全地假设通过处理动态调度我已经解决了负载平衡的一些问题吗?我希望在性能细节中找到一些东西。以下是有帮助的详细信息。还有用于使用调度的 Pagerank 的代码...
# pragma omp parallel for schedule(dynamic, 64)
for (int u = 0; u < vertex_count; u++) {
int* in_edge = G.getAdjList(u);
double sum = 0.0;
for (int j = 0; j < in_edge_counts[u]; j++) {
int v = in_edge[j];
sum += conntrib[v];
}
pr_temp[u] = sum * damp + adj;
}
使用调度
107470.977295 task-clock (msec) # 1.743 CPUs utilized
1,187 context-switches # 0.011 K/sec
44 cpu-migrations # 0.000 K/sec
2,279,522 page-faults # 0.021 M/sec
255,920,277,205 cycles # 2.381 GHz (20.00%)
17,116,048,117 stalled-cycles-frontend # 6.69% frontend cycles idle (20.02%)
153,944,352,418 stalled-cycles-backend # 60.15% backend cycles idle (20.02%)
148,412,677,859 instructions # 0.58 insn per cycle
# 1.04 stalled cycles per insn (30.01%)
27,479,936,585 branches # 255.696 M/sec (40.01%)
321,470,463 branch-misses # 1.17% of all branches (50.01%)
78,562,370,506 L1-dcache-loads # 731.010 M/sec (50.00%)
2,075,635,902 L1-dcache-load-misses # 2.64% of all L1-dcache hits (49.99%)
3,100,740,665 LLC-loads # 28.852 M/sec (50.00%)
964,981,918 LLC-load-misses # 31.12% of all LL-cache hits (50.00%)
不使用调度
106872.881349 task-clock (msec) # 1.421 CPUs utilized
1,237 context-switches # 0.012 K/sec
69 cpu-migrations # 0.001 K/sec
2,262,865 page-faults # 0.021 M/sec
254,236,425,448 cycles # 2.379 GHz (20.01%)
14,384,218,171 stalled-cycles-frontend # 5.66% frontend cycles idle (20.04%)
163,855,226,466 stalled-cycles-backend # 64.45% backend cycles idle (20.03%)
149,318,162,762 instructions # 0.59 insn per cycle
# 1.10 stalled cycles per insn (30.03%)
27,627,422,078 branches # 258.507 M/sec (40.03%)
213,805,935 branch-misses # 0.77% of all branches (50.03%)
78,495,942,802 L1-dcache-loads # 734.480 M/sec (50.00%)
2,089,837,393 L1-dcache-load-misses # 2.66% of all L1-dcache hits (49.99%)
3,166,900,999 LLC-loads # 29.632 M/sec (49.98%)
929,170,535 LLC-load-misses # 29.34% of all LL-cache hits (49.98%)
perf stat
不是了解多线程程序中动态调度的性能影响的正确工具。为此,您需要一个允许您在分析期间区分线程和时间的工具。
perf timechart
may be worth a shot, but it is not aware of OpenMP specifically. You will get the best insight by using a analysis tool that is made specifically for OpenMP. Furthermore to get the dynamics, I recommend using a tracing / timeline tool as opposed to a profiling tool that only shows you a summary. Example of such a tool would be Intel VTune or Score-P (tracing) / Vampir(可视化)。
schedule(dynamic, 64)
告诉 OpenMP 不要假设每个内循环迭代花费相同的时间,IIRC。
因此静态调度将工作分解为 u 值的范围,但是您的一个线程最终必须完成比其余线程更多的总工作(或由于某种原因需要很长时间),从而延迟了所有线程的总时间完成。
It was running on a compute server with Four AMD Opterons. The machine was mostly idle at the time. The only difference between the two is the use of scheduling. I left out the time because the time is inclusive of a pre processing step that both incur but it still differs by 10 seconds.
在那种情况下,1.4 与 1.7 CPU 的总体利用率可以用您所说的 4 / 14 秒期间的更多利用率来解释。您可以通过使程序在并行部分之前退出并对其进行分析来近似有趣部分的结果。从总计数中减去这些计数以获得非常粗略的近似值。
IDK为什么工作不平衡;这是你的代码+数据; G.getAdjList(u)
可能需要更多时间,或者更有可能 in_edge_counts[u]
对于某些线程来说平均而言更大。
conntrib[in_edge[j]]
的位置差异可能会产生很大差异,当不同的 in_edge
元素接近先前值时,会导致分散读取或缓存命中的缓存未命中。
当您有不同的内核竞争内存 + 最后一级缓存带宽时,延迟为 in_edge
值的缓存行请求提供服务比延迟响应 conntrib[]
,因为 CPU 需要 in_edge[]
数据才能知道它需要哪些缓存行来获取更多 conntrib
数据。因此 in_edge
缓存未命中会降低内存并行性,可能会使该线程获得更少的内存带宽份额。
不知道这些影响会平衡多少。您的数据更有可能分布不均。
太糟糕了,AMD 没有高效的 AVX2 收集,因为您的数据非常适合 vpgatherdd ymm
。并不是说它对公平性或任何事情都有帮助,只是(在 Skylake 上)相对于标量收集提供可能较小的加速。