Ciąg Fibonacciego jest naprawdę ciekawym ciągiem możemy go znaleźć w wielu sytuacjach czy dziedzinach np. w matematyce
lub przy … rozmnażaniu królików. Ciekawostka jest to że liczba płatków w większości kwiatów to właśnie liczby Fibonacciego , większość zwierząt rozmnaża się także zgodnie z ciągiem Fibonacciego.
http://pl.wikipedia.org/wiki/Ciąg_Fibonacciego
Ciąg Fibonacciego jest rekurencyjny tzn. kolejny wyraz jest równy sumie poprzednich dwóch wyrazów co prowadzi do ciekawych właściwości oraz powiązania z innym pojęciem matematycznym który występuje prawie wszędzie nawet w ludzkim ciele 🙂 ! Chodzi oczywiście o Złoty Podział http://pl.wikipedia.org/wiki/Złoty_podział z tym zaś pojęciem związana jest liczba FI
Ciąg wygląda tak
0,1,1,2,3,5,8,13,21,34,55,89,144,..
Przyglądając się ciągowi możemy znaleźć 3 ciekawe właściwości
- Pierwsza z nich jeśli spojrzymy na 3 , 6 i 9 wyraz ciągu zauważymy że wszystkie są podzielne przez 2
następnie patrząc na 4 , 8 i 12 wyraz ciągu wszystkie są podzielne przez 3. Co piąty wyraz dzieli się przez 5 co szósty przez 8 co siódmy przez 13 co ósmy przez 21 i tak dalej. Dzielniki które uzyskaliśmy są kolejnymi liczbami ciągu Fibonacciego 😉 - Druga z nich jeśli weźmiemy 3 dowolne kolejne wyrazy ciągu to pierwszy wyraz pomnożony przez trzeci wyraz zawsze różni się od kwadratu wyrazu drugiego o 1
13,21,34
21,34,55
- Trzecia właściwość jest związana z wspomnianą wyżej liczbą fi jeżeli będziemy liczyć stosunki kolejnych liczb w ciągu będziemy dążyć do coraz dokładniejszej reprezentacji liczby fi
co daje nam
Obliczanie Ciągu iteracyjnie ( elementu na danej pozycji )
Do tego zadania napisałem taką o to małą funkcję w c++
unsigned long int calculateFibonacci( int index ){ unsigned long int lastNumber = 1; unsigned long int sumReturn = 0; unsigned long int tmpValue = 0; for( int iPosition = 0 ; iPosition < index ; iPosition++ ){ tmpValue = sumReturn; sumReturn += lastNumber; lastNumber = tmpValue; } return sumReturn; }
bardzo prosta i działającą całkiem dobrze. No właśnie całkiem ? czy da się to jakoś przyspieszyć ? Otóż da 😉 zaprzęgniemy do tego funkcje typu naked oraz trochę asm
http://gynvael.coldwind.pl/?id=14
Na początku sposób w jaki testowałem wydajność rozwiązań
int iLoop = 1000000; SYSTEMTIME timeStart, timeEnd; GetSystemTime( &timeStart ); while( iLoop >= 0 ){ calculateFibonacci( 1000 ); iLoop--; } GetSystemTime( &timeEnd ); std::cout << "Time : " << ( ( timeEnd.wSecond * 1000 ) + timeEnd.wMilliseconds ) - ( ( timeStart.wSecond * 1000 ) + timeStart.wMilliseconds ) << std::endl;
widać tutaj deklarowanie i incjalizacje zmiennej iLoop której wartość wynosi milion dalej tworzymy dwie struktury SYSEMTIME
które służą do „przechowywania czasu”
następnie wywołujemy GetSystemTime z windows.h dalej nasza funkcja obliczająca dany element ciągu zostaje wywołana 1000000 razy za każdym razem obliczając element na 1000 pozycji
Następnie mamy pobranie czasu oraz obliczenie delty i jej wyświetlenie. Taki sposób benchmarkingu daje niepewne wyniki i jest dość niedokładny ale jednak dla naszych domowych warunków wystarczy.
Jaki jest wynik ? Na moim komputerze oscyluje on około 3200
Znowu pada pytanie czy można to zoptymalizować i jak bardzo ?
Tak jak wcześniej wspomniałem posłużyłem się tutaj asm ( składnia intel czyli do parametrów kompilatora trzeba dodać -masm=intel )
extern "C"{ unsigned int calculateFibonacciASM( int index ); } __asm( ".globl _calculateFibonacciASM\n" "_calculateFibonacciASM:\n" "mov ecx, dword ptr [ esp + 4 ] \n" "mov eax,0\n" "mov esi,1\n" "mov edi,0\n" "loop:\n" "test ecx,ecx\n" "jbe loopEnd\n" "sub ecx,1\n" "mov edi,eax\n" "add eax,esi\n" "mov esi,edi\n" "jmp loop\n" "loopEnd:\n" "ret" );
Widzimy tutaj deklaracje funkcji naked oraz jej etykietę wraz z kodem
Kod testów
iLoop = 1000000; GetSystemTime( &timeStart ); while( iLoop >= 0 ){ calculateFibonacciASM( 1000 ); iLoop--; } GetSystemTime( &timeEnd ); std::cout << "Time : " << ( ( timeEnd.wSecond * 1000 ) + timeEnd.wMilliseconds ) - ( ( timeStart.wSecond * 1000 ) + timeStart.wMilliseconds ) << std::endl;
Jakie są wyniki ? Na moim komputerze oscyluje on wokół 750. Czyli uzyskaliśmy wzrost wydajności o około 72-77% całkiem nieźle :). Pełen kod programu którego używałem do testowania
#include #include extern "C"{ unsigned int calculateFibonacciASM( int index ); } __asm( ".globl _calculateFibonacciASM\n" "_calculateFibonacciASM:\n" "mov ecx, dword ptr [ esp + 4 ] \n" "mov eax,0\n" "mov esi,1\n" "mov edi,0\n" "loop:\n" "test ecx,ecx\n" "jbe loopEnd\n" "sub ecx,1\n" "mov edi,eax\n" "add eax,esi\n" "mov esi,edi\n" "jmp loop\n" "loopEnd:\n" "ret" ); unsigned long int calculateFibonacci( int index ){ unsigned long int lastNumber = 1; unsigned long int sumReturn = 0; unsigned long int tmpValue = 0; for( int iPosition = 0 ; iPosition < index ; iPosition++ ){ tmpValue = sumReturn; sumReturn += lastNumber; lastNumber = tmpValue; } return sumReturn; } int main(){ int iLoop = 1000000; SYSTEMTIME timeStart, timeEnd; GetSystemTime( &timeStart ); while( iLoop >= 0 ){ calculateFibonacci( 1000 ); iLoop--; } GetSystemTime( &timeEnd ); std::cout << calculateFibonacci( 1000 ) << std::endl; std::cout << "Time : " << ( ( timeEnd.wSecond * 1000 ) + timeEnd.wMilliseconds ) - ( ( timeStart.wSecond * 1000 ) + timeStart.wMilliseconds ) << std::endl; iLoop = 1000000; GetSystemTime( &timeStart ); while( iLoop >= 0 ){ calculateFibonacciASM( 1000 ); iLoop--; } GetSystemTime( &timeEnd ); std::cout << calculateFibonacciASM( 1000 ) << std::endl; std::cout << "Time : " << ( ( timeEnd.wSecond * 1000 ) + timeEnd.wMilliseconds ) - ( ( timeStart.wSecond * 1000 ) + timeStart.wMilliseconds ) << std::endl; char ignoreInput; std::cin >> ignoreInput; return 0; }
Źródła
Używając składni AT&T mógłbyś zrobić tą funkcję ładniej ( czytelniej ) 😛
Oraz wydaje mi się że GetTickCount() było by w tym przypadku dokładniejsze.
Jestem przyzwyczajony do składni intela 😉