Występuje problem z precyzją zmiennoprzecinkową w bibliotece dynamicznej.Czy plik wykonywalny w systemie Linux może wpływać na precyzję zmiennoprzecinkową w połączonej bibliotece dynamicznej?
set-up jest w następujący sposób:
- Mamy dynamiczną bibliotekę, która wykonuje obliczeń X na dużej tablicy liczb zmiennoprzecinkowych. X składa się z wielu operacji zmiennoprzecinkowych.
- Łączymy to dynamiczne biblioteki do dwóch plików wykonywalnych: A i B.
- W bibliotece drukujemy wejście do obliczeń X.
- dla obu systemem wykonywalnego A i B dokładnie to samo wejście jest zgłoszony (do DBL_DIG miejsca dziesiętne).
- Wyjście obliczeń X jest jednak różna dla wykonywalnego A niż dla wykonywalnego B.
Oba pliki wykonywalne i biblioteki napisane w C++ i skompilowany na tym samym komputerze przy użyciu tego samego GCC wersję kompilatora . Biblioteka jest skompilowana tylko raz z tymi samymi ustawieniami kompilatora co pliki wykonywalne A, ale ustawienia kompilatora dla wykonywalnych B mogą być różne.
Ponieważ używana jest ta sama biblioteka, spodziewaliśmy się tej samej dokładności obliczeń dla obu plików wykonywalnych, pod warunkiem, że dostarczono to samo wejście. Wygląda na to, że precyzja zmiennoprzecinkowa biblioteki zależy od czynników zewnętrznych, np. specyficzne konfiguracje procesów.
Czy jest to możliwe, a jeśli tak, to w jaki sposób można uzyskać taką samą precyzję w obu przebiegach (program A i B)?
Edycja 1
udało mi się stworzyć minimalny przykład, który pokazuje różnice. Jeśli (jak obliczeń X powiedzmy) Używam następujący kod w bibliotece wyniki są różne dla obu przebiegów (A i B):
float* value = new float;
*value = 2857.0f;
std::cout << std::setprecision(15) << std::log(*value) << std::endl;
ja również drukowane pływa w formacie binarnym i pokazują różnicę w ostatni bit.
Niestety nie można kontrolować całego łańcucha kompilacji plików wykonywalnych A. W rzeczywistości A jest ponownie biblioteką dynamiczną, która jest używana z innego pliku wykonywalnego, dla którego nie mogę kontrolować ani znać opcji kompilatora.
Próbowałem użyć wielu różnych opcji kompilatora optymalizacji na pliku wykonywalnym B, aby zobaczyć, czy mogę uzyskać takie same wyniki jak dla pliku wykonywalnego A, ale do tej pory nie rozwiązało to problemu.
Edycja 2
Wyjście asemblera z powyższym kodzie jest:
.LFB1066:
.cfi_startproc
.cfi_personality 0x9b,DW.ref.__gxx_personality_v0
push rbp #
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
push rbx #
.cfi_def_cfa_offset 24
.cfi_offset 3, -24
sub rsp, 8 #,
.cfi_def_cfa_offset 32
mov edi, 4 #,
call [email protected] #
mov DWORD PTR [rax], 0x45329000 #* D.23338,
mov rdi, QWORD PTR [email protected][rip] # tmp66,
mov rax, QWORD PTR [rdi] # cout._vptr.basic_ostream, cout._vptr.basic_ostream
mov rax, QWORD PTR -24[rax] # tmp68,
mov QWORD PTR 8[rax+rdi], 15 # <variable>._M_precision,
movsd xmm0, QWORD PTR .LC1[rip] #,
call [email protected] #
mov rbx, rax # D.23465,
mov rax, QWORD PTR [rax] # <variable>._vptr.basic_ostream, <variable>._vptr.basic_ostream
mov rax, QWORD PTR -24[rax] # tmp73,
mov rbp, QWORD PTR 240[rbx+rax] # D.23552, <variable>._M_ctype
test rbp, rbp # D.23552
je .L9 #,
cmp BYTE PTR 56[rbp], 0 # <variable>._M_widen_ok
je .L5 #,
movsx esi, BYTE PTR 67[rbp] # D.23550, <variable>._M_widen
Edycja 3
Jak sugerowano w komentarzach ja drukowanych zarówno pływających tryb punktu zaokrąglania i Informacje o statusie SSE w bibliotece.
W obu seriach (wykonywalny A i B) dostaję te same wartości:
tryb- zaokrągleń: 895
- SSE cywilny: 8114
Czy jeden z plików binarnych jest kompilowany z wysokim poziomem optymalizacji? Niektóre poziomy mogą powodować niebezpieczną matematykę lub modyfikować precyzję domyślną. Zobacz: https://gcc.gnu.org/wiki/FloatingPointMath dla listy przełączników, które zmieniają zachowanie operacji zmiennoprzecinkowych w GCC. – Matthew
Muszę to sprawdzić. Wrócę do tego jutro. Ale czy to oznacza, że ustawienia kompilacji pliku wykonywalnego mogą wpływać na precyzję w już skompilowanej bibliotece współdzielonej? –
Tak, jeśli opcja zmienia domyślną szerokość lub zachowanie obliczeniowe. Spróbuj wydrukować rzeczywiste bajty zmiennych w bibliotece i nie używaj printf, aby sprawdzić, czy są one poprawnie reprezentowane, i użyj '-ffloat-store' we wszystkich kompilacjach, aby upewnić się, że szerokość pozostaje taka sama. Na wstępie powinieneś wymusić '-00' na wszystkich kompilacjach, aby sprawdzić, czy to rozwiązuje problem. – Matthew