sty
8

Diabeł tkwi w szczegółach precyzji

Pamięć komputera jest skończona. Zastanówmy się przez chwilę, co może wydarzyć się, gdy będziemy chcieli przechować w niej liczby, o nieskończonej ilości cyfr – na przykład ułamki. Zajmijmy się liczbami zmiennoprzecinkowymi, zapisanymi w notacji naukowej. W reprezentacji taką liczbę dzieli się na dwie części: significand (mantysa) oraz exponent (mówi, o ile miejsc należy przesunąć przecinek). Przykładowo dla pary (significand/exponent) 1.246/2 otrzymamy liczbę 124.6 (1.246e2 w notacji naukowej).

Oczywiście nie można zapisać w pamięci liczby zmiennoprzecinkowej z bezbłędną dokładnością. Stosuje się pewne przybliżenia, które mogą wpłynąć na wynik. Co jednak stanie się, gdy dla danej liczby algorytm aproksymacji niewypali?

Pojawił się interesujący błąd , który umożliwił sparaliżowanie części serwerów z obsługą PHP. Przypisanie do zmiennej skryptu PHP wartości liczbowej 2.2250738585072011e-308 (lub przedstawionej w innej wykładniczej postaci) sprawiało, że procesory serwerów pożerały prawie wszystkie zasoby, a następnie następował crash naszej maszyny. Na atak podatne były architektury wykorzystujące koprocesor FPU z zestawem instrukcji x87 (stad posiadacze 64-bitowców, których procesory wykorzystują SSE, zamiast starego już x87 błędu nie zasmakują). Okazało się, że za bug odpowiada funkcja zend_strod() [jest to wewnętrzna funkcja języka PHP]. Konwertuje ona liczbę zmiennoprzecinkową i to (jak się okazało) w dość pechowy sposób. Osobno przetwarzana jest podstawa (2.2250738585072011) oraz wykładnik (-308). W każdej iteracji algorytmu następuje pewne przybliżenie. Pętla kończy swoje działanie, gdy otrzymany błąd jest mniejszy od ustalonego. Okazuje się jednak, że algorytm niezupełnie polepsza przybliżenie błędu, stąd pętla wykonuje się nieskończoną liczbę razy. Taki infinite loop powoduje crash serwera. Chcąc zaimplementować własna arytmetykę liczb zmiennoprzecinkowych okazuje się, że głównym problemem niekoniecznie będą błędne przybliżenia. Dużo poważniejsze mogą okazać się błędy w projekcie algorytmu aproksymacji. Jeśli błąd przybliżenia nigdy nie osiągnie ustawionej w warunku pętli oczekiwanej wartości, lub nie będzie się on zmieniał w kolejnych iteracjach, wtedy nasz algorytm może wykonywać się nieskończenie długo.

Opisany błąd liczby 2.2250738585072011e-308 dotyczy procesorów z zestawem instrukcji x87. Obecnie większość z nich obsługuje również SSE, gdzie ten błąd nie występuje. Najoczywistszym rozwiązaniem jest przekompilowanie interpretera PHP, ustawiając CFLAGS na -ffloat-store lub -mfpmath=sse. Polecam używanie drugiej z flag (w porównaniu do –ffloat-store, po kompilacji programy działają szybciej). GCC, w 32-bitowym środowisku używa domyślnie instrukcji x87. Ustawiając -mfpmath=sse każemy mu wykorzystać SSE (kompilatory x86-64 mają domyślnie ustawioną właśnie tą flagę, dlatego problem z 2.2250738585072011e-308 nie dotykał 64-bitowców).
Istnieje również możliwość zmniejszenia precyzji FPU. Zainteresowanych odsyłam do zapoznania się z makrem _FPU_SETCW z fpu_control.h



  

Dodaj komentarz

*

Audio-CAPTCHA