views:

102

answers:

3

I´ve a line of code that is called millions of times inside a for loop, checking if a passed argument is double.NaN. I´ve profiled my application and one of the bottlenecks is this simple function:

public void DoSomething(double[] args)
{
    for(int i = 0; i < args.Length;i++)
    {
       if(double.IsNan(args[i]))
       {
         //Do something
       }
    }
}

Can I optimize it even if I can´t change the code inside the if?

+12  A: 

If you have really optimized other parts of your code, you can let this function become a little bit cryptic an utilize the definition of Not a Number (NaN):

"The predicate x != y is True but all others, x < y , x <= y , x == y , x >= y and x > y , are False whenever x or y or both are NaN.” (IEEE Standard 754 for Binary Floating-Point Arithmetic)

Translating that to your code you would get:

public void DoSomething(double[] args)
{
    for(int i = 0; i < args.Length;i++)
    {
       double value = args[i];  
       if(value != value)
       {
         //Do something
       }
    }
}

In an ARM device using WindoWs CE + .NET Compact Framework 3.5 with around 50% probability of getting a Nan, value != value is twice as fast as double.IsNan(value).

Just be sure to measure your application execution after!

Hbas
+3  A: 

I find it hard (but not impossible) to believe that any other check on args[i] would be faster than double.IsNan().

One possibility is if this is a function. There is an overhead with calling functions, sometimes substantial, especially if the function itself is relatively small.

You could take advantage of the fact that the bit patterns for IEEE754 NaNs are well known and just do some bit checks (without calling a function to do it) - this would remove that overhead. In C, I'd try that with a macro. Where the exponent bits are all 1 and the mantissa bits are not all 0, that's a NaN (signalling or quiet is decided by the sign bit but you're probably not concerned with that). In addition, NaNs are never equal to one another so you could test for equality between args[i] and itself - false means it's a NaN.

Another possibility may be workable if the array is used more often than it's changed. Maintain another array of booleans which indicate whether or not the associated double is a NaN. Then, whenever one of the doubles changes, compute the associated boolean.

Then your function becomes:

public void DoSomething(double[] args, boolean[] nan) {
    for(int i = 0; i < args.Length; i++) {
       if (nan[i]) {
         //Do something
       }
    }
}

This is the same sort of "trick" used in databases where you pre-compute values only when the data changes rather than every time you read it out. If you're in a situation where the data is being used a lot more than being changed, it's a good optimisation to look into (most algorithms can trade off space for time).

But remember the optimisation mantra: Measure, don't guess!

paxdiablo
double.isNan is really a small function... So, as you pointed out, there is an overhead calling it.
Hbas
In Common Intermediate Language (CIL) it has 7 lines of code:L_0000: ldarg.0L_0001: ldarg.0L_0002: beq.s L_0006L_0004: ldc.i4.1L_0005: retL_0006: ldc.i4.0L_0007: ret
Hbas
+1  A: 

Just to further reiterate how important performance testing is I ran the following test on my Core i5-750 in 64-bit native and 32-bit mode on Windows 7 compiled with VS 2010 targetting .NET 4.0 and got the following results:

 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();
    }

In x86 mode (Release, naturally):

DoSomething() = 139544
DoSomething2() = 137924

In x64 mode:

DoSomething() = 19417
DoSomething2() = 17448

However, something interesting happens if our distribution of NaN's is sparser. If we change our 0.5 constant to 0.9 (only 10% NaN's) we get:

x86:

DoSomething() = 31483
DoSomething2() = 31731

x64:

DoSomething() = 31432
DoSomething2() = 31513

Reordering the calls shows the same trend as well. Food for thought.

Ron Warholic