为什么我的代码会导致指令缓存未命中
Why does my code cause instruction-cache misses?
根据cachegrind,此校验和计算例程是整个应用程序中指令缓存加载和指令缓存未命中的最大贡献者之一:
#include <stdint.h>
namespace {
uint32_t OnesComplementSum(const uint16_t * b16, int len) {
uint32_t sum = 0;
uint32_t a = 0;
uint32_t b = 0;
uint32_t c = 0;
uint32_t d = 0;
// helper for the loop unrolling
auto run8 = [&] {
a += b16[0];
b += b16[1];
c += b16[2];
d += b16[3];
b16 += 4;
};
for (;;) {
if (len > 32) {
run8();
run8();
run8();
run8();
len -= 32;
continue;
}
if (len > 8) {
run8();
len -= 8;
continue;
}
break;
}
sum += (a + b) + (c + d);
auto reduce = [&]() {
sum = (sum & 0xFFFF) + (sum >> 16);
if (sum > 0xFFFF) sum -= 0xFFFF;
};
reduce();
while ((len -= 2) >= 0) sum += *b16++;
if (len == -1) sum += *(const uint8_t *)b16; // add the last byte
reduce();
return sum;
}
} // anonymous namespace
uint32_t get(const uint16_t* data, int length)
{
return OnesComplementSum(data, length);
}
请参阅此处的asm输出。
也许它是由循环展开引起的,但生成的对象代码似乎并不太多。
如何改进代码?
更新
- 因为校验和函数位于匿名命名空间中,所以它被位于同一cpp文件中的两个函数内联并重复
- 循环展开仍然是有益的。删除它会减慢代码的速度
改进无限循环加快了代码的速度(但由于某种原因,我在mac上得到了相反的结果)。
- 修复之前:在这里你可以看到两个校验和和17210个L1 IR未命中
- 修复后:修复内联问题和无限循环后,L1指令缓存未命中率降至8324
- "InstructionFetch"在固定示例中较高。我不知道该怎么解释。这只是意味着大多数活动都发生在那里吗?还是暗示了一个问题
将主循环替换为:
const int quick_len=len/8;
const uint16_t * const the_end=b16+quick_len*4;
len -= quick_len*8;
for (; b16+4 <= the_end; b16+=4)
{
a += b16[0];
b += b16[1];
c += b16[2];
d += b16[3];
}
如果你使用-O3,似乎没有必要手动循环展开
此外,测试用例允许过多的优化,因为输入是静态的,结果是未使用的,打印出结果也有助于验证优化版本不会破坏任何
我使用的完整测试:
int main(int argc, char *argv[])
{
using namespace std::chrono;
auto start_time = steady_clock::now();
int ret=OnesComplementSum((const uint8_t*)(s.data()+argc), s.size()-argc, 0);
auto elapsed_ns = duration_cast<nanoseconds>(steady_clock::now() - start_time).count();
std::cout << "loop=" << loop << " elapsed_ns=" << elapsed_ns << " = " << ret<< std::endl;
return ret;
}
与theis(CLEAN LOOP
)和您的改进版本(UGLY LOOP
)以及更长的测试字符串的比较
loop=CLEAN_LOOP elapsed_ns=8365 = 14031
loop=CLEAN_LOOP elapsed_ns=5793 = 14031
loop=CLEAN_LOOP elapsed_ns=5623 = 14031
loop=CLEAN_LOOP elapsed_ns=5585 = 14031
loop=UGLY_LOOP elapsed_ns=9365 = 14031
loop=UGLY_LOOP elapsed_ns=8957 = 14031
loop=UGLY_LOOP elapsed_ns=8877 = 14031
loop=UGLY_LOOP elapsed_ns=8873 = 14031
此处验证:http://coliru.stacked-crooked.com/a/52d670039de17943
编辑:
事实上,整个功能可以简化为:
uint32_t OnesComplementSum(const uint8_t* inData, int len, uint32_t sum)
{
const uint16_t * b16 = reinterpret_cast<const uint16_t *>(inData);
const uint16_t * const the_end=b16+len/2;
for (; b16 < the_end; ++b16)
{
sum += *b16;
}
sum = (sum & uint16_t(-1)) + (sum >> 16);
return (sum > uint16_t(-1)) ? sum - uint16_t(-1) : sum;
}
哪个比-O3的OP更好,但与-O2:相比更差
http://coliru.stacked-crooked.com/a/bcca1e94c2f394c7
loop=CLEAN_LOOP elapsed_ns=5825 = 14031
loop=CLEAN_LOOP elapsed_ns=5717 = 14031
loop=CLEAN_LOOP elapsed_ns=5681 = 14031
loop=CLEAN_LOOP elapsed_ns=5646 = 14031
loop=UGLY_LOOP elapsed_ns=9201 = 14031
loop=UGLY_LOOP elapsed_ns=8826 = 14031
loop=UGLY_LOOP elapsed_ns=8859 = 14031
loop=UGLY_LOOP elapsed_ns=9582 = 14031
所以里程数可能会有所不同,除非知道确切的体系结构,否则我只会选择更简单的
相关文章:
- 我的神经网络不起作用 [XOR 问题]
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 我的字符计数代码计算错误.为什么
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- cmake在我的项目中所需的所有静态库都不成功
- 为什么我的代码在输出中增加了93天
- 我的简单if-else语句是如何无法访问的代码
- 使用宏扩展的泛型:为什么指令缓存使用不当?
- 为什么我的拆卸C 代码使用指令指针和偏移来获取字符串文字
- 此英特尔至强 mov 指令如何损坏我的应用内存?
- 为什么我的代码会导致指令缓存未命中
- 我怎么知道我的数组在缓存中
- 是什么导致我的代码中的缓存未命中
- C++:int数组[a][b][C]={0};未将所有值设置为0.是那个指令错了,还是我的输出函数出了问题
- 我可以在C++中控制复制到CPU缓存中的内容吗
- 编译器是如何知道我的CPU的指令集的
- 为什么我的系统没有释放缓冲区/缓存
- 为什么添加一条从未执行过的指令会导致我的代码性能下降
- 如何检查我的 Linux Box 安装安装是否具有 SSE 指令功能
- 为什么我的8M L3缓存不能为大于1M的数组提供任何好处?