我在for
循环中调用了数百万次代码,检查传递的参数是否为double.NaN
。我描述了我的应用程序,其中一个瓶颈就是这个简单的功能:
public void DoSomething(double[] args)
{
for(int i = 0; i < args.Length;i++)
{
if(double.IsNan(args[i]))
{
//Do something
}
}
}
即使我无法更改if
答案 0 :(得分:18)
如果你真的优化了代码的其他部分,你可以让这个函数变得有点神秘,利用非数字(NaN)的定义:
“谓词x!= y是真的但是全部 其他人,x&lt; y,x&lt; = y,x == y,x&gt; = y和x&gt; y,只要x或者为假 是或两者都是NaN。“(IEEE标准754 for Binary Floating-Point Arithmetic)
将其翻译成您的代码:
public void DoSomething(double[] args)
{
for(int i = 0; i < args.Length;i++)
{
double value = args[i];
if(value != value)
{
//Do something
}
}
}
在使用WindoWs CE + .NET Compact Framework 3.5的ARM设备中,获得Nan的概率约为50%,value!= value的速度是double.IsNan(value)的两倍。
确保衡量您的应用程序执行后!
答案 1 :(得分:4)
我觉得很难(但并非不可能)相信args[i]
上的任何其他检查都会比double.IsNan()
更快。
一种可能性是这是一个功能。调用函数有一些开销,有时很大,特别是如果函数本身相对较小的话。
您可以利用以下事实:IEEE754 NaN的位模式是众所周知的,只需进行一些位检查(没有调用函数来执行此操作) - 这将消除该开销。在C中,我会尝试使用宏。如果指数位都是1且尾数位都不是0,那就是NaN(信号或静默由符号位决定,但你可能并不关心它)。此外,NaNs永远不会彼此相等,因此您可以测试args[i]
与其自身之间的相等性 - false表示它是NaN。
如果阵列的使用频率高于其更改,则另一种可能性是可行的。保持另一个布尔数组,指示相关的双数是否为NaN。然后,每当其中一个双精度发生变化时,计算相关的布尔值。
然后你的功能变为:
public void DoSomething(double[] args, boolean[] nan) {
for(int i = 0; i < args.Length; i++) {
if (nan[i]) {
//Do something
}
}
}
这与数据库中使用的“技巧”相同,只有在数据发生变化时才会预先计算值,而不是每次读出数据时。如果你的数据被使用的情况比被改变的要多得多,那么这是一个很好的优化(大多数算法可以在时间上交换空间)。
但请记住优化口头禅:测量,不要猜测!
答案 2 :(得分:1)
为了进一步重申性能测试的重要性,我在使用VS 2010目标.NET 4.0编译的Windows 7上以64位本机和32位模式在我的Core i5-750上运行了以下测试,并获得了以下结果:
public static bool DoSomething(double[] args) {
bool ret = false;
for (int i = 0; i < args.Length; i++) {
if (double.IsNaN(args[i])) {
ret = !ret;
}
}
return ret;
}
public static bool DoSomething2(double[] args) {
bool ret = false;
for (int i = 0; i < args.Length; i++) {
if (args[i] != args[i]) {
ret = !ret;
}
}
return ret;
}
public static IEnumerable<R> Generate<R>(Func<R> func, int num) {
for (int i = 0; i < num; i++) {
yield return func();
}
}
static void Main(string[] args) {
Random r = new Random();
double[] data = Generate(() => {
var res = r.NextDouble();
return res < 0.5 ? res : Double.NaN;
}, 1000000).ToArray();
Stopwatch sw = new Stopwatch();
sw.Start();
DoSomething(data);
Console.WriteLine(sw.ElapsedTicks);
sw.Reset();
sw.Start();
DoSomething2(data);
Console.WriteLine(sw.ElapsedTicks);
Console.ReadKey();
}
在x86模式下(自然发布):
DoSomething() = 139544
DoSomething2() = 137924
在x64模式下:
DoSomething() = 19417
DoSomething2() = 17448
然而,如果NaN
的分布较为稀疏,会发生一些有趣的事情。如果我们将0.5
常量更改为0.9
(只有10%NaN),我们会得到:
86:
DoSomething() = 31483
DoSomething2() = 31731
64:
DoSomething() = 31432
DoSomething2() = 31513
重新排序电话也显示出相同的趋势。值得深思。