为什么GCC的__builtin_prefetch不能提高性能?

3

我正在编写一个分析社交网络图的程序。这意味着程序需要大量的随机内存访问。我认为预取应该会有所帮助。下面是从一个顶点的邻居读取数值的代码示例。

for (size_t i = 0; i < v.get_num_edges(); i++) {
    unsigned int id = v.neighbors[i];
    res += neigh_vals[id];
}

我将上面的代码转换为下面的代码,并预取顶点邻居的值。

int *neigh_vals = new int[num_vertices];

for (size_t i = 0; i < v.get_num_edges(); i += 128) {
    size_t this_end = std::min(v.get_num_edges(), i + 128);
    for (size_t j = i; j < this_end; j++) {
        unsigned int id = v.neighbors[j];
        __builtin_prefetch(&neigh_vals[id], 0, 2);
    }
    for (size_t j = i; j < this_end; j++) {
        unsigned int id = v.neighbors[j];
        res += neigh_vals[id];
    }
}

在这段 C++ 代码中,我没有重载任何运算符。
不幸的是,这段代码并没有真正提高性能。我想知道为什么。显然,在这种情况下,硬件预取无法工作,因为硬件无法预测内存位置。
我想知道是否是由于 GCC 优化引起的。当我编译代码时,我启用了 -O3。我真的希望即使启用了 -O3,预取也能进一步提高性能。在这种情况下,-O3 优化会将两个循环合并吗?-O3 是否可以默认启用预取?
我使用的是 gcc 版本 4.6.3,并在 Intel Xeon E5-4620 上运行程序。
谢谢, Da

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Basile Starynkevitch
请参考此答案以获取与此相关的问题的解答。 - Basile Starynkevitch
2
看起来你正在一个循环中进行预取,并希望数据在下一个循环中仍然可用。这不太可能有所帮助,而且可能会造成损失。 - Retired Ninja
你需要编辑你的问题以改进它。并且你使用的是哪个版本的gcc编译器,用了哪种处理器? - Basile Starynkevitch
另外:在整个“for”循环中,v.get_num_edges()是否不变?看起来你可以将其赋值给一个变量,而不是每次通过循环顶部调用它。 - Andy Lester
1个回答

9

是的,一些最近版本的GCC(例如2015年3月的4.9版)能够在使用-O3进行优化时发出一些PREFETCH指令(即使没有任何显式的__builtin_prefetch)。

我们不知道get_neighbor在做什么,以及vneigh_val的类型是什么。

预取并不总是有利的。添加显式的__builtin_prefetch可能会使您的代码变慢。 您需要进行测量。

正如Retired Ninja所评论的那样,在一个循环中预取数据,并希望数据在随后的循环中被缓存是错误的。

也许您可以尝试

for (size_t i = 0; i < v.get_num_edges(); i++) {
  fg::vertex_id_t id = v.get_neighbor(i);
  __builtin_prefetch (neigh_val[v.get_neighbor(i+4)]);
  res += neigh_vals[id];
}

你可以通过经验法则,将4用最合适的常数进行替换。

但是我猜测上面的__builtin_prefetch是无用的(因为编译器可能已经能够自动添加它),并且它可能会造成伤害(或者甚至导致程序崩溃,当计算其参数产生未定义行为时,例如如果v.get_neighbor(i+4)是未定义的;然而预取地址空间外的地址不会造成伤害-但可能会减缓您的程序)。请进行基准测试。

参见this answer相关问题的答案。

请注意,在C++中,所有的[]get_neighbor都可以被重载,并变得非常复杂,所以我们不能猜测!

在某些情况下,硬件限制了性能,无论添加了什么样的__builtin_prefetch(添加它们可能会损害性能)。

顺便说一下,您可以传递-O3 -mtune=native -fdump-tree-ssa -S -fverbose-asm以了解编译器正在做什么(并查看生成的转储文件和汇编文件内部);此外,-O3有时会产生比-O2更慢的代码。

如果您有时间浪费在优化上,可以考虑显式多线程, OpenMP, OpenCL。请记住,过早地进行优化是不好的。您进行了基准测试吗?您是否对整个应用程序进行了分析?


4
需要翻译的内容:Just to be clear, “or even crash the program” does not mean prefetching generates faults if the address is invalid—but the address expression itself must be valid.清楚明确地说,“甚至导致程序崩溃”并不意味着如果地址无效,则预取会生成故障 - 但地址表达式本身必须是有效的。 - Jon Purdy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接