2011-08-30

Dlaczego przejście z double na float nie przyspieszyło renderowania

Postanowiłem się temu dokładniej przyjrzeć.

Zanim w ogóle uderzymy w C# przyjrzymy się jak to wygląda w C++. Włączamy release, wszelkie optymalizację, zostawiamy informację o debagowaniu pozwalające w miarę śledzić kod, wyłączamy inlinowanie funkcji.
static float Test1(float a, float b)
{
    return a + 0.5f + b;
}

static float Test2(float a, float b)
{
    return sin(a) + 0.5f + b;
}

static double Test3(double a, double b)
{
    return a + 0.5 + b;
}

static double Test4(double a, double b)
{
    return sin(a) + 0.5 + b;
}

static float Test5(double a, float b)
{
    return a + 0.5 + b;
}

static void Test()
{
    double r = 0;

    {
        Stopwatch sw;
        for (int i=0; i<100000000; i++)
            r += Test1(i, i + 5);
        sw.Stop();
        auto t = sw.GetElapsedMilliseconds();
        printf("float simple: %f\n", t);
    }

    {
        Stopwatch sw;
        for (int i=0; i<100000000; i++)
            r += Test3(i, i + 5);
        sw.Stop();
        auto t = sw.GetElapsedMilliseconds();
        printf("double simple: %f\n", t);
    }

    {
        Stopwatch sw;
        for (int i=0; i<10000000; i++)
            r += Test2(i, i + 5);
        sw.Stop();
        auto t = sw.GetElapsedMilliseconds();
        printf("float sin: %f\n", t);
    }

    {
        Stopwatch sw;
        for (int i=0; i<10000000; i++)
            r += Test4(i, i + 5);
        sw.Stop();
        auto t = sw.GetElapsedMilliseconds();
        printf("double sin: %f\n", t);
    }

    {
        Stopwatch sw;
        for (int i=0; i<1000000000; i++)
            r += Test5(i, i + 5);
        sw.Stop();
        auto t = sw.GetElapsedMilliseconds();
        printf("mixed simple: %f\n", t);
    }

    printf("%f", r);
}
Wyniki x64:
float simple: 312.602579
double simple: 309.030617
float sin: 739.128367
double sin: 1250.607130
mixed simple: 4314.021684
Wyniki x86:
float simple: 682.324569
double simple: 498.409225
float sin: 797.726457
double sin: 745.483504
mixed simple: 12319.519241
Można zgłupieć. Raz jedno jest szybsze, raz drugie. Ale generalnie float nie przyspiesza, jedynie w x64 sin jest liczony szybciej na float, ale to raczej wynika z tego, że ktoś popsuł wersję double.

Skorzystanie z _controlfp( _PC_24, MCW_PC ); (przestawienie FPU w tryb pojedynczej precyzji) nic nie zmienia. Jedynie zwalnia, gdyż biblioteka standardowa przestawia i pewnie przywraca status FPU. Tutaj w sieci znalazłem informacje, że sin nie przyspiesza w pojedynczej precyzji ale takie log, pow, div już tak.

Moja szybka wersja funkcji sin, ale pewnie wymagające wielu założeń:
static float mysin(float a)
{
    __asm
    {
        fld a
        fsin
        fst a
    }
}

static double mysin(double a)
{
    __asm
    {
        fld a
        fsin
        fst a
    }
}
Wyniki wersji przyspieszonej x86:
float simple: 687.831624
double simple: 498.392324
float sin: 554.845588
double sin: 548.098432
mixed simple: 12308.813480
Jak widać nasz sin jest szybszy od tego standardowego. Wersji x64 na SIMD ani FPU nie mam, nie chce mi się z tym bawić. Visual C++ nie wspiera wstawek assemblerowych dla x64.

Po włączeniu inlinowania funkcji wersja przyspieszona jeszcze przyspiesza. Wyniki wersji x64 nie zmieniają się. Natomiast wersja x86 przyspiesza i to bardzo:
float simple: 269.746434
double simple: 198.085651
float sin: 774.067095
double sin: 732.198366
mixed simple: 2793.813390
Raytracer napisany C# jest podobnie wydajny w x64 jak w x86. Zgodnie z testami w C++ float są podobnie wydajne jak double. W C# jest podobnie.
float Test1(float a, float b)
{
    return a + 0.5f + b;
}

float Test2(float a, float b)
{
    return (float)Math.Sin(a) + 0.5f + b;
}

double Test3(double a, double b)
{
    return a + 0.5 + b;
}

double Test4(double a, double b)
{
    return Math.Sin(a) + 0.5 + b;
}

float Test5a(double a, float b)
{
    return (float)a + 0.5f + b;
}

float Test5b(double a, float b)
{
    return (float)(a + 0.5 + b);
}
Wyniki w x86:
float simple: 1112
double simple: 421
float sin: 510
double sin: 439
mixed simple A: 11150
mixed simple B: 9555
Wyniki w x64:
float simple: 620
double simple: 610
float sin: 577
double sin: 573
mixed simple A: 8028
mixed simple B: 9330
Testy typu simple są zdecydowanie wolniejsze, ale testy typu sin są porównywalne szybkością do C++. A mixed jest mixed, jak nazwa wskazuje.

Wniosek float nie jest szybsze od double, ale może być szybsze jeśli często odwołujemy się do pamięci.

Brak komentarzy:

Prześlij komentarz