多次检查条件与设置变量;低级优化
Checking a conditional vs setting a variable multiple times; low-level optimization
如果我正在搜索一组值和每个值的 运行ning 代码,并且我想在找到特定质量时打开一个布尔值,然后在我找到某个质量时再次关闭运行 该对象的代码,运行 检查布尔值是否需要关闭的条件是否更快,还是在每个循环中简单地关闭它更快?
例如(伪代码):
bool found = false;
for(particle in literallyAHaystack) {
bool isNeedle = particle == "needle";
if(isNeedle) {
found = true;
}
// [some code that uses the 'found' variable]
if(isNeedle) {
found = false;
}
}
对
bool found = false;
for(particle in literallyAHaystack) {
bool isNeedle = particle == "needle";
if(isNeedle) {
found = true;
}
// [some code that uses the 'found' variable]
found = false; // a conditional no longer surrounds this statement
}
我知道这是非常低级且通常毫无意义的优化,但我仍然对它的真相感兴趣。我希望不要因为这个问题的琐碎而冒犯任何人。
第二个将是 "faster",因为没有条件检查,因此不需要跳转。
速度会明显加快吗?几乎肯定不是。无论如何,编译器 可能 已经进行了此优化,但请不要引用我的话——我不确定是否这样做。
如果 bool found
是一个局部变量,在几乎所有 CPU 架构上,无条件地将其设置为 false
几乎肯定在每种情况下都更好。如果它完全存在于编译器输出中(而不仅仅是变成分支逻辑的一部分),它可能只会出现在寄存器中。写入寄存器是最便宜的操作之一,比分支要便宜得多。
即使它曾经命中内存,回写缓存也很常见,因此重复存储到同一位置只会命中 L1,而不会产生到更大的共享缓存或主内存的流量。
如果编译器发出的代码在每个循环结束时实际将标志存储在内存中,则下一次迭代检查标志将导致大约 5 个周期的存储转发延迟(例如在 Intel Haswell 上)。
但如果出现问题,那是编译器没有很好地优化你的代码的错。这就是为什么我们使用编译器而不是直接用 asm 编写的原因:编译器可能会完全优化掉 found
变量。很好,这不是重构 C 的理由。
有关 x86 上此类内容的更多信息,请参阅 http://agner.org/optimize/ and other links in the x86 标记 wiki。
要查看您的代码如何编译,请将其放在 http://gcc.godbolt.org/ 上(并使用 O3 -march=haswell -ffast-math
或其他内容。)
如果 found
是全局的(并且可能最后被另一个线程修改过),那么只先读取它可能是有意义的,所以核心 运行 你的代码不需要使其他核心的缓存行副本无效。
我正在想象一个标志,它是不同线程可能使用的共享状态结构的一部分(由受锁保护的关键部分中的代码使用)。 (不过,在这种情况下,这将是一个糟糕的设计,因为 found
在使用后总是留下 false
,所以没有持久状态,所以它应该是本地的。)
不过,可能不值得使用条件分支来避免该存储。如果标志在任何循环迭代中都被修改(即如果针曾经匹配),那么您也可以随意修改它。
只有当您可以从一些商店到同一位置到零,而不仅仅是更少时,避免商店才有用。缓存工作。
例如向量化时,如果您想写入一个向量的 3 个元素而不是所有 4 个元素,那么将重叠存储到目的地有时是有用的,并且写入超出存储内容的末尾是可以的。例如在 .
如果我正在搜索一组值和每个值的 运行ning 代码,并且我想在找到特定质量时打开一个布尔值,然后在我找到某个质量时再次关闭运行 该对象的代码,运行 检查布尔值是否需要关闭的条件是否更快,还是在每个循环中简单地关闭它更快?
例如(伪代码):
bool found = false;
for(particle in literallyAHaystack) {
bool isNeedle = particle == "needle";
if(isNeedle) {
found = true;
}
// [some code that uses the 'found' variable]
if(isNeedle) {
found = false;
}
}
对
bool found = false;
for(particle in literallyAHaystack) {
bool isNeedle = particle == "needle";
if(isNeedle) {
found = true;
}
// [some code that uses the 'found' variable]
found = false; // a conditional no longer surrounds this statement
}
我知道这是非常低级且通常毫无意义的优化,但我仍然对它的真相感兴趣。我希望不要因为这个问题的琐碎而冒犯任何人。
第二个将是 "faster",因为没有条件检查,因此不需要跳转。
速度会明显加快吗?几乎肯定不是。无论如何,编译器 可能 已经进行了此优化,但请不要引用我的话——我不确定是否这样做。
如果 bool found
是一个局部变量,在几乎所有 CPU 架构上,无条件地将其设置为 false
几乎肯定在每种情况下都更好。如果它完全存在于编译器输出中(而不仅仅是变成分支逻辑的一部分),它可能只会出现在寄存器中。写入寄存器是最便宜的操作之一,比分支要便宜得多。
即使它曾经命中内存,回写缓存也很常见,因此重复存储到同一位置只会命中 L1,而不会产生到更大的共享缓存或主内存的流量。
如果编译器发出的代码在每个循环结束时实际将标志存储在内存中,则下一次迭代检查标志将导致大约 5 个周期的存储转发延迟(例如在 Intel Haswell 上)。
但如果出现问题,那是编译器没有很好地优化你的代码的错。这就是为什么我们使用编译器而不是直接用 asm 编写的原因:编译器可能会完全优化掉 found
变量。很好,这不是重构 C 的理由。
有关 x86 上此类内容的更多信息,请参阅 http://agner.org/optimize/ and other links in the x86 标记 wiki。
要查看您的代码如何编译,请将其放在 http://gcc.godbolt.org/ 上(并使用 O3 -march=haswell -ffast-math
或其他内容。)
如果 found
是全局的(并且可能最后被另一个线程修改过),那么只先读取它可能是有意义的,所以核心 运行 你的代码不需要使其他核心的缓存行副本无效。
我正在想象一个标志,它是不同线程可能使用的共享状态结构的一部分(由受锁保护的关键部分中的代码使用)。 (不过,在这种情况下,这将是一个糟糕的设计,因为 found
在使用后总是留下 false
,所以没有持久状态,所以它应该是本地的。)
不过,可能不值得使用条件分支来避免该存储。如果标志在任何循环迭代中都被修改(即如果针曾经匹配),那么您也可以随意修改它。
只有当您可以从一些商店到同一位置到零,而不仅仅是更少时,避免商店才有用。缓存工作。
例如向量化时,如果您想写入一个向量的 3 个元素而不是所有 4 个元素,那么将重叠存储到目的地有时是有用的,并且写入超出存储内容的末尾是可以的。例如在