33 votes

Test Double.IsNaN 100 fois plus rapide?

J'ai trouvé ça dans la .NET Code Source: Il prétend être 100 fois plus rapide que l' System.Double.IsNaN. Est-il une raison de ne pas utiliser cette fonction plutôt que d' System.Double.IsNaN?

[StructLayout(LayoutKind.Explicit)]
private struct NanUnion
{
    [FieldOffset(0)] internal double DoubleValue;
    [FieldOffset(0)] internal UInt64 UintValue;
}

// The standard CLR double.IsNaN() function is approximately 100 times slower than our own wrapper,
// so please make sure to use DoubleUtil.IsNaN() in performance sensitive code.
// PS item that tracks the CLR improvement is DevDiv Schedule : 26916.
// IEEE 754 : If the argument is any value in the range 0x7ff0000000000001L through 0x7fffffffffffffffL 
// or in the range 0xfff0000000000001L through 0xffffffffffffffffL, the result will be NaN.         
public static bool IsNaN(double value)
{
    NanUnion t = new NanUnion();
    t.DoubleValue = value;

    UInt64 exp = t.UintValue & 0xfff0000000000000;
    UInt64 man = t.UintValue & 0x000fffffffffffff;

    return (exp == 0x7ff0000000000000 || exp == 0xfff0000000000000) && (man != 0);
}

EDIT: Encore selon la .NET Code Source, le code d' System.Double.IsNaN est le suivant:

public unsafe static bool IsNaN(double d)
{
    return (*(UInt64*)(&d) & 0x7FFFFFFFFFFFFFFFL) > 0x7FF0000000000000L;
}

59voto

Hans Passant Points 475940

Il prétend être 100 fois plus rapide que le Système.Double.IsNaN

Oui, c' utilisé pour être vrai. Il vous manque le temps-machine pour savoir quand cette décision a été prise. Double.IsNaN() n'a pas l'habitude de regarder comme ça. À partir de la SSCLI10 code source:

   public static bool IsNaN(double d)
   {
       // Comparisions of a NaN with another number is always false and hence both conditions will be false.
       if (d < 0d || d >= 0d) {
          return false;
       }
       return true;
   }

Qui effectue très mal sur la FPU dans le code 32 bits si d est NaN. Juste un aspect de la conception de puces, elle est considérée comme exceptionnelle dans le micro-code. Le processeur Intel manuels disent très peu sur le sujet, autre que la documentation d'un processeur perf compteur qui comptabilise le nombre de "virgule Flottante aide", et notant que le micro-code séquenceur entre en jeu pour denormals et NaNs, "potentiellement des coûts des centaines de cycles". Autrement pas un problème de code 64 bits, il utilise des instructions SSE2 qui n'ont pas cette perf frappé.

Un peu de code pour jouer avec pour voir vous-même:

using System;
using System.Diagnostics;

class Program {
    static void Main(string[] args) {
        double d = double.NaN;
        for (int test = 0; test < 10; ++test) {
            var sw1 = Stopwatch.StartNew();
            bool result1 = false;
            for (int ix = 0; ix < 1000 * 1000; ++ix) {
                result1 |= double.IsNaN(d);
            }
            sw1.Stop();
            var sw2 = Stopwatch.StartNew();
            bool result2 = false;
            for (int ix = 0; ix < 1000 * 1000; ++ix) {
                result2 |= IsNaN(d);
            }
            sw2.Stop();
            Console.WriteLine("{0} - {1} x {2}%", sw1.Elapsed, sw2.Elapsed, 100 * sw2.ElapsedTicks / sw1.ElapsedTicks, result1, result2);

        }
        Console.ReadLine();
    }
    public static bool IsNaN(double d) {
        // Comparisions of a NaN with another number is always false and hence both conditions will be false.
        if (d < 0d || d >= 0d) {
            return false;
        }
        return true;
    }
}

Qui utilise la version de Double.IsNaN() qui a obtenu micro-optimisé. Ces micro-optimisations ne sont pas mal dans un cadre btw, la grande charge de Microsoft .NET programmeurs, c'est qu'ils peuvent rarement deviner quand leur code est dans le chemin critique d'une application.

Les résultats sur ma machine lors du ciblage de code 32 bits (Haswell mobile):

00:00:00.0027095 - 00:00:00.2427242 x 8957%
00:00:00.0025248 - 00:00:00.2191291 x 8678%
00:00:00.0024344 - 00:00:00.2209950 x 9077%
00:00:00.0024144 - 00:00:00.2321169 x 9613%
00:00:00.0024126 - 00:00:00.2173313 x 9008%
00:00:00.0025488 - 00:00:00.2237517 x 8778%
00:00:00.0026940 - 00:00:00.2231146 x 8281%
00:00:00.0025052 - 00:00:00.2145660 x 8564%
00:00:00.0025533 - 00:00:00.2200943 x 8619%
00:00:00.0024406 - 00:00:00.2135839 x 8751%

12voto

ken2k Points 24640

Voici une référence naïve:

 public static void Main()
{
    int iterations = 500 * 1000 * 1000;

    double nan = double.NaN;
    double notNan = 42;

    Stopwatch sw = Stopwatch.StartNew();

    bool isNan;
    for (int i = 0; i < iterations; i++)
    {
        isNan = IsNaN(nan);     // true
        isNan = IsNaN(notNan);  // false
    }

    sw.Stop();
    Console.WriteLine("IsNaN: {0}", sw.ElapsedMilliseconds);

    sw = Stopwatch.StartNew();

    for (int i = 0; i < iterations; i++)
    {
        isNan = double.IsNaN(nan);     // true
        isNan = double.IsNaN(notNan);  // false
    }

    sw.Stop();
    Console.WriteLine("double.IsNaN: {0}", sw.ElapsedMilliseconds);

    Console.Read();
}
 

De toute évidence, ils ont tort:

IsNaN: 15012

double.IsNaN: 6243


EDIT + NOTE: Je suis sûr que le timing changera en fonction des valeurs d'entrée, de nombreux autres facteurs, etc., mais affirmer qu'en général, ce wrapper est 100 fois plus rapide que l'implémentation par défaut semble tout simplement faux.

7voto

Cory Nelson Points 10540

J'appelle des manigances. La version "rapide" a un nombre d'opérations considérablement plus important et effectue même plus de lectures depuis la mémoire (pile, donc en L1 mais toujours plus lente que les registres).

 00007FFAC53D3D01  movups      xmmword ptr [rsp+8],xmm0  
00007FFAC53D3D06  sub         rsp,48h  
00007FFAC53D3D0A  mov         qword ptr [rsp+20h],0  
00007FFAC53D3D13  mov         qword ptr [rsp+28h],0  
00007FFAC53D3D1C  mov         qword ptr [rsp+30h],0  
00007FFAC53D3D25  mov         rax,7FFAC5423D40h  
00007FFAC53D3D2F  mov         eax,dword ptr [rax]  
00007FFAC53D3D31  test        eax,eax  
00007FFAC53D3D33  je          00007FFAC53D3D3A  
00007FFAC53D3D35  call        00007FFB24EE39F0  
00007FFAC53D3D3A  mov         r8d,8  
00007FFAC53D3D40  xor         edx,edx  
00007FFAC53D3D42  lea         rcx,[rsp+20h]  
00007FFAC53D3D47  call        00007FFB24A21680  
            t.DoubleValue = value;
00007FFAC53D3D4C  movsd       xmm5,mmword ptr [rsp+50h]  
00007FFAC53D3D52  movsd       mmword ptr [rsp+20h],xmm5  

            UInt64 exp = t.UintValue & 0xfff0000000000000;
00007FFAC53D3D58  mov         rax,qword ptr [rsp+20h]  
00007FFAC53D3D5D  mov         rcx,0FFF0000000000000h  
00007FFAC53D3D67  and         rax,rcx  
00007FFAC53D3D6A  mov         qword ptr [rsp+28h],rax  
            UInt64 man = t.UintValue & 0x000fffffffffffff;
00007FFAC53D3D6F  mov         rax,qword ptr [rsp+20h]  
00007FFAC53D3D74  mov         rcx,0FFFFFFFFFFFFFh  
00007FFAC53D3D7E  and         rax,rcx  
00007FFAC53D3D81  mov         qword ptr [rsp+30h],rax  

            return (exp == 0x7ff0000000000000 || exp == 0xfff0000000000000) && (man != 0);
00007FFAC53D3D86  mov         rax,7FF0000000000000h  
00007FFAC53D3D90  cmp         qword ptr [rsp+28h],rax  
00007FFAC53D3D95  je          00007FFAC53D3DA8  
00007FFAC53D3D97  mov         rax,0FFF0000000000000h  
00007FFAC53D3DA1  cmp         qword ptr [rsp+28h],rax  
00007FFAC53D3DA6  jne         00007FFAC53D3DBD  
00007FFAC53D3DA8  xor         eax,eax  
00007FFAC53D3DAA  cmp         qword ptr [rsp+30h],0  
00007FFAC53D3DB0  setne       al  
00007FFAC53D3DB3  mov         dword ptr [rsp+38h],eax  
00007FFAC53D3DB7  mov         al,byte ptr [rsp+38h]  
00007FFAC53D3DBB  jmp         00007FFAC53D3DC1  
00007FFAC53D3DBD  xor         eax,eax  
00007FFAC53D3DBF  jmp         00007FFAC53D3DC1  
00007FFAC53D3DC1  nop  
00007FFAC53D3DC2  add         rsp,48h  
00007FFAC53D3DC6  ret  
 

Par rapport à la version .NET:

             return (*(UInt64*)(&d) & 0x7FFFFFFFFFFFFFFFL) > 0x7FF0000000000000L;
00007FFAC53D3DE0  movsd       mmword ptr [rsp+8],xmm0  
00007FFAC53D3DE6  sub         rsp,38h  
00007FFAC53D3DEA  mov         rax,7FFAC5423D40h  
00007FFAC53D3DF4  mov         eax,dword ptr [rax]  
00007FFAC53D3DF6  test        eax,eax  
00007FFAC53D3DF8  je          00007FFAC53D3DFF  
00007FFAC53D3DFA  call        00007FFB24EE39F0  
00007FFAC53D3DFF  mov         rdx,qword ptr [rsp+40h]  
00007FFAC53D3E04  mov         rax,7FFFFFFFFFFFFFFFh  
00007FFAC53D3E0E  and         rdx,rax  
00007FFAC53D3E11  xor         ecx,ecx  
00007FFAC53D3E13  mov         rax,7FF0000000000000h  
00007FFAC53D3E1D  cmp         rdx,rax  
00007FFAC53D3E20  seta        cl  
00007FFAC53D3E23  mov         dword ptr [rsp+20h],ecx  
00007FFAC53D3E27  movzx       eax,byte ptr [rsp+20h]  
00007FFAC53D3E2C  jmp         00007FFAC53D3E2E  
00007FFAC53D3E2E  nop  
00007FFAC53D3E2F  add         rsp,38h  
00007FFAC53D3E33  ret  
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X