【发布时间】:2016-08-25 04:34:00
【问题描述】:
我有一个名为 reorder.cc 的源文件,如下所示:
void reorder(float *output, float *input) {
output[56] = input[0];
output[57] = input[1];
output[58] = input[2];
output[59] = input[3];
output[60] = input[4];
...
output[75] = input[19];
output[76] = input[20];
output[77] = input[21];
output[78] = input[22];
output[79] = input[23];
output[80] = input[24];
...
output[98] = 0;
output[99] = 0;
output[100] = 0;
output[101] = 0;
output[102] = 0;
output[103] = 0;
output[104] = 0;
output[105] = input[1];
output[106] = input[2];
output[107] = input[3];
output[108] = input[4];
output[109] = input[5];
output[110] = input[6];
output[111] = 0;
...
}
函数 reorder 有一个很长的从输入到输出缓冲区的内存移动操作列表。从输入到输出的对应关系很复杂,但通常有足够长的运行,至少 10 个浮点数可以保证是连续的。运行被从任意输入索引开始的新运行中断,或者是“0”值。
带有 (g++-6 -march=native -Ofast -S reorder.cc) 的关联程序集文件 (.S) 文件生成以下程序集:
.file "reorder.cc"
.text
.p2align 4,,15
.globl _Z9optimizedPfS_
.type _Z9optimizedPfS_, @function
_Z9optimizedPfS_:
.LFB0:
.cfi_startproc
movss (%rsi), %xmm0
movss %xmm0, 32(%rdi)
movss 4(%rsi), %xmm0
movss %xmm0, 36(%rdi)
movss 8(%rsi), %xmm0
movss %xmm0, 40(%rdi)
movss 12(%rsi), %xmm0
movss %xmm0, 44(%rdi)
movss 16(%rsi), %xmm0
movss %xmm0, 48(%rdi)
movss 20(%rsi), %xmm0
movss %xmm0, 52(%rdi)
movss 28(%rsi), %xmm0
movss %xmm0, 60(%rdi)
movss 32(%rsi), %xmm0
movss %xmm0, 64(%rdi)
movss 36(%rsi), %xmm0
...
... 对应于每条装配线的单个移动单个标量 (fp32) 值。我认为编译器足够智能,可以编译成更智能的指令,例如 MOVDQU(移动未对齐双四字,适用于 128 位字)运行足够长的时间?
我正在考虑手写一个简单的解析器,它需要长时间运行并自动调用 movdqu,但我发现这很乏味、笨拙且容易出错。
是否有一个特殊的编译器标志可以自动检测这些长时间运行并生成有效的指令?我注定要使用内在函数来进一步优化这段代码,还是有一个聪明的技巧可以自动为我做这个记账?
reorder.cc 大约有 100,000 条这些输入、输出对的指令,这是我正在处理的较小的测试用例。
另外,关于编译这些移动指令的 100K+ 或更多行的大型源文件有什么技巧吗? g++-6 -Ofast 在像这样的 1M 行文件上需要数小时才能用于 Macbook Pro i7 处理器。
【问题讨论】:
-
嗯,使用 memcpy 可能是最理想的方法
-
我用 memcpy 替换了一次运行,但它生成了 movq(64 位移动而不是 movss 的 32 位)。在深入重写之前,我仍在寻找是否有自动解决方案。
-
如果您的算法确切地知道应该如何复制数据,您可以将长赋值拆分为几个单独的
memcpy和memset。 -
幸运的是,0 很容易处理。初始化时整个输出缓冲区只需 calloc() 或 memset 为 0。 memcpy 仍然生成 64 位移动。如果可能,寻找 128 或 256 位移动而不诉诸内在函数。
-
如果这个缓冲区很大,那么可以使用
calloc来利用来自操作系统的新页面无论如何都开始归零的事实(以避免信息泄漏)。重用缓冲区时,不要在写入部分缓冲区之前memset整个缓冲区;这可能会比使用零和新加载的数据(特别是如果它大于 L1 缓存)按顺序编写它的效率低。
标签: c++ memory assembly compiler-optimization