在测试.NET中浮子的性能时,我偶然发现了一个奇怪的情况:对于某些值,乘法似乎比正常情况慢。这是测试案例:
using System;
using System.Diagnostics;
namespace NumericPerfTestCSharp {
class Program {
static void Main() {
Benchmark(() => float32Multiply(0.1f), "nfloat32Multiply(0.1f)");
Benchmark(() => float32Multiply(0.9f), "nfloat32Multiply(0.9f)");
Benchmark(() => float32Multiply(0.99f), "nfloat32Multiply(0.99f)");
Benchmark(() => float32Multiply(0.999f), "nfloat32Multiply(0.999f)");
Benchmark(() => float32Multiply(1f), "nfloat32Multiply(1f)");
}
static void float32Multiply(float param) {
float n = 1000f;
for (int i = 0; i < 1000000; ++i) {
n = n * param;
}
// Write result to prevent the compiler from optimizing the entire method away
Console.Write(n);
}
static void Benchmark(Action func, string message) {
// warm-up call
func();
var sw = Stopwatch.StartNew();
for (int i = 0; i < 5; ++i) {
func();
}
Console.WriteLine(message + " : {0} ms", sw.ElapsedMilliseconds);
}
}
}
结果:
float32Multiply(0.1f) : 7 ms
float32Multiply(0.9f) : 946 ms
float32Multiply(0.99f) : 8 ms
float32Multiply(0.999f) : 7 ms
float32Multiply(1f) : 7 ms
为什么param = 0.9f的结果如此不同?
测试参数:.NET 4.5,发布构建,X86上的代码优化,没有附加的调试器。
正如其他人所提到的,当涉及亚正态浮点值时,各种处理器不支持正常速度计算。这是设计缺陷(如果行为会损害您的应用程序,或者是麻烦的话)或功能(如果您喜欢较便宜的处理器或替代使用硅的替代用途,而硅是通过不使用大门来进行此工作的)。
>了解为什么在.5:
的过渡很有启发性假设您乘以 p 。最终,该值变得很小,以至于结果是一定的亚正态值(低于2 -126 在32位IEEE二进制浮点数中)。然后乘法变得慢。当您继续乘以时,值继续减少,并且达到2 -149 ,这是可以表示的最小正数。现在,当您乘以 p 时,确切的结果当然是2 -149 p ,它在0和2 - 149 ,是两个最近的代表值。机器必须围绕结果并返回这两个值之一。
哪一个?如果 p 小于½,则2 -149 p 比0比2 -149 ,因此机器返回0。然后,您不再使用下正态值,并且乘法再次快速。如果 p 大于½,则2 -149 p 更接近2 -149 ,而不是0,因此,机器返回2 -149 ,然后继续使用下正态值,并且乘法保持缓慢。如果 p 恰好是½,则舍入规则说使用其在其显着的低位(分数部分)中为零的值(2 -149 )
有1个。您报告.99f出现快速。这应该以缓慢的行为结束。也许您发布的代码并不是您用.99F快速衡量的代码?也许起始值或迭代次数已更改?
有一些方法可以解决此问题。一个是,硬件具有指定的模式设置,可以将使用或获得的任何亚正态值更改为零,称为" denormals为零"或"齐平至零"模式。我不使用.NET,也不能建议您如何在.NET中设置这些模式。
另一种方法是每次添加一个微小的值,例如
n = (n+e) * param;
其中 e
至少为2 -126 / param
。请注意,应计算2 -126 /param
,除非您可以保证n
足够大以至于(n+e) * param
不会产生下正态值,否则应计算出。这也假定n
并不负。这样做的效果是确保计算出的值始终足够大,以至于正常范围内。
以这种方式添加e
当然会更改结果。但是,例如,如果您使用一些回声效果(或其他过滤器)处理音频,那么e
的值太小,无法引起人类听到音频的任何效果。它可能太小,无法在产生音频时引起硬件行为的任何变化。
我怀疑这与变性值(fp值小于〜1e-38)以及与处理它们相关的成本有关。
如果您测试了否定值并将其删除,则可以恢复理智。
static void float32Multiply(float param) {
float n = 1000f;
int zeroCount=0;
for (int i = 0; i < 1000000; ++i) {
n = n * param;
if(n<1e-38)n=0;
}
// Write result to prevent the compiler from optimizing the entire method away
Console.Write(n);
}