C/C++ 最快的 cmath 日志操作
Posted
技术标签:
【中文标题】C/C++ 最快的 cmath 日志操作【英文标题】:C/C++ fastest cmath log operation 【发布时间】:2011-10-03 02:34:54 【问题描述】:我正在尝试计算 logab(并得到一个浮点数,而不是整数)。我打算以log(b)/log(a)
的身份执行此操作。从数学上讲,我可以使用任何cmath
对数函数(以 2、e 或 10 为底)进行此计算;但是,我会在我的程序中经常运行这个计算,所以我想知道其中一个是否比其他的快得多(或者更好的是,如果有更快但仍然简单的方法来做到这一点)。如果重要的话,a 和 b 都是整数。
【问题讨论】:
用 Donald Knuth 的话来说:“我们应该忘记小的效率,比如说大约 97% 的时间:过早的优化是万恶之源” @You “无意识的咒语是富有成效的思想的诅咒” @You - 我一直觉得报价被过度使用了。当然,在某些情况下,您可能会花费大量精力,降低代码的可读性,最终却没有注意到差异。还有很多其他情况,您可以花费很少的精力,完全不影响可读性,并做出巨大的改进。除非您完全停止考虑潜在客户,否则要知道哪个案例是哪个案例。 @You:乘法、加法和减法都比 log、exp 和 trig 快 很多。 Sqrt和divide介于两者之间。 (英特尔 Skylake 有一个 非常 快速的 FP 除法单元,但它仍然是 a factor of 8 worse throughput, and a factor of ~3 worse latency than FP mul。sqrt 只是稍微慢一点)。检查几何平均值为(x^2+y^2) < maxdistance^2
而不是sqrt(x^2+y^2) < maxdistance
要快得多,尤其是。如果您重复执行此检查(例如在 Mandelbrot 内循环中),或者使用整数。 (x86 标量整数除法比 SIMD FP 除法慢。)
@You 那是部分且确实是选择性的引用。我建议您阅读其余部分,尤其是最后一句。全文如下: '程序员浪费大量时间去思考或担心他们程序中非关键部分的速度,而在考虑调试和维护时,这些效率上的尝试实际上会产生强烈的负面影响.我们应该忘记小的效率,比如大约 97% 的时间:过早优化是万恶之源。然而,我们不应该放弃那关键的 3% 的机会。'
【参考方案1】:
首先,预先计算 1.0/log(a)
并将每个 log(b)
乘以该表达式。
编辑:我最初说自然对数(以 e 为底)是最快的,但其他人说处理器直接支持以 2 为底的并且最快。我没有理由怀疑。
编辑 2: 我最初认为 a
是一个常数,但在重新阅读从未陈述过的问题时。如果是这样,那么预先计算将没有任何好处。但是,如果是这样,您可以通过选择适当的变量名来保持可读性:
const double base_a = 1.0 / log(a);
for (int b = 0; b < bazillions; ++b)
double result = log(b) * base_a;
奇怪的是,Microsoft 不提供 base 2 日志功能,这解释了为什么我不熟悉它。 x86 instruction for calculating logs 还包括自动乘法,不同基数所需的常数也可通过optimized instruction 获得,因此我希望 3 个不同的日志函数具有相同的时序(即使基数 2 也必须乘以1).
【讨论】:
+1 不错的观察。虽然另一个答案是有道理的,因为无论如何这些操作都非常快,优化它有点多。 这是真的,但新编译器的优化几乎消除了做这些事情的需要,这有点令人难过。尽管大多数人仍然会写出这样的优化。 我很确定以 2 为底的对数是最快的,因为它唯一拥有自己的汇编指令(至少在 8087 指令集中)。 @Mark:完全正确。除非设置了像fast-math
这样的标志,否则编译器不能用倒数乘法代替除法(不仅因为它会改变舍入,还因为它可能导致虚假溢出和 NaN)。是的,在大多数当前处理器上,除法比乘法慢得多。
现代编译器不太可能使用日志指令,因为它们不准确(相对于您可以在软件中执行的操作)并且仅在已弃用的 x87 指令集中可用。【参考方案2】:
答案是:
视情况而定 描述它您甚至没有提及您的 CPU 类型、变量类型、编译器标志、数据布局。如果你需要同时做很多这些,我相信会有一个 SIMD 选项。你的编译器将优化它,只要你使用对齐并清除简单的循环(如果你喜欢古老的方法,也可以使用 valarray)。
很有可能,英特尔编译器在这方面有针对英特尔处理器的特定技巧。
如果您真的想要,您可以使用 CUDA 并利用 GPU。
我想,如果你不幸缺少这些指令集you could go down at the bit fiddling level 并编写一个does a nice approximation 的算法。在这种情况下,我可以打赌不止一个苹果派,2-log 会比任何其他 base-log 都快
【讨论】:
【参考方案3】:在我有数据的平台上,log2
比其他平台快得多,符合我的预期。但请注意,差异非常很小(只有百分之几)。这真的不值得担心。
写一个清晰的实现。然后测量性能。
【讨论】:
【参考方案4】:在8087指令集中,只有一个以2为底的对数指令,所以我猜这个是最快的。
当然这类问题很大程度上取决于你的处理器/架构,所以我建议做一个简单的测试并计时。
【讨论】:
在现代 x86 处理器上,事实证明良好的软件日志实现比硬件日志指令快,因此该指令中使用的基础对问题没有任何影响。 @Stephen Canon,如果你有一些计时结果的链接,这将是一个展示它的好地方。 @Mark Ransom,英特尔优化手册将fyl2x
列为每 85 个周期有 1 个结果的吞吐量,以及 140 到 190 个周期之间的延迟。相比之下,在我的 MacBook Pro 上使用 OSX 上的系统数学库,我测量 log2( )
具有约 72 个周期的延迟和每 46 个周期 1 个结果的吞吐量。所以这里的软件实现速度大约是原来的两倍。【参考方案5】:
由于b
和a
是整数,所以您可以使用bit twiddling 的所有荣耀来找到它们的日志到基数2。这里有一些:
我会留给您选择最适合您需求的“快速日志”功能。
【讨论】:
很酷的链接。我想知道这些方法是否比现代处理器上的直接方法更快? @MarkRansom:它们都比使用硬件指令计算前导零要慢得多,which all modern architectures have,因为循环方法会使用不同的输入进行分支错误预测。 hw insn 很便宜。 x86 的原始bsr
很笨重(如果输入为 0,则未定义),但确实直接给出了 log2 结果,而不是 32-log2(a)。只有最近的 CPU 支持 lzcnt
,它是为 0
定义的。 AVX512CD 将引入VPLZCNTD
,它对整数向量中的每个元素执行此操作。
另外,我认为 OP 正在寻找一种不会将中间结果四舍五入/截断为整数的解决方案。当然起始值是整数,但integer_log2(uint32_t)
的范围只有 0..32,所以小数部分会产生很大 的差异。 2^30 到 2^31 之间的数字范围很大,但它们都有相同的 ilog2。以上是关于C/C++ 最快的 cmath 日志操作的主要内容,如果未能解决你的问题,请参考以下文章