我有大量像这样的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;
}
}
我希望避免写入主内存。第一个版本总是执行写操作,第二个版本只执行写操作(如果尚未设置标志),但在绝大多数情况下,标志已经设置。
其他哪些因素(例如分支预测)可能会影响性能?
到目前为止,我看到了一个小的速度提升,我希望随着数据集变大,这将变得更加重要。
是否存在此更改的风险,使大型数据集的程序变慢,如果是这样,在什么情况下会发生这种情况?
答案 0 :(得分:10)
设置之前的测试确实有所不同,但它取决于你的使用情况。
在任何一种情况下,数据都会以高速缓存行结束(例如,只是写入或测试和设置)。
但是,如果您的缓存行被标记为脏(例如已修改)或清除,则会有所不同。必须将脏缓存行写回主内存,而干净的缓存行只能被遗忘并填充新数据。
现在考虑您的代码会破坏大量数据,并且您只访问每个数据块一次或两次。如果是这样,可以假设大多数内存访问都是缓存未命中。如果大多数缓存行在发生缓存未命中并且大多数缓存行都是脏的时候是脏的,会发生什么?
在将新数据加载到线路之前,必须将它们写回主存储器。这比忘记缓存行的内容要慢。它还会使缓存和主存储器之间的内存带宽加倍。
这可能不会对CPU核心产生影响,因为这些天内存很快,但另一个CPU(希望)也会做其他工作。如果buss没有忙于移动和移出缓存行,你可以确定其他CPU核心会更快地执行所有内容。
简而言之:保持缓存线清洁将是带宽需求的一半,并使缓存失误更便宜。
关于分支:当然:这是昂贵的,但缓存失误更糟糕!此外,如果您很幸运,CPU将使用它的乱序执行功能来抵消缓存未命中以及分支的成本。
如果你真的希望从这段代码中获得最佳性能,并且如果你的大多数访问都是缓存未命中,你有两个选择:
绕过缓存:x86体系结构具有非临时加载和存储功能。它们隐藏在SSE指令集的某个地方,可以通过内在函数从c语言中使用。
(仅限专家):使用一些内联汇编程序行代替使用CMOV(条件移动)指令的汇编程序替换测试和设置函数。这不仅可以保持缓存行清洁,还可以避免分支。现在CMOV是一个慢速指令,如果无法预测分支,它只会胜过分支。因此,您将更好地对代码进行基准测试。
答案 1 :(得分:3)
这是一个有趣的问题,Nils关于缓存行的答案肯定是很好的建议。
我想强调分析代码来衡量实际性能的重要性 - 您能否衡量在您遇到的数据中已经设置该标志的频率?根据答案,性能可能会发生很大变化。
只是为了好玩,我使用你的代码在5000万元素阵列上运行一套set-then-set的比较,这个阵列填充了不同比例的1。这是一张图表:
(来源:natekohl.net)
当然,这只是一个玩具的例子。但请注意非线性性能 - 我没想到 - 当数组几乎完全填充1时,测试然后设置变得比普通设置更快。
答案 2 :(得分:2)
这些是我对你的要求的解释,
假设,
我建议以下事项。
答案 3 :(得分:1)
当移动到更大的数据集时,此优化可能不会导致速度降低。
当读取值时,缓存抖动将是相同的,分支预测惩罚也将是相同的,这些是在此优化的关键因素。
分支预测存储每个分支指令的历史记录,因此只要您使用不同地址的指令(例如内联函数)对它们进行分支,它就不关心您拥有多少个实例。如果你有一个单独的函数实体(没有内联),你将拥有一个分支指令,这将抑制分支预测,使其更频繁地错过并增加惩罚。
答案 4 :(得分:0)
您可以随时进行分析,但我非常确定第一个版本更快且更不明显。
答案 5 :(得分:0)
这两种方法都要求将数据加载到缓存中,因此您的唯一保存将是读/写和写入之间的差异。
我没有看到这种变化如何通过更大的数据集使你的代码变慢,所以你可能在这方面足够安全。
它闻起来有点像对我的过早优化。 (除非您的分析已将此视为瓶颈)
与性能相关的所有事情一样,确保代码更改效果的最佳方法是测量它。您应该能够相对容易地创建大量的测试数据。
答案 6 :(得分:0)
如果您真的担心时间性能,请将标志更改为完整的int而不是位域。然后将其设置为写入而不是像位域那样的读写。
但正如已经指出的那样,这种微观优化的气味。
答案 7 :(得分:0)
设置前的测试没有任何意义 - 没有测试的代码更清晰,也更快一些。
作为旁注 - 内联函数是有意义的,因为函数调用的开销比函数体大,尽管优化编译器应该不加思索地进行。
答案 8 :(得分:0)
由于没有人说过,我会。
你为什么要使用位字段?布局将因编译器而异,因此它们对接口无用。它们可能会或可能不会更节省空间;编译器可能只是决定将它们推入32位字段,以便有效地填充事物。不能保证它们更快,事实上它们可能会更慢。
我已禁止他们在工作中使用。除非有人能给我一个令人信服的理由,他们提供任何额外的能力,否则不值得与他们一起玩。