我有大量的C结构实例,如下所示:
struct mystruct { /* ... */ unsigned flag: 1; /* ... */ };
flag
最初为0但在退出某个函数时必须为1.
最简单的实现是:
void set_flag(struct mystruct *sp) { sp->flag = 1U; }
但是这样做对性能的影响可能是什么:
void set_flag(struct mystruct *sp) { if (sp->flag == 0U) { sp->flag = 1U; } }
我希望避免写入主内存.第一个版本总是执行写操作,第二个版本只执行写操作(如果尚未设置标志),但在绝大多数情况下,标志将已设置.
还有哪些其他因素(例如分支预测)可能会影响性能?
到目前为止,我看到了一个小的速度增加,我希望随着数据集变大,这将变得更加重要.
是否有这种变化的风险使得大型数据集的程序变慢,如果是这样,在什么情况下会发生这种情况?
设置之前的测试确实有所不同,但它取决于您的使用情况.
在任何一种情况下,数据都将以高速缓存行结束(例如,只是写入或测试和设置).
但是,如果您的缓存行被标记为脏(例如已修改)或清除,则会有所不同.必须将脏缓存行写回主内存,而干净的缓存行只能被遗忘并填充新数据.
现在考虑您的代码会破坏大量数据,并且您只能访问每个数据块一次或两次.如果是这样,可以假设大多数内存访问都是缓存未命中.如果大多数缓存行在发生缓存未命中且大多数缓存行都是脏的时候是脏的,会发生什么?
在将新数据加载到生产线之前,必须将它们写回主存储器.这比忘记缓存行的内容要慢.它还会使高速缓存和主存储器之间的内存带宽加倍.
这对于曾经的CPU核心来说可能没有什么区别,因为这些天内存很快,但另一个CPU(希望)也会做其他工作.如果总线没有忙于移入和移出缓存线,您可以确定其他CPU内核将执行更快的速度.
简而言之:保持缓存线清洁将使带宽需求减半,并使缓存失误更便宜.
关于分支:当然:这是昂贵的,但缓存失误更糟糕!此外,如果您很幸运,CPU将使用它的乱序执行功能来抵消缓存未命中以及分支的成本.
如果你真的希望从这段代码中获得最佳性能,并且如果你的大多数访问都是缓存未命中,你有两个选择:
绕过缓存:x86体系结构具有非临时加载和存储用于此目的.它们隐藏在SSE指令集的某个地方,可以通过内在函数从c语言中使用.
(仅适用于专家):使用一些内联汇编程序行代替使用CMOV(条件移动)指令的汇编程序替换测试和设置函数.这不仅可以保持缓存行清洁,还可以避免分支.现在CMOV是一个慢速指令,如果无法预测分支,它只会胜过分支.因此,您将更好地对代码进行基准测试.