Sens optymalizacja potęgowania przy użyciu switch

0

Znalazłem w pewnej bibliotece numerycznej taki kod:

/// <summary>
/// Rasises an argument to an integer power.
/// </summary>
/// <param name="x">The argument.</param>
/// <param name="n">The power.</param>
/// <returns>The value of x<sup>n</sup>.</returns>
/// <remarks>
/// <para>Low integer powers can be computed by optimized algorithms much faster than the general
/// alrogithm for an arbitrary real power employed by <see cref="System.Math.Pow"/>.</para>
/// </remarks>
public static double Pow (double x, int n) {

    if (n < 0) return (1.0 / Pow(x, -n));

    switch(n) {
        case 0:
            // we follow convention that 0^0 = 1
            return (1.0);
        case 1:
            return (x);
        case 2:
            // 1 multiply
            return (x * x);
        case 3:
            // 2 multiplies
            return (x * x * x);
        case 4: {
                // 2 multiplies
                double x2 = x * x;
                return (x2 * x2);
            }
        case 5: {
                // 3 multiplies
                double x2 = x * x;
                return (x2 * x2 * x);
            }
        case 6: {
                // 3 multiplies
                double x2 = x * x;
                return (x2 * x2 * x2);
            }
        case 7: {
                // 4 multiplies
                double x3 = x * x * x;
                return (x3 * x3 * x);
            }
        case 8: {
                // 3 multiplies
                double x2 = x * x;
                double x4 = x2 * x2;
                return (x4 * x4);
            }
        case 9: {
                // 4 multiplies
                double x3 = x * x * x;
                return (x3 * x3 * x3);
            }
        case 10: {
                // 4 multiplies
                double x2 = x * x;
                double x4 = x2 * x2;
                return (x4 * x4 * x2);
            }
        default:
            return (Math.Pow(x, n));
    }


}

Czy taka optymalizacja ma w ogóle sens? Bo ja jakoś nie zauważyłem, żeby to było szybsze od metody z biblioteki standardowej. Spotkaliście się z czymś takim?

0

double n w .pow zamiast int n w twojej wersji. Ten oblicza x do potęgi 0-10 i tylko całkowite. Jak nie z tego przedziału to masz default który uzywa pow.

1

Po pierwsze, pow jest wolny, i to bardzo. Nie wiem, czy da się zrobić ogólnie lepiej niż log2, mnożenie i 2^, dwie z tych operacji są wolne. Szybkie potęgowanie nie ma sensu w przypadku dużych potęg. Switch z kolei jest w miarę szybki, mnożenie podobnie. Dodatkowo niskie potęgi całkowite są zdecydowanie częściej używane niż wysokie, przy optymalizacji takich rzeczy jak biblioteka powinieneś brać pod uwagę właśnie takie przypadki (tzn. częste). Najprawdopodobniej twórcy sprawdzili do której liczby opłaca się taka optymalizacja. Dzięki takiemu rozwiązaniu nie ma potrzeby stosowania takiej samej optymalizacji po stronie osoby piszącej kod z użyciem tej biblioteki (spotkałem się z użyciem takiego rozbijania na mnożenie wielokrotnie i przynosi ono wymierne efekty).

0

Pewnie dużo też zależy od tego o jakiej bibliotece standardowej mówimy...
Tu chyba chodzi o Jave.

Ale w Delphi widziałem że ktoś pisał swoją wersję Pow opartą na ASM - chyba była dużo szybsza (aktualniejsza) niż standardowa.

0

Tu chyba chodzi o Jave.

Ale że niby gdzie? Kod podany przez somekinda jest w C#, można to poznać po konwencji nazewniczej - w Javie metody są od małej litery, w C# od dużej. No i komentarz funkcji nie wygląda mi na JavaDoc.

0

switch to tylko wyliczenie adresu i skok tak więc nie stanowi dużego narzutu. Z kolei ogólny algorytm szybkiego potęgowania jest dobry dla większych liczb, dla mniejszych szybsze będzie zwykłe mnożenie. Do tego zauważ, że potęgowanie do 2 i do 3 potęgi jest całkiem częste więc metoda jest zoptymalizowana pod najczęstszy przypadek.

Bo ja jakoś nie zauważyłem, żeby to było szybsze od metody z biblioteki standardowej

A robiłeś testy na sensownej ilosci danych?

1

Postanowiłem zrobić mały profiling (wcześniej - wczoraj rano - wrzuciłem tu krótkiego posta co o tym myślę, ale doszedłem do wniosku że jest zbyt mało wiarygodny/konkretny i usunąłem go).

Testy robiłem w dwóch odmianach - small (do potęgi 0..4) i big (do potęgi 0..14). Kod jest straszny, ale kto by się tam przejmował:

Zostało użyte 5 różnych funkcji do testowania - LibPow (czyli oryginalne szybkie potęgowanie), IfLibPow (to samo, tylko z drabinką ifów zamiast switcha), IfBsLibPow (to samo, tylko ze strukturą ifów wykonującą wyszukiwanie binarne), CtblPow (za pomocą calltable) i PurePow (czyli Math.Pow systemowe).

Wyniki były dość zaskakujące (ale jednocześnie występują spore różnice przy kolejnych uruchomieniach, czyli ciężko jednoznacznie określić który sposób jest 'najlepszy'), mianowicie:

Small   Purepow:        1844858 // 5
Small   Libpow:         922417 // 3
Small   IfLibpow:       1367359 // 4
Small   IfbsLibpow:     774709 // 1
Small   CtbLibpow:      902713 // 2

Big     Purepow:        2128109 // 5
Big     Libpow:         1488574 // 3
Big     IfLibpow:       1469830 // 1
Big     IfbsLibpow:     1583804 // 4
Big     CtbLibpow:      1482071 // 2

Jeszcze jedna uwaga - to nie jest dokładne uszeregowanie, kolejność na podium skacze prawie za każdym uruchomieniem. To takie w miarę reprezentatywne IMO.

Czyli - ostatnie miejsce zajmuje zawsze czysta funkcja Math.Pow. Smutne...

Trzecie miejsce (sam środek stawki) zajmuje podana przez somekinda LibPow

Drugie miejsce zajmuje calltable - niby nieźle, ale wychodzi na to że w żadnym z powyższych przypadków kompilator nie tworzy własnego calltable, które jest najszybszym możliwym rozwiązaniem takiego problemu (to samo koncepcyjnie co mój kod, ale bez narzutu związanego z wywołaniem lambd).

I teraz walka między pierwszym i czwartym (!) miejscem - jeśli wartości funkcji są małe, wygrywa wyszukiwanie binarne a drabinka ifów (jakim cudem szybsza od switcha? nie wiem.) jest czwarta. Jeśli wartości funkcji są duże, drabinka ifów jest pierwsza a wyszukiwanie binarne - czwarte (jakim cudem skoro to kompletnie nielogiczne? Najwyraźniej branch prediction i optymalizacje JIT wygrywają z intuicją, i średnio 7 porównań wygrywa ze średnio 4).

Przynajmniej takie są moje wnioski, nie wiem na ile ten mój benchmark wiarygodny :>


Edit 2: przeniosłem na pastebin bo strasznie razi ten kod, w dodatku nie mieści się w jednym poście: http://pastebin.com/fgPkLut1

@somekind - nie mam pojęcia, ale ten kod jest tak brzydki że kilka linijek więcej mu nie zaszkodzi :>

1

A u somekinda działa tak samo szybko. Widocznie twój .NET jest zepsuty :D

1

Generalnie domyślna implementacja .NET na Windows korzysta z funkcji _CIpow z msvcrt.dll, która to z kolei zaimplementowana jest za pomocą SEE bądź SEE2. Z tego co wyczytałem za pomocą IDY nie ma żadnych optymalizacji dla małych wykładników.

http://screener.tk/f/o/f/F1Yxv.png

0

Z ciekawości sprawdziłem jak to będzie działać w C++ (funkcja pow). Pierwsza kolumna to ticki druga kolumna to milisekundy.
Dla losowych wykładników z zakresu 0-10
10384551668 3997 //Optymalizacja ze switchem
11399947342 4388 //Optymalizacja z ifami
12107595836 4660 //Bez optymalizacji (sam pow)

Dla losowych wykładników z zakresu 0-999
53774740191 20700 //Optymalizacja ze switchem
55315173044 21293 //Optymalizacja z ifami
53231637415 20491 //Bez optymalizacji (sam pow)

Wyniki są dosyć powtarzalne (tzn. nie zmieniają się bardzo przy każdym uruchomieniu). Generalnie wszystko zgodnie z oczekiwaniami: dla liczb z zakresu 0-10 najszybsza jest optymalizacja ze switchem, potem optymalizacja z ifami (o 0.4s wolniejsza) i najwolniejszy zwykły pow.
Dla liczb zakresu 0-999 sam pow jest najszybszy potem, optymalizacja ze switchami wolniejsza o 0.3 sekundy i najwolniejsza optymalizacja z ifami. Kod można zobaczyć tutaj:
http://pastebin.com/v51vZ5DX

Pozostałoby tylko jeszcze wyliczyć gdzie znajduje się optymalny limit liczb dla których opłaca się robić optymalizację, ale nie chce mi się już tego sprawdzać.

0

@0x200x20
Sens jest chyba dużo większy niż myślałeś:

Metoda 1 - Twoje LibPow()
Metoda 2 - Twoje LibPow() bez pierwszego IF-a - czyli if (n < 0)
Metoda 3 - std::pow()

Zmieniłem pętlę tak, żeby nie losowała, bo wyniki przez to były niestabilne, czyli u mnie:

                sum += LibPow(112.3, i % LIMIT);

Wyniki: (LIMIT 11)

===  pow_test1.exe ===
Execution time: 0.645 s
Execution time: 0.636 s
Execution time: 0.640 s

===  pow_test2.exe ===
Execution time: 0.482 s
Execution time: 0.474 s
Execution time: 0.475 s

===  pow_test3.exe ===
Execution time: 0.668 s
Execution time: 0.630 s
Execution time: 0.639 s

Wyniki dla Limit = 1001

===  pow_test1.exe ===
Execution time: 4.092 s
Execution time: 4.084 s
Execution time: 4.086 s

===  pow_test2.exe ===
Execution time: 3.903 s
Execution time: 3.896 s
Execution time: 3.885 s

===  pow_test3.exe ===
Execution time: 3.929 s
Execution time: 3.929 s
Execution time: 3.924 s

Czyli przy n = 0..1000 metoda 2 nadal jest szybsza od std::pow()

0

@vpiotr - Twoje wyniki wyglądają dosyć podejrzanie, bo wszystkie są praktycznie takie same.
Po pierwsze rand() to liczby pseudolosowe, zawsze takie same przy jednorazowym odpaleniu programu, więc nie wiem jak by to miało wpływać na stabilnosc.
Po drugie pochodzą one z rozkładu jednostajnego (pomijając operacje modulo, która nieznacznie zmienia rozkład ale nie ma to wielkego znaczenia).
Po trzecie sprawdziłem też dla kolejnych i mod n zamiast dla randa i wyniki nadal znacznie się różnią.
Na jakim kompilatorze kompilowałeś, jakie opcje optymalizacji i jaki system?

0

W sumie zgadzam się z Twoimi 1 & 2, ale wyniki mam takie a nie inne.
Moje wyniki mogą się różnić bezwzlędnie w stosunku do Twoich, ale względnie - między sobą powinny być podobne i powinieneś móc to u siebie też zauważyć.
Czas badałem innym narzędziem - zewnętrznym (ptime).

Kompilator: VS2010, x32, opcje: Omit Frame Pointers, Maximize Speed, Favor Fast code, Whole program optimization.

0

Bez losowania liczb dla LIMIT 11 w VS2010 takie same opcje optymalizacji
metoda 1 (ze switchem):
404 ms
metoda 2 (pow)
743 ms

0.3 sekundy różnicy. Mógłbyś wkleić swój program, żebym go u siebie przetestował?

0

Moja wersja (nr metody zgodny z poprzednim postem):

#include "stdafx.h"
#include <cmath>
#include <iostream>

using namespace std;

static double LibPow(double x, int n)
{
    if (n < 0)
    { return (1.0 / LibPow(x, -n)); }
 
    switch (n)
    {
        case 0:
        { return (1.0); }
        case 1:
        { return (x); }
        case 2:
        { return (x * x); }
        case 3:
        { return (x * x * x); }
        case 4:
        {
            double x2 = x * x;
            return (x2 * x2);
        }                case 5:
        {
            double x2 = x * x;
            return (x2 * x2 * x);
        }
        case 6:
        {
            double x2 = x * x;
            return (x2 * x2 * x2);
        }
        case 7:
        {
            double x3 = x * x * x;
            return (x3 * x3 * x);
        }
        case 8:
        {
            double x2 = x * x;
            double x4 = x2 * x2;
            return (x4 * x4);
        }
        case 9:
        {
            double x3 = x * x * x;
            return (x3 * x3 * x3);
        }
        case 10:
        {
            double x2 = x * x;
            double x4 = x2 * x2;
            return (x4 * x4 * x2);
        }
        default:
        { return std::pow(x, n); }
        }
}

static double LibPow2(double x, int n)
{
    switch (n)
    {
        case 0:
        { return 1.0; }
        case 1:
        { return x; }
        case 2:
        { return x * x; }
        case 3:
        { return x * x * x; }
        case 4:
        {
            double x2 = x * x;
            return x2 * x2;
        }                
        case 5:
        {
            double x2 = x * x;
            return x2 * x2 * x;
        }
        case 6:
        {
            double x2 = x * x;
            return x2 * x2 * x2;
        }
        case 7:
        {
            double x3 = x * x * x;
            return x3 * x3 * x;
        }
        case 8:
        {
            double x2 = x * x;
            double x4 = x2 * x2;
            return x4 * x4;
        }
        case 9:
        {
            double x3 = x * x * x;
            return x3 * x3 * x3;
        }
        case 10:
        {
            double x2 = x * x;
            double x4 = x2 * x2;
            return x4 * x4 * x2;
        }
        default:
        { 
            return std::pow(x, n); }
        }
}



#define METHOD 3
#define LIMIT 1001

int main(int argc, char* argv[])
{
        double sum = 0;
        for(int i = 0; i < 100000000; ++i)
        {
#if METHOD == 1
                sum += LibPow(112.3, i % LIMIT);
 
#else
#if METHOD == 2
                sum += LibPow2(112.3, i % LIMIT);
#else
                sum += std::pow(112.3, i % LIMIT);
#endif
#endif
        }
 
        cout << "Result: " << sum << endl;
        return 0;
}

0

/* edit - patrz: mój post poniżej */

Hmm, nieszczególnie jest się czym chwalić, ale:

Chciałem być sprytny i przepisać to do asma, ale chyba coś robię nie tak, bo okazało się że moja superzoptymalizowana wersja zajmuje na moim komputerze 21 sekund ;)
Z drugiej strony, zdecydowanie coś robię nie tak, bo wersja 'zwykła' zajmuje u mnie 17 sekund (u was po 3-4?) - aż tak złego komputera nie mam.

Jeszcze się temu przyjrzę, na razie kod asm (bardzo kiepski kod asm... I tak ponad 2 godziny z życiorysu mi ukradło pisanie poprawianie go. Ech, jak dobrze że powstały języki wysokopoziomowe...)
http://pastebin.com/LYHqjZcG
/* edit - lekko uładniony: http://pastebin.com/0MJgBJNQ */

Oraz kod C:
http://pastebin.com/jYrUMYgB

Kompilacja:

nasm aaa.asm -f win32
gcc aaa.c aaa.obj -std=c99
0

patrzylem do asm zrobionym z wersji C i na tyle na ile sie znam duzo tam do poszalenia nie widzialem. switch robi sie jako calltable, zaraz na poczatku porownanie z 10; zmienne tylko w rejestrach, ale probuj...

0

Teraz się zabrałem za to i...

Pierwsze wrażenie - beznadzieja, musiałbym mój kod przyśpieszyć z 5 razy...

MYPOW:  tick: 4208501913         ms:01539
STDPOW: tick: 4498530887         ms:01645
LIBPOW: tick: 1098325662         ms:00401

Drugie wrażenie, po otworzeniu tego w Idzie... No bez jaj, ten kod stworzony przez GCC to jest przekręt totalny :>.
http://oi48.tinypic.com/148zvqp.jpg

Kod 'zoptymalizowanej' funkcji libPow można podsumować tak (tylko że nie ma ifów a skok w calltable):

if (b < 0) { return 1 / libPow(a, -b); }
if (b > 10) { return pow(a, b); }
if (b == 0) { return 1.0; }
else if (b == 1) { return 112.3; }
else if (b == 2) { return 12611.29; }
...
else { return 319005053244475703296.0; }

Czyli nie ma żadnego potęgowania nigdzie, tylko zwrócenie wartości wyliczonej na etapie kompilacji...

GCC vs reszta świata - 1:0.

Jeśli chce testować wydajność teraz chyba muszę własny test napisać czy coś...


Edit:
Po dopisaniu float f = 0.1; zamienieniu 112.3 na f++;

MYPOW:  tick: 4727433994         ms:01728
STDPOW: tick: 7454183016         ms:02725
LIBPOW: tick: 5282477697         ms:01931

I takie wyniki mi się dużo bardziej podobają...

0

Czemu przekręt? Normalna redukcja :]

0
main(void) {
	float x, y;
	scanf("%f %f", &x, &y);
	x += y / 1000000000;
	printf("%0.9f", x);
} 

na wejściu są dwie jedynki, wynik to 1.000000001 - float tego nie potrafi.

0

w miejsce kłopotliwego scanf() wygodniejsze będzie "volatile float z=1; x = y = z;"

0

Wystarczy wczytać floata, który będziesz potęgował z zewnątrz, żeby tego nie zwijało (scanf, argv).

0

Popatrzmy sobie na programik:

main() {
 	float x, y;
 	// omyłka *********************** było  scanf("%f %f", &x, &y), a miało być to co niżej.
 	x = 1; y = 1; //  przepraszam
 	x += y / 1000000000;
 	printf("%0.9f",`code> Agresywny optymizator może zredukować do postaci `main(){ 
	puts(„1.000000001”); }

Przy okazji, liczba 1.000000001 nie jest liczbą typu float tylko double. Bo na takich liczbach wykonywane są obliczenia. Mimo tego, że w wyrażeniu mamy tylko float i int.
W wersji

 main() {
 	volatile float z=1;
 	float x=z, y=z;
 	x += y / 1000000000;
 	printf("%0.9f", x);}

wymuszamy wykonanie obliczeń przez program, a nie przez kompilator.
Ale przecie w printf() mamy x typu float.
To wcale nie musi być prawdą. Myślę, że zostanie to zoptymizowane do postaci:

 main() {
	float x=1, y=1;
	double __tmp = x + y * 1e-9; 
 	x = __tmp; 
 	printf( "%0.9f", __tm`code> A może nawet jeszcze dalej:`main(){
 	printf(„%0.9f”, 1.0 + 1.0 * 1e-9 ); }
0
Xitami napisał(a):

Popatrzmy sobie na programik:

main() {
float x, y;
 	scanf("%f %f", &x, &y);
 	x += y / 1000000000;
 	printf("%0.9f",`code> Agresywny optymizator może zredukować do postaci `main(){ 
	puts(„1.000000001”); }

Przy okazji, liczba 1.000000001 nie jest liczbą typu float tylko double. Bo na takich liczbach wykonywane są obliczenia. Mimo tego, że w wyrażeniu mamy tylko float i int.

@Xitami, nie wiem w jakim języku głównie siedzisz, ale bez obrazy, chyba nie rozumiesz kodu który sam piszesz...
Kod

x += y / 1000000000

nie może być zredukowany tak jak to podałeś, bo zwykle najpierw robimy dzielenie a potem dodawanie,
czyli wynik to "prawie" x - patrz tutaj: http://ideone.com/H2owg

0

Sposób w jaki obliczane są małe potęgi wynika ze wzoru:x^n=x^{a+b}=x^a\cdot\ x^b gdzie oczywiście n=a+b
jest to sposób gwarantujący liczbę mnożeń nie większą niż gdy użyjemy "Szybkiego Potęgowania"
Warto popatrzeć na drzewko: (znalezione w sieci)

 1    2    4    6   10   18   28     <- wykładnik max
 0    1    2    3    4    5    6     <- liczba mnożeń
----------------------------------------------------------------------
 1 -  2 -  4 -  8 - 16 - 32 - 64     <- jak "szybkie potędowanie"
.     |         |    |    + - 33
.     |         |    + - 18 - 36
.     |         |         + - 19
.     |         + -  9 - 17 - 34
.     |              + - 13 - 26
.     + -  3 -  6 - 12 - 24 - 48
.          |         |    + - 27
.          |         + - 15 - 30
.          |              + - 21 
.          + -  5 - 10 - 20 - 40
.               |    + - 11 - 22
.               + -  7 - 14 - 28

"liczba mnożeń" to tyle mnożeń by uzyskać daną potęgę
"wykladnik max" wystarczy nie więcej niż "liczba mnożeń" by uzyskać wszystki nie większe max

Z drzewka wynika, że 10 nie jest przypadkową liczbą.
W czterech mnożeniach, zmieszczą się wszystkie potęgi aż do 10, ale także 12 i 16
(za 11,13,14,15 można powielić to co jest w default)
Kłopot z tym taki, że mimo że sposób szybszy od szybkigo potęgowania to znalezienie takiego "minimal additino chain" jest zadaniem obliczeniowo złożonym
i w dodatku, to samo można uzyskać na kilka sposobów
(szybszy, znaczy wymagający mniejszej liczby mnożeń)
Użyte w pomiarach %10 i %1000 to bardzo nie "okrągłe" liczby, przy pomiarach wolałbym by dodatkowe zabawki były jak naj mniejsze w porównaniu z tym co chciałbym zmierzyć
Napiszę parę linijek w C, zobaczymy co wyjdzie.

1

pomierzyłem trochę:

#ifdef __cplusplus
#      include <cstdio>
#      include <cmath>
       using namespace std;
#else
#      include <stdio.h>
#      include <math.h>
#endif

static double LibPow(double x, int n) {
	switch (n)    {
		case 0: { return ( 1.0 ); }
		case 1: { return ( x ); }
		case 2: { return ( x * x ); }
		case 3: { return ( x * x * x ); }
		case 4: { double x2 = x * x; return( x2 * x2 ); }
		case 5: { double x2 = x * x; return( x2 * x2 * x ); }
		case 6: { double x2 = x * x; return( x2 * x2 * x2 ); }
		case 7: { double x3 = x * x * x; return( x3 * x3 * x ); }
		case 8: { double x2 = x * x; double x4 = x2 * x2; return( x4 * x4 ); }
		case 9: { double x3 = x * x * x; return( x3 * x3 * x3); }
		case 10:{ double x2 = x * x; double x4 = x2 * x2; return( x4 * x4 * x2 ); }
		case 11:{ return pow(x, n); }
		case 12:{ double x2=x*x; double x6=x2*x2*x2; return( x6*x6 ); }		
		default:{ return pow(x, n); } } }

int main() {
	int i;
	double x=0;
	for( i =0; i<32*1024*1024; i++ )
		x += LibPow(1.000000001, i % 16);  // 16 albo 1024, albo &15 , &1023
	printf("%f", x);
	return 0; }

otrzymałem następujące czasy:

. %    C (gcc-4.3.4)  C++ (gcc-4.3.4)  C++0x (gcc-4.5.1)  C99 strict (gcc-4.3.4)
1024       2.21            0.51              2.52                2.46
. 16       2.39            0.47              2.50                2.55

1024       3.98            1.91              3.95                4.27
. 16       0.63            0.34              0.67                1.57

Pierwsze dwa wiersze dla pow(), drugie dla LibPow()
Mam zgryz, przy %1024 czasy wypadły okropnie, za to małe potęgi wypadły bardzo dobrze.

13/1024 to mało, dodatkowy narzut zjada czas, ale gdyby wyciągnąć sprawę dalej... (np. do sześciu mnożeń, n <= 28)

i jeszcze sprawa precyzji, wydaje mi się, że LibPow() dostarcza zwykle więcej poprawnych cyfr niż pow()

moja tabelka to dwa kompilatory z różnymi przełącznikami (IdeOne), fajnie byłoby gdyby komuś chciało się to sprawdzić na czymś innym.

1

Stara tabelka

. %    C (gcc-4.3.4)  C++ (gcc-4.3.4)  C++0x (gcc-4.5.1)  C99 strict (gcc-4.3.4)
1024       2.21            0.51              2.52                2.46  pow
. 16       2.39            0.47              2.50                2.55
 
1024       3.98            1.91              3.95                4.27  swpow
. 16       0.63            0.34              0.67                1.57

Po zmianie kodu na

int main() {
        int i;
        volatile double z=1.000000001;
        double x=0;
        for( i =0; i< 16*1024*2*1024; i++ )
                x += swpow(z, i % 16);  // 16 albo 1024, pow() albo swpow()
        printf("%f", x);
        return`code> Uruchamiałem 3 razy, odrzucałem czas najmniejszy i największy`. %    C (gcc-4.3.4)  C++ (gcc-4.3.4)  C++0x (gcc-4.5.1)  C99 strict (gcc-4.3.4)
1024       3.99            2.02              3.80                3.97  pow
. 16       2.36            0.60              2.21                2.47
 
1024       3.80            1.85              3.73                4.32  swpow
. 16       0.85            0.41              0.81                1.46

Wygląda, że sens ma.

1

Rozwinąłem case do 18, a później do 32.
Nowe czasy

. %    C (gcc-4.3.4)  C++ (gcc-4.3.4)  C++0x (gcc-4.5.1)  C99 strict (gcc-4.3.4)
1024       3.95            1.84              3.90                4.50  // case do 18
1024       3.88            1.86              3.80                4.60  // case do 32
  32       0.69            0.57              0.65                1.44  // case do 32, i % 32
#define X(n,w) double x##n = w;

static double swpow(double x, int n) {
    switch (n)    {
        case 0: { return ( 1.0 ); }  // 0
        case 1: { return ( x ); }  // 0
        case 2: { return ( x * x ); }  // 1
        case 3: { return ( x * x * x ); }  // 2
        case 4: { X(2,x*x) return( x2 * x2 ); }  // 2
        case 5: { X(2,x*x) return( x2 * x2 * x ); }  // 3
        case 6: { X(2,x*x) return( x2 * x2 * x2 ); }  // 3
        case 7: { X(3,x*x*x) return( x3 * x3 * x ); }  
        case 8: { X(2,x*x) X(4,x2*x2) return( x4 * x4 ); }
        case 9: { X(3,x*x*x) return( x3 * x3 * x3); }
        case 10:{ X(2,x*x) X(4,x2*x2) return( x4 * x4 * x2 ); }
        case 11:{ X(2,x*x) X(4,x2*x2) return( x4 * x4 * x2 * x ); }
        case 12:{ X(2,x*x) X(6,x2*x2*x2) return( x6*x6 ); }
        case 13:{ X(2,x*x) X(4,x2*x2) X(8,x4*x4) return( x8 * x4 *x ); }
        case 14:{ X(3,x*x*x) X(7,x3*x3*x) return( x7*x7 ); }
        case 15:{ X(3,x*x*x) X(6,x3*x3) return( x6*x6*x3 ); }
        case 16:{ double x2=x*x; x2*=x2; x2*=x2; return( x2 * x2 ); }
        case 17:{ X(2,x*x) X(4, x2*x2) X(8,x4*x4) return( x8*x8*x ); }
        case 18:{ X(2,x*x) X(4, x2*x2) X(8,x4*x4) return( x8*x8*x2 ); }  // 5
        case 19:{ X(2,x*x) X(4, x2*x2) X(8,x4*x4) return( x8*x8*x2*x ); }  // 6
        case 20:{ X(2,x*x) X(3,x2*x) X(5, x2*x3) X(10,x5*x5) return( x10*x10 ); }  // 5
        case 21:{ X(3,x*x*x) X(6,x3*x3) X(15,x6*x6*x3); return( x15*x6 ); }
        case 22:{ X(2,x*x) X(4,x2*x2) X(11,x4*x4*x2*x ) return( x11*x11 ); }  // 6
        case 23:{ X(2,x*x) X(3,x2*x) X(5, x2*x3) X(10,x5*x5) return( x10*x10*x3 ); }  // 6
        case 24:{ X(2,x*x) X(6,x2*x2*x2) X(12,x6*x6); return( x12*x12 ); }  // 5
        case 25:{ X(2,x*x) X(4, x2*x2) X(8,x4*x4) X(17,x8*x8*x); return( x17*x8 ); }  // 6
        case 26:{ X(2,x*x) X(4,x2*x2) X(8,x4*x4) X(13,x8*x4*x) return( x13*x13 ); }  // 6
        case 27:{ X(2,x*x) X(3,x2*x) X(6,x3*x3) X(12,x6*x6) return( x12*x12*x3 ); }  //  6
        case 28:{ X(3,x*x*x) X(7,x3*x3*x) X(14,x7*x7) return( x14*x14 ); }  // 6
        case 29:{ return( pow( x, n ) ); }
        case 30:{ X(3,x*x*x) X(6,x3*x3) X(12,x6*x6) return( x12*x12*x6 ); }  // 6
        case 31:{ return( pow( x, n ) ); }
        case 32:{ X(2,x*x) X(4,x2*x2) X(8,x4*x4) X(16,x8*x8) return( x16*x16 ); }  // 5
        default:{ return( pow(x, n) ); } } }

http://ideone.com/0qtsS

0

Przyszło mi do głowy, że trochę można zrównoleglić
przed switch() policzyć x2=xx (2), a może nawet x3=xx*x (</sup>3)
wyniki raczej w w granicach błędu pomiaru
@Wibowit wspominasz czasem o kompilatorze Intela, mógłbyś trochę pomierzyć?

. %    C (gcc-4.3.4)  C++ (gcc-4.3.4)  C++0x (gcc-4.5.1)  C99 strict (gcc-4.3.4)
  32       0.69            0.57              0.65                1.44  było
  32       0.66            0.53              0.64                1.63   ^2
  32       0.64            0.53              0.63                1.81   ^2 ^3
0

Zebrałem stare czasy i dodałem nowe. Ciekawe. http://ideone.com/glOHz

. %    C (gcc-4.3.4)  C++ (gcc-4.3.4)  C++0x (gcc-4.5.1)  C99 strict (gcc-4.3.4)

biblioteczne pow()

1024       3.99            2.02              3.80                3.97 
. 64       2.98            0.94              2.94                3.30
. 32       2.66            0.66              2.57                2.76
. 16       2.36            0.60              2.21                2.47

poniżej swpow

1024       3.95            1.84              3.90                4.50  // case do 18
1024       3.88            1.86              3.80                4.60  // case do 32
  32       0.69            0.57              0.65                1.44  // case do 32
  
1024       3.90            1.84              3.77                4.49  // case do 63
  64       0.60            0.58              0.55                1.82  // case do 63
  32       0.52            0.53              0.49                1.63  // case do 63
  16       0.44            0.46              0.41                1.37  // case do 63

i jeszcze potęgowanie szybkie

1024       1.91            1.91              1.89                3.78
  64       0.81            0.82              0.83                2.19
  32       0.51            0.52              0.50                1.68
  16       0.36            0.34              0.32                1.37

potęgowanie szybkie, ale dokładniejsze fbpow()
 
1024       3.33            3.33              3.85                ? > 5
  64       1.82            1.80              2.10                3.73
  32       1.33            1.34              1.71                3.00
  16       1.06            1.07              1.45                2.68

:-)

1 użytkowników online, w tym zalogowanych: 0, gości: 1