Thread View: pl.comp.lang.asm
19 messages
19 total messages
Started by DMR
Mon, 04 Feb 2019 09:19
[1/3 NTG] C - Optymalizacje wstawkami
Author: DMR
Date: Mon, 04 Feb 2019 09:19
Date: Mon, 04 Feb 2019 09:19
70 lines
2966 bytes
2966 bytes
Jaka tu cisza i spokój... Raj socjopaty. :-) No, dobra... Całe moje programowanie polega na tworzeniu aplikacji, które mają zassać dane, wymłócić je i wypluć wyniki - tyle, że tych danych bywa sporawo. Używam Code::Blocks z MinGW i różne mam z nim przygody, o czym pewnie jeszcze napiszę, ale ostatnio kompletnie szlag mnie trafił, bo nie mogłem nigdzie znaleźć mojej "ulubionej" funkcji sincos() - a takie odwołanie FPU pamiętam z niegdysiejszych zainteresowań asemblerem. Rozważałem nawet powrót/przejście na inny kompilator, Borland, albo Microsoft, ale to byłby tylko półśrodek, w dodatku kłopotliwy. Mamy przecież koprocesor, który tylko czeka na instrukcje. :-) Podrążyłem trochę temat i znalazłem rozwiązanie, od razu zbenchmarkowane: volatile double a = 0.55; .... for (n = 0; n < nIterations; n++) { asm ("fldl %2;" "fsincos;" "fstpl %1;" "fstpl %0;" : "=m"(sin_a), "=m"(cos_a) : "m"(a)); } Przy nIterations = 50 000 000 całość liczy się nieco ponad sekundę, natomiat przy użyciu sin() i cos() z math.h... prawie cztery. Dla pojedynczych funkcji przyśpieszenie nie jest aż tak spektakularne, ale i tak wychodzi ponad dwa razy szybciej. Warto więc się było nad tematem pochylić. No i kodowanie "na żywca", bez użycia zewnętrznych bibliotek jest jakby bardziej... sexy! :-) Znalazłem taką stronę z gotowcami: https://www.willus.com/mingw/_fastmath.shtml Ale tam czytam: I've noticed lately that the difference between my in-lines and the gcc 3.x/4.x defaults depends significantly on what arguments are sent to the functions. Sometimes mine are faster; sometimes the gcc defaults are faster. In general, with gcc 4.x, I've found that only my sincos in-line gives me any benefit over the gcc default on Core 2 processors, and it's not by much. Może ktoś mi wytłumaczyć, o co tu biega? Bo albo gość zmaścił odwołania FPU, albo GCC nie umie prawidłowo obsłużyć odwołań, albo te wbudowane funkcje liczą sinusy na jakimś SSE/AVX (w co wątpię). Ja w każdym razie mam odmienne doświadczenia (chyba, że się nie znam). Druga sprawa - fajnie było czytać stare książki o asemblerze, przedstawiające program jako sekwencję instrukcji. Ale teraz kończymy drugą dekadę XXI wieku - procesory mają wiele rdzeni, a system ujeżdża jednocześnie kupę procesów. Co się dzieje, jeśli w sam środek misternie poskładanego zestawu instrukcji wpierdzieli nam się inny proces?
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: "Bogdan (bogdro)
Date: Wed, 06 Feb 2019 21:21
Date: Wed, 06 Feb 2019 21:21
180 lines
8259 bytes
8259 bytes
W dniu 04.02.2019 o 18:19, DMR pisze: > Jaka tu cisza i spokój... Raj socjopaty. :-) Po prostu asembler jest tak łatwy, że nikt nie ma pytań :D > No, dobra... > > Całe moje programowanie polega na tworzeniu aplikacji, które mają zassać dane, wymłócić je i wypluć wyniki - tyle, że tych danych bywa sporawo. > Używam Code::Blocks z MinGW i różne mam z nim przygody, o czym pewnie jeszcze napiszę, ale ostatnio kompletnie szlag mnie trafił, bo nie mogłem nigdzie znaleźć mojej "ulubionej" funkcji sincos() - a takie odwołanie FPU pamiętam z niegdysiejszych zainteresowań asemblerem. > Rozważałem nawet powrót/przejście na inny kompilator, Borland, albo Microsoft, ale to byłby tylko półśrodek, w dodatku kłopotliwy. Funkcji sincos() nie masz w ogóle w math.h? Mój "man sincos" mówi, że zadeklarowana jest, ale musisz najpierw zrobić #define _GNU_SOURCE (najlepiej przed jakimkolwiek #include). Sprawdź. Podobno obecne od glibc 2.1. No, ale to poboczna sprawa. > Mamy przecież koprocesor, który tylko czeka na instrukcje. :-) Czeka i się nudzi :) > Podrążyłem trochę temat i znalazłem rozwiązanie, od razu zbenchmarkowane: > > > volatile double a = 0.55; > ... > for (n = 0; n < nIterations; n++) > { > asm ("fldl %2;" > "fsincos;" > "fstpl %1;" > "fstpl %0;" > : "=m"(sin_a), "=m"(cos_a) : "m"(a)); > } > > > Przy nIterations = 50 000 000 całość liczy się nieco ponad sekundę, natomiat przy użyciu sin() i cos() z math.h... prawie cztery. > Dla pojedynczych funkcji przyśpieszenie nie jest aż tak spektakularne, ale i tak wychodzi ponad dwa razy szybciej. > Warto więc się było nad tematem pochylić. No i kodowanie "na żywca", bez użycia zewnętrznych bibliotek jest jakby bardziej... sexy! :-) Wiadomo! :) > Znalazłem taką stronę z gotowcami: > > https://www.willus.com/mingw/_fastmath.shtml > > Ale tam czytam: > I've noticed lately that the difference between my in-lines and the gcc 3.x/4.x defaults depends significantly on what arguments are sent to the functions. Sometimes mine are faster; sometimes the gcc defaults are faster. In general, with gcc 4.x, I've found that only my sincos in-line gives me any benefit over the gcc default on Core 2 processors, and it's not by much. > > > Może ktoś mi wytłumaczyć, o co tu biega? > Bo albo gość zmaścił odwołania FPU, albo GCC nie umie prawidłowo obsłużyć odwołań, albo te wbudowane funkcje liczą sinusy na jakimś SSE/AVX (w co wątpię). > Ja w każdym razie mam odmienne doświadczenia (chyba, że się nie znam). Wkrótce faktycznie ponoć ma już nie być x87 i wszystko ma być liczone za pomocą SSE/AVX itp. Ale tutaj wyraźnie jest instrukcja x87, więc o ile procesor sobie nie "wymyśli" inaczej, powinno to iść przez x87: __asm__ ("fsincos;" : "=t" (*c), "=u" (*s) : "0" (x) : "st(7)"); I też obstawiałbym, że instrukcja sprzętowa powinna być szybsza niż cokolwiek, co jest programowe. Ale rolę tu może grać kilka czynników: - sposób testu: mam nadzieję, że autor kodu czyści poprawnie stos FPU, tzn. wkłada jedną wartość, po czym zdejmuje dwie, dokładnie jak w powyższym benchmarku (bo jeśli nie, to pewnie sypie błędami). Składni wstawek GCC prawie nie znam, więc być może to już się tak robi "samo" dzięki *c i *s. Nie wiem, czemu st7 jest na liście rejestrów psutych, skoro psuty nie jest. Chyba że tylko dla formalności (?), ale może to powodować dodatkową "obudowę" dla tego kodu (zapisanie rejestru). Kod glibc, który mam na systemie, nie ma psutych rejestrów, - sposób testu 2: czy kod jest w pętli? Ile wykonań pętli? Ile przebiegów całego programu? Czy najwyższe wyniki zostały odrzucone, jako posiadające nakład przełączania między zadaniami w systemie? - jak sam cytat mówi, zależy od argumentów funkcji. Być może wartości "znormalizowane" do przedziału [0; 2*pi) liczą się szybciej na FPU, a wartości spoza przedziału - wolniej, - typ procesora - wiadomo, instrukcje są przyspieszane lub spowalniane. Nie wiadomo, czy na tym Core 2 jego wersja działa szybciej dla wszystkich argumentów, czy tylko dla niektórych, czy może dla niektórych działa szybciej niezależnie od procesora, - kompilator / biblioteka C. Na przykład, w moim glibc (2.27) kod sincos() jest dość złożony, bo sprawdza jeszcze błędy i normalizuje parametry, jeśli trzeba, a to na pewno nie przyspiesza: # define __sincos_code \ register long double __cosr; \ register long double __sinr; \ register unsigned int __swtmp; \ __asm __volatile__ \ ("fsincos\n\t" \ "fnstsw %w2\n\t" \ "testl $0x400, %2\n\t" \ "jz 1f\n\t" \ "fldpi\n\t" \ "fadd %%st(0)\n\t" \ "fxch %%st(1)\n\t" \ "2: fprem1\n\t" \ "fnstsw %w2\n\t" \ "testl $0x400, %2\n\t" \ "jnz 2b\n\t" \ "fstp %%st(1)\n\t" \ "fsincos\n\t" \ "1:" \ : "=t" (__cosr), "=u" (__sinr), "=a" (__swtmp) : "0" (__x)); \ *__sinx = __sinr; \ *__cosx = __cosr Ten kod potwierdzałby też teorię o zakresach argumentów i być może to jest właśnie przyczyna, ale bez wiedzy, co robi GCC autora, trudno powiedzieć na 100%. Generalnie rada jest taka, aby mierzyć. Zmierzyłeś, że x87 działa szybciej (przynajmniej dla jednego, stałego argumentu), to używaj x87. Jeśli zakres danych masz inny, wybierz jakąś próbkę zawierającą wartości ze skrajów i środka przedziału, i też zmierz. Przypuszczam, że przedział [0; 2*pi) jest bezpieczny, więc jeśli masz wartości tylko z niego, to pewnie możesz robić samo FSINCOS i np. nie analizować FPU status word i robić tego wszystkiego, co jest powyżej, łącznie ze skakaniem (JZ). > Druga sprawa - fajnie było czytać stare książki o asemblerze, przedstawiające program jako sekwencję instrukcji. > Ale teraz kończymy drugą dekadę XXI wieku - procesory mają wiele rdzeni, a system ujeżdża jednocześnie kupę procesów. > Co się dzieje, jeśli w sam środek misternie poskładanego zestawu instrukcji wpierdzieli nam się inny proces? To jest tzw. context switch i ma on być niewidzialny dla każdego z procesów. I to procesor/system ma zadbać, aby tak było. Z tego, co na szybko znalazłem w podręczniku do AMD64 (tom 2, System Programming, rozdział 1.7.1 Hardware Multitasking), w trybach "legacy" (czyli innych niż 64-bitowy Long Mode - rzeczywisty, chroniony, ...) jest sobie tzw. Task State Segment i przy przełączaniu się na inne zadanie, procesor zapisuje stan bieżącego, po czym przywraca, gdy to zadanie/proces znów stanie się bieżącym/aktywnym. Ten sam rozdział mówi o tym, że w nowszych systemach operacyjnych przełączanie zadań jest wykonywane programowo, więc w tym przypadku system ma zadbać o to, aby wszystko zapisać i przywrócić. A jak np. stary system ma wiedzieć o nowych rejestrach, które zostały wprowadzone po jego wydaniu? Pewnie nie wie, ale może po to ma instrukcje z "SAVE" i "RSTOR" w nazwie, aby wiedziały za niego. Szczegółów niestety nie znam, ale jeśli będziesz chciał sobie poczytać, to może będzie to "jakiś" trop. Podręczniki nazywają się: - Intel Architecture Software Developer's Manual, - AMD64 Architecture Programmer’s Manual. To w kwestii zachowywania stanu procesora. W kwestii wydajności - jeśli w trakcie pętli nastąpi przełączenie zadania, to wydajność może nieźle paść. Dlatego np. jak robisz pętle do mierzenia czasu np. po 1000 iteracji i dostajesz wynik X, a jak zrobisz po 10.000 i dostajesz nie zawsze 10 * X, tylko np. w 90% przypadkach 10 * X, a w 10% np. 100 * X, to wiadomo, że było przerwanie/przełączenie zadania. Mierzenia czasu to oczywiście nie ułatwia, dlatego pisałem o robieniu kilku/wielu prób i odrzucaniu najwyższych wyników (np. 10% lub wszystkich tych, gdzie był wyraźny "pik"). -- Pozdrawiam/Regards - Bogdan (GNU/Linux & FreeDOS) Kurs asemblera x86 (DOS, GNU/Linux): http://bogdro.evai.pl Grupy dyskusyjne o asm: pl.comp.lang.asm alt.pl.asm alt.pl.asm.win32 www.Xiph.org www.TorProject.org Soft(EN): http://bogdro.evai.pl/soft
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: DMR
Date: Fri, 08 Feb 2019 09:31
Date: Fri, 08 Feb 2019 09:31
162 lines
6747 bytes
6747 bytes
To była cisza przed burzą... :-) Dzięki za odpowiedź! > To jest tzw. context switch i ma on być niewidzialny dla każdego z > procesów. I to procesor/system ma zadbać, aby tak było. > Z tego, co na szybko znalazłem w podręczniku do AMD64 (tom 2, System > Programming, rozdział 1.7.1 Hardware Multitasking), w trybach "legacy" > (czyli innych niż 64-bitowy Long Mode - rzeczywisty, chroniony, ...) > jest sobie tzw. Task State Segment i przy przełączaniu się na inne > zadanie, procesor zapisuje stan bieżącego, po czym przywraca, gdy to > zadanie/proces znów stanie się bieżącym/aktywnym. No właśnie. Przypuszczałem, że między procesami jest to jakoś tak załatwione. > __asm__ ("fsincos;" : "=t" (*c), "=u" (*s) : "0" (x) : "st(7)"); Ta składnia jest koszmarna, ale domyślam się, że stanowi ona pewnego rodzaju ochronę przed "wtargnięciem" w ciało programu. Kompilator ma prawo przecież robić jakieś swoje założenia, a nie może być tak, że w połowie wchodzimy jak Ruski do baru i zaczynamy mu przestawiać rejestry. Tyle, że jest problem - jak my nie chcemy słuchać kompilatora, to on też może się wypiąć i pomimo naszych prób optymalizacji całość wyjdzie gorzej niż przedtem... Na szczęście koprocesor ze swoją stosową strukturą wydaje się pod tym względem raczej "bezpieczny", ale CPU to już inny level. Okazuje się, że używanie "prostych" wstawek, to nie jest wcale trywialny problem. > Wkrótce faktycznie ponoć ma już nie być x87 i wszystko ma być liczone > za pomocą SSE/AVX itp. Ale w sumie - co mnie właściwie obchodzi, który tranzystor liczy mi sinusa? :-) > I też obstawiałbym, że instrukcja sprzętowa powinna być szybsza niż > cokolwiek, co jest programowe. Tego nawet nie zakładałem. Po prostu sugerowanie, że bezpośrednie odwołanie do FPU może działać wolniej, niż rozwiązanie pośrednio odwołujące się do tego samego FPU nie grało mi z powodów "cybernetycznych". Mea culpa - nie przestudiowałem tych funkcji. Myślałem, że gość wie co robi... ;-) > Na przykład, w moim glibc (2.27) kod > sincos() jest dość złożony, bo sprawdza jeszcze błędy i normalizuje > parametry, jeśli trzeba Ta instrukcja jest zdaje się bardzo tolerancyjna, jeśli chodzi o parametry? Zdaje się, że przyjmuje od -2^63 do 2^63 Kto by zresztą tyle tam wpisywał - ważne, że podanie mu -0.333 nie spowoduje wyświetlenia pozdrowień od Billa Gatesa... ;-) A skoro już przy rozwiązaniach sprzętowych stanęliśmy... Ostatnio musiałem napisać procedurę, która z pliku tekstowego wczytanego jako blok bajtów "wyparsuje" odpowiednie dane, ale okazało sie, że standardowe funkcje strtol() i strtod() zachowują się wyjątkowo dziwacznie w stosunku do deklarowanej logiki ich działania (z zagadkowych przyczyn nie kończyły działania na endptr, tylko wyłaziły dalej szukając zera, a przy okazji zawieszając program...). Potrzeba matką wynalazków, więc szybko spłodziłem odpowiedniki, co doprowadziło mnie niemal do euforii, bo okazało się, że "moje" funkcje działają KILKADZIESIĄT razy szybciej od standardowych. A że pliczek miał 87 megabajtów.... ;-) Skoro tak, to - co było do przewidzenia - szybko zajawiłem, żeby sprawić sobie własny komplet jak najlepszych procedur konwertujących tekst <-> liczby. Z tekstów na liczby poszło gładko, tym bardziej, że mnożenie przez 10 można zastąpić sumą iloczynów przez 8 i 2. Za to w druga stronę... też poszło gładko. ;-) Przynajmniej w tym sensie, że udało się uzyskać kilkukrotne przyśpieszenie względem funkcji standardowych. W odpowiedniku itoa() znalazł się taki fragment (z dokładnością do zera): *cptr = 0x0; while (iValue) { *--cptr = char(iValue % 10) | '0'; iValue /= 10; } Niby wszystko gra, ale każdego szanującego się zboczeńca-perfekcjonistę w oczy kłuje to modulo, a za chwilę dzielenie przez 10. Kombinowałem, żeby resztę uzyskać z ilorazu, z tymi shiftami, ale wyniki uzyskałem bardzo podobne, z tendencją lekko gorszą. Coś mi się we łbie kolebało z tym modulo i przy okazji poszukiwań natrafiłem na stronę http://bogdro.evai.pl/ ;-) Faktycznie, instrukcja DIV liczy iloraz i modulo za jednym zamachem, przerżnąłem więc ten kod i... d*pa! :-( Wyszło dwa razy wolniej. Tego już w ogóle nie rozumiałem, więc szybko nauczyłem się instrukcji -S, żeby GCC wypluł mi kod asemblera. I, patrz Pan, nie było tam niczego choćby podobnego do DIV... Jako, że nie byłem pewny, czy tu ktokolwiek zagląda, swojemu rozgoryczeniu dałem upust na pl.comp.programming, wklejając "publiczny" kod: https://godbolt.org/z/Xs117J Jakaś dobra dusza potwierdziła moje mgliste domysły, że chodzi o użycie dużego "quasi-współczynnika", który tam się potem odpowiednio "przekręcą", a potem shift i tak dalej. Postudiuję ten temat, zwłaszcza że z daleka śmierdzi mi jakimś przepełnieniem, ale nie jestem przekonany, czy da się tu uzyskać jakieś przyśpieszenie względem kodu w C. A sprawa jest kluczowa, bo jest to też fragment konwersji liczb zmiennoprzecinkowych na tekst. Na razie rozdzielam część rzeczywistą i ułamkową konwersją na int i odejmowaniem/mnożeniem, ale może tu da się coś jeszcze ugryźć - zależy mi tylko na konwersji w "zafiksowanym" formacie RRRRRRRR.FFFFFFFF Tu się kolega na ten temat mocno produkuje: https://github.com/miloyip/dtoa-benchmark No, to się wygadałem. :-)
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: "Bogdan (bogdro)
Date: Fri, 08 Feb 2019 22:27
Date: Fri, 08 Feb 2019 22:27
162 lines
7403 bytes
7403 bytes
W dniu 08.02.2019 o 18:31, DMR pisze: > To była cisza przed burzą... :-) > Dzięki za odpowiedź! :-) [...] >> __asm__ ("fsincos;" : "=t" (*c), "=u" (*s) : "0" (x) : "st(7)"); > > > Ta składnia jest koszmarna, ale domyślam się, że stanowi ona pewnego rodzaju ochronę przed "wtargnięciem" w ciało programu. > Kompilator ma prawo przecież robić jakieś swoje założenia, a nie może być tak, że w połowie wchodzimy jak Ruski do baru i zaczynamy mu przestawiać rejestry. Dokładnie. Po to właśnie są te parametry wejściowe i wyjściowe, a zwłaszcza "clobber list". Choć to i tak jest raczej "wskazówka", które rejestry psujemy, aby kompilator mógł je sobie zachować. Jakby popsuć jakiś rejestr i nie umieścić go na liście, to raczej kompilator by nie wykrył. Ale cóż, to i tak autor takiego kodu sam sobie robi "kuku", a nie kompilatorowi :). > Tyle, że jest problem - jak my nie chcemy słuchać kompilatora, to on też może się wypiąć i pomimo naszych prób optymalizacji całość wyjdzie gorzej niż przedtem... No, w środek wstawki się raczej nie wprosi, ale może np. obłożyć wstawkę właśnie np. zapisywaniem modyfikowanych rejestrów (PUSH + POP) lub spowolnić kod otaczający w inny sposób, fakt. > Na szczęście koprocesor ze swoją stosową strukturą wydaje się pod tym względem raczej "bezpieczny", ale CPU to już inny level. > > Okazuje się, że używanie "prostych" wstawek, to nie jest wcale trywialny problem. Nie, nie musi być, nawet pomijając mniej lub bardziej dziwne składnie. Można zawsze pisać swoje procedurki w osobnym pliku, kompilować osobno i dopiero linkować razem. Ale płaci się za to wywołaniem CALL, stosem itd. [...] >> Na przykład, w moim glibc (2.27) kod >> sincos() jest dość złożony, bo sprawdza jeszcze błędy i normalizuje >> parametry, jeśli trzeba > > > Ta instrukcja jest zdaje się bardzo tolerancyjna, jeśli chodzi o parametry? > Zdaje się, że przyjmuje od -2^63 do 2^63 Według podręcznika - tak. Ale kod glibc wygląda tak a nie inaczej z jakichś powodów. Pewnie jest przyczyna, dla której sprawdza flagi koprocesora. Nie sprawdzałem, która to flaga (przepełnienie, wartość zdenormalizowana, ...). No i nie wiem, jak tam z precyzją przy dużych parametrach - może nie być tak dobra, jak dla małych. > Kto by zresztą tyle tam wpisywał - ważne, że podanie mu -0.333 nie spowoduje wyświetlenia pozdrowień od Billa Gatesa... ;-) :) > A skoro już przy rozwiązaniach sprzętowych stanęliśmy... > > Ostatnio musiałem napisać procedurę, która z pliku tekstowego wczytanego jako blok bajtów "wyparsuje" odpowiednie dane, ale okazało sie, że standardowe funkcje strtol() i strtod() zachowują się wyjątkowo dziwacznie w stosunku do deklarowanej logiki ich działania (z zagadkowych przyczyn nie kończyły działania na endptr, tylko wyłaziły dalej szukając zera, a przy okazji zawieszając program...). Większość funkcji C działa na łańcuchach znaków kończących się bajtem zerowym, bo po prostu tak są zdefiniowane łańcuchy znaków w C. Można zawsze np. zmienić '\n' lub kropkę na bajt zerowy '\0' i dopiero takim "stuningowanym" łańcuchem nakarmić strdo*(). > Potrzeba matką wynalazków, więc szybko spłodziłem odpowiedniki, co doprowadziło mnie niemal do euforii, bo okazało się, że "moje" funkcje działają KILKADZIESIĄT razy szybciej od standardowych. A że pliczek miał 87 megabajtów... ;-) Tak, asembler pomaga :). > Skoro tak, to - co było do przewidzenia - szybko zajawiłem, żeby sprawić sobie własny komplet jak najlepszych procedur konwertujących tekst <-> liczby. > Z tekstów na liczby poszło gładko, tym bardziej, że mnożenie przez 10 można zastąpić sumą iloczynów przez 8 i 2. > Za to w druga stronę... też poszło gładko. ;-) > Przynajmniej w tym sensie, że udało się uzyskać kilkukrotne przyśpieszenie względem funkcji standardowych. > > W odpowiedniku itoa() znalazł się taki fragment (z dokładnością do zera): > > *cptr = 0x0; > while (iValue) > { > *--cptr = char(iValue % 10) | '0'; > iValue /= 10; > } > > Niby wszystko gra, ale każdego szanującego się zboczeńca-perfekcjonistę w oczy kłuje to modulo, a za chwilę dzielenie przez 10. Samo dzielenie też kłuje ;). > Kombinowałem, żeby resztę uzyskać z ilorazu, z tymi shiftami, ale wyniki uzyskałem bardzo podobne, z tendencją lekko gorszą. > > Coś mi się we łbie kolebało z tym modulo i przy okazji poszukiwań natrafiłem na stronę http://bogdro.evai.pl/ ;-) > > Faktycznie, instrukcja DIV liczy iloraz i modulo za jednym zamachem, przerżnąłem więc ten kod i... d*pa! :-( > Wyszło dwa razy wolniej. > > Tego już w ogóle nie rozumiałem, więc szybko nauczyłem się instrukcji -S, żeby GCC wypluł mi kod asemblera. > I, patrz Pan, nie było tam niczego choćby podobnego do DIV... Pewnie poziom optymalizacji większy od zera? > Jako, że nie byłem pewny, czy tu ktokolwiek zagląda, swojemu rozgoryczeniu dałem upust na pl.comp.programming, wklejając "publiczny" kod: > https://godbolt.org/z/Xs117J > > Jakaś dobra dusza potwierdziła moje mgliste domysły, że chodzi o użycie dużego "quasi-współczynnika", który tam się potem odpowiednio "przekręcą", a potem shift i tak dalej. Tak, często zamiast dzielenia przez X stosuje się mnożenie przez 2^n/X. Wtedy "iloraz" odczytujesz ze starszego rejestru. Np. dla liczb 16-bitowych, dla uproszczenia, współczynnik wynosi 65536/10 = 6553: A / 10 = ((A << 16)/10) >> 16 = (A * 6553) >> 16 Czyli zamiast dzielić R/E/AX przez 10, mnożysz przez współczynnik i odczytujesz wynik z R/E/DX. Nie jest to dokładnie tak, bo jeszcze czasem trzeba "poprawić". Ale z grubsza. Ten konkretny kod, oprócz mnożenia przez upatrzony współczynnik ((2^32 - 1) * 8/10), robi dodatkowe rzeczy: - dzieli EDX przez 8 (czyli EDX = 1/10 dzielnej << 32), - kopiuje go do EAX, mnoży przez 4 - dodaje EDX do EAX (tutaj EAX = EDX * 5), - mnoży EAX przez 2 (EAX = EDX * 10), - odejmuje to od dzielnej (ECX = dzielna - dzielna * 2^32 * 8/10 / 8 * 10 = dzielna - dzielna / 10 * 10 = dzielna % 10), - zwraca tę różnicę. > Postudiuję ten temat, zwłaszcza że z daleka śmierdzi mi jakimś przepełnieniem, ale nie jestem przekonany, czy da się tu uzyskać jakieś przyśpieszenie względem kodu w C. > A sprawa jest kluczowa, bo jest to też fragment konwersji liczb zmiennoprzecinkowych na tekst. > Na razie rozdzielam część rzeczywistą i ułamkową konwersją na int i odejmowaniem/mnożeniem, ale może tu da się coś jeszcze ugryźć - zależy mi tylko na konwersji w "zafiksowanym" formacie RRRRRRRR.FFFFFFFF [...] Jeśli kompilujesz C z optymalizacjami, to wygrać z kompilatorem w tych czasach może być ciężko. Natomiast można oszczędzić w innych miejscach, np. I/O jest powolne - nie wyświetlaj na ekran ani nie zapisuj do pliku po jednym czy kilku bajtach. Buforuj i zapisuj cały bufor na raz. Tak samo z odczytem. Ale to już zależy, co program ma robić. -- Pozdrawiam/Regards - Bogdan (GNU/Linux & FreeDOS) Kurs asemblera x86 (DOS, GNU/Linux): http://bogdro.evai.pl Grupy dyskusyjne o asm: pl.comp.lang.asm alt.pl.asm alt.pl.asm.win32 www.Xiph.org www.TorProject.org Soft(EN): http://bogdro.evai.pl/soft
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: mezzogm@gmail.co
Date: Mon, 11 Feb 2019 07:56
Date: Mon, 11 Feb 2019 07:56
149 lines
4578 bytes
4578 bytes
> Nie, nie musi być, nawet pomijając mniej lub bardziej dziwne składnie. > Można zawsze pisać swoje procedurki w osobnym pliku, kompilować > osobno i dopiero linkować razem. Ale płaci się za to wywołaniem CALL, > stosem itd. Czyli, tradycyjnie - znaj proporcjum, mocium panie! ;-) Nauka jest taka, że sens ma "owstawkowanie" W CAŁOŚCI bloków wydzielonych funkcjonalnie z programu (niekoniecznie w postaci osobnej procedury). > Większość funkcji C działa na łańcuchach znaków kończących się bajtem > zerowym, bo po prostu tak są zdefiniowane łańcuchy znaków w C. long int strtol (const char* str, char** endptr, int base) double strtod (const char* str, char** endptr) Funkcja zwraca przekonwertowaną wartość, zwraca również (przez referencję endptr) wskaźnik do pierwszego znaku ZA przekonwertowanym ciągiem cyfr. Jakiekolwiek gmeranie w bajtach dalej odbieram jako RAŻĄCY BŁĄD - chyba, że ktoś mnie przekona, że jest w tym jakiś głębszy sens > Można zawsze np. zmienić '\n' lub kropkę na bajt zerowy '\0' i > dopiero takim "stuningowanym" łańcuchem nakarmić strdo*(). Można różne rzeczy, ale "ręczne" parsowanie ciągu bajtów w celu stworzenia złudzenia jakiejś użyteczności zrypanej funkcji standardowej budzi u mnie wewnętrzny sprzeciw. > Pewnie poziom optymalizacji większy od zera? Poziom optymalizacji, to takie trochę czary-mary-makagigi. Kod wynikowy - to jest konkret. :-) > Natomiast można oszczędzić w innych miejscach, np. I/O jest powolne - > nie wyświetlaj na ekran ani nie zapisuj do pliku po jednym czy kilku > bajtach. Buforuj i zapisuj cały bufor na raz. Tak samo z odczytem. O czym ta mowa... ;-) Korzystając z prostych środków napisałem program, który wykonuje swoją pracę w niecałe pół sekundy, podczas gdy jego wersja "kanoniczna" robiła to samo w ponad cztery - już z uwzględnieniem buforowania. To jest zysk wyłącznie na optymalizacji I/O, i to nawet bez powąchania asemblera. ;-) Sprzętowo takie przyśpieszenie mógłby zapewnić chyba upgrade do jakiegoś i7 z 2026 roku - a i to wątpliwe, bo ostatnio gęstnieją narzekania, że z coś nie ten-tego z Prawem Moore'a... W ogóle taką "masówkę" powinno się załatwiać w wątku niezależnym od GUI, a już najlepiej temat zrównoleglić. Tylko jak zrównoleglić sekwencyjny odczyt z pliku? Można by stanąć gdzieś w środku bufora, złapać na szybko koniec linii i puścić drugi wątek parsujący. Jeszcze tylko trzeba upchać jakoś dane z dwóch wątków, wiedząc, że wyszukiwać w nich będzie funkcja hashująca... Choroba, samo pisanie na tej grupie jest inspirujące! :-) > Jeśli kompilujesz C z optymalizacjami, to wygrać z kompilatorem w > tych czasach może być ciężko. Taki kod w C: QueryPerformanceCounter(&StartCounter); *cptr = 0x0; while (iValue) { *--cptr = char(iValue % 10) | '0'; iValue /= 10; } QueryPerformanceCounter(&EndCounter); wypluty przez MinGW do kodu asemblera wygląda tak: call _QueryPerformanceCounter@4 subl $4, %esp movl $2137483647, %ecx leal 143(%esp), %ebx leal 133(%esp), %edi movb $0, 143(%esp) .p2align 4,,10 L2: movl %ecx, %eax subl $1, %ebx imull %esi movl %ecx, %eax sarl $31, %eax sarl $2, %edx subl %eax, %edx leal (%edx,%edx,4), %eax addl %eax, %eax subl %eax, %ecx orl $48, %ecx cmpl %edi, %ebx movb %cl, (%ebx) movl %edx, %ecx jne L2 leal 72(%esp), %eax movl %eax, (%esp) call _QueryPerformanceCounter@4 subl $4, %esp Zakładam, że cała akcja dzieje się pomiędzy dwoma wywołaniami QueryPerformanceCounter. Da się tu coś istotnego wywalić? Na moje oko (skłute składnią AT&T :D) kompilator wyłapał i uwzględnił sąsiadujące modulo i dzielenie przez 10. A wtedy - nic tu się nie urwie (ponad poziom szumu). Czym się kurde różni SAR od SHR?
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: DMR
Date: Tue, 12 Feb 2019 08:04
Date: Tue, 12 Feb 2019 08:04
12 lines
543 bytes
543 bytes
A tak przy okazji I/O - dzisiaj gadam do smartfona, a on wszystko rozumie, Apple ma Siri... I to wszystko na jakimś tam ARM. A tu człowiek się boksuje z jakimś spritf... Mało tego, temat wcale nie jest zamknięty: https://dl.acm.org/citation.cfm?id=3192369 Faktem jest, że jak "głupi" smartfon coś źle zrozumie, to można mu wyperswadować, tak czy siak, ale jak output "niechcący" poprzestawia cyferki, to może się skończyć katastrofą... ;-)
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: DMR
Date: Tue, 12 Feb 2019 15:07
Date: Tue, 12 Feb 2019 15:07
70 lines
2585 bytes
2585 bytes
> Fakt, funkcja powinna się zatrzymać po ostatnim rozpoznanym znaku. > Masz jakiś minimalistyczny kod, którym można odtworzyć problem, wraz z > parametrami, które problem generują? O kodzie nie ma co dyskutować: char *cBuffer; // wskazuje na zaalokowaną pamięć (new / malloc() / lub // windowsowe HeapAlloc()) z zassanym plikiem tekstowym char *cptr, *endptr; double fValue; cptr = cBuffer; while (*cptr < '!') // przeskocz białe znaki cptr++; fValue = strtod(cptr, &endptr); cptr = endptr; I tak dalej. Natomiast same "parametry" potrafią ważyć kilkadziesiąt mega, i dlatego problem stał się... zauważalny - delikatnie rzecz ujmując. Chodzi o to, że jeśli ciąg znaków reprezentujący liczbę całkowitą/zmiennoprzecinkową znajdzie się na początku "długiego" bufora znaków, to czas odczytu funkcją strtol/strtod robi się SKANDALICZNIE długi. Dla bufora o długości kilku megabajtów to są już prawie setne sekundy - to razy te megabajty liczb powoduje totalne zawieszenie programu z CPU wykręconym na 100%. Zacząłem kombinować i okazało się, że wstawienie "\0" zaraz za cyferkami rozwiązuje problem - ale mnie to, niestety, absolutnie nie satysfakcjonuje... Co ciekawe - myślałem, że to może team GCC coś tam "poprawił", podpiąłem więc do C::B swego czasu kozacki kompilator MS Visual C++ Toolkit 2003 - to samo. > Funkcja nie jest zrypana z punktu widzenia jej użyteczności w > ogólnych zastosowaniach. Jest zrypana z punktu widzenia logiki funkcjonowania, a to wystarczy. > Twoje dane nie pasują do tej akurat funkcji, więc trzeba coś zrobić - > albo pogmerać trochę w danych tak, aby działały funkcje standardowe, > albo zrobić swoje funkcje, lepiej pasujące do danych. Generalnie to > drugie rozwiązanie pewnie będzie lepsze i szybsze (chociażby przez to, > że przez ciąg bajtów przejdzie tylko raz). Nie mówiąc o "wartości > edukacyjnej" :) A co ja właśnie robię...? :D Ale tak to już jest - zaczynasz rozwiązywać jeden problem i.... zaraz pojawia się kolejka następnych. Może to kwestia nastawienia...? :D
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: "Bogdan (bogdro)
Date: Tue, 12 Feb 2019 21:23
Date: Tue, 12 Feb 2019 21:23
175 lines
6259 bytes
6259 bytes
W dniu 11.02.2019 o 16:56, mezzogm@gmail.com pisze: [...] >> Większość funkcji C działa na łańcuchach znaków kończących się bajtem >> zerowym, bo po prostu tak są zdefiniowane łańcuchy znaków w C. > > > long int strtol (const char* str, char** endptr, int base) > double strtod (const char* str, char** endptr) > > Funkcja zwraca przekonwertowaną wartość, zwraca również (przez referencję endptr) wskaźnik do pierwszego znaku ZA przekonwertowanym ciągiem cyfr. > Jakiekolwiek gmeranie w bajtach dalej odbieram jako RAŻĄCY BŁĄD - chyba, że ktoś mnie przekona, że jest w tym jakiś głębszy sens Fakt, funkcja powinna się zatrzymać po ostatnim rozpoznanym znaku. Masz jakiś minimalistyczny kod, którym można odtworzyć problem, wraz z parametrami, które problem generują? >> Można zawsze np. zmienić '\n' lub kropkę na bajt zerowy '\0' i >> dopiero takim "stuningowanym" łańcuchem nakarmić strdo*(). > > > Można różne rzeczy, ale "ręczne" parsowanie ciągu bajtów w celu stworzenia złudzenia jakiejś użyteczności zrypanej funkcji standardowej budzi u mnie wewnętrzny sprzeciw. Funkcja nie jest zrypana z punktu widzenia jej użyteczności w ogólnych zastosowaniach. Nie jest i nie ma być uniwersalną funkcją umiejącą sparsować wszystko, co się da. Twoje dane nie pasują do tej akurat funkcji, więc trzeba coś zrobić - albo pogmerać trochę w danych tak, aby działały funkcje standardowe, albo zrobić swoje funkcje, lepiej pasujące do danych. Generalnie to drugie rozwiązanie pewnie będzie lepsze i szybsze (chociażby przez to, że przez ciąg bajtów przejdzie tylko raz). Nie mówiąc o "wartości edukacyjnej" :). >> Pewnie poziom optymalizacji większy od zera? > > > Poziom optymalizacji, to takie trochę czary-mary-makagigi. > Kod wynikowy - to jest konkret. :-) Tak, ale zwróciłem uwagę, że pewnie -O0 wygenerowałoby kod z instrukcją (I)DIV. Skoro tu jej nie ma, to pewnie było co najmniej -O1, co daje taki kod wymagający głębszej analizy. [...] > W ogóle taką "masówkę" powinno się załatwiać w wątku niezależnym od GUI, a już najlepiej temat zrównoleglić. > Tylko jak zrównoleglić sekwencyjny odczyt z pliku? > Można by stanąć gdzieś w środku bufora, złapać na szybko koniec linii i puścić drugi wątek parsujący. > Jeszcze tylko trzeba upchać jakoś dane z dwóch wątków, wiedząc, że wyszukiwać w nich będzie funkcja hashująca... > > Choroba, samo pisanie na tej grupie jest inspirujące! :-) :) Odczytu z pliku nie zrównoleglaj, bo cały zysk może zostać przejedzony przez ciągłe uruchamianie fseek(). Załaduj cały plik do pamięci, a dalej - jak piszesz: znaleźć "gdzieś w środku" separator, i jeden wątek puszczasz na pierwszej części bufora, a drugi - na drugiej. >> Jeśli kompilujesz C z optymalizacjami, to wygrać z kompilatorem w >> tych czasach może być ciężko. > > > Taki kod w C: > > > QueryPerformanceCounter(&StartCounter); > > *cptr = 0x0; > while (iValue) > { > *--cptr = char(iValue % 10) | '0'; > iValue /= 10; > } > > QueryPerformanceCounter(&EndCounter); > > > wypluty przez MinGW do kodu asemblera wygląda tak: > > > > call _QueryPerformanceCounter@4 > subl $4, %esp > movl $2137483647, %ecx > leal 143(%esp), %ebx > leal 133(%esp), %edi > movb $0, 143(%esp) > .p2align 4,,10 > L2: > movl %ecx, %eax > subl $1, %ebx > imull %esi > movl %ecx, %eax > sarl $31, %eax > sarl $2, %edx > subl %eax, %edx > leal (%edx,%edx,4), %eax > addl %eax, %eax > subl %eax, %ecx > orl $48, %ecx > cmpl %edi, %ebx > movb %cl, (%ebx) > movl %edx, %ecx > jne L2 > leal 72(%esp), %eax > movl %eax, (%esp) > call _QueryPerformanceCounter@4 > subl $4, %esp > > > Zakładam, że cała akcja dzieje się pomiędzy dwoma wywołaniami QueryPerformanceCounter. Tak, poza "subl $4, %esp", rzecz jasna (usuwanie argumentów ze stosu). > Da się tu coś istotnego wywalić? > Na moje oko (skłute składnią AT&T :D) kompilator wyłapał i uwzględnił sąsiadujące modulo i dzielenie przez 10. > A wtedy - nic tu się nie urwie (ponad poziom szumu). No, można byłoby przestać używać pamięci, a wziąć jakiś nieużywany rejestr, np. EBP. Kod w poprzednich linkach wyglądał na lepszy, więc coś tam się poprawić da. Pierwszy SAR ustawia cały rejestr na 0 lub FFFFFFFF, pewnie można byłoby to też jakoś przerobić. Ale to kwestia doboru rozwiązania. Może to będzie szybsze w zadanych parametrach. Jakie były parametry -O, -mtune i -march? Z innymi jest inny kod? A tak poza tym na oko to nie za wiele da się urwać. Jak pisałem, gdy włączasz optymalizacje, wygrać z kompilatorem w tych czasach może być ciężko. > Czym się kurde różni SAR od SHR? SAR to dzielenie arytmetyczne, czyli takie "po ludzku" z zachowaniem znaku (bit znaku zostaje na miejscu i to on jest kopiowany w prawo, a nie zera są pompowane od lewej, jak w zwykłym SHR). SHR to proste przesuwanie bitów. Weźmy sobie liczbę 80h (0x80). Bitowo jest to 1000 0000. Jeśli traktujesz ją jako liczbę bez znaku, to ta wartość odpowiada liczbie +128. Jeśli jako liczbę ze znakiem, wynosi to -128. I teraz jedziemy: 1000 0000 SAR 1 = 1100 0000 (-128 / 2 = -64) 1100 0000 SAR 1 = 1110 0000 (-64 / 2 = -32) .... 1111 1110 SAR 1 = 1111 1111 (-2 / 2 = -1) Wygląda logicznie. Z kolei, gdybyś użył zwykłego SHR, to -128 / 2 +64. Już trochę mniej sensownie. A jeśli traktujesz to jako liczbę bez znaku: 1000 0000 SHR 1 = 0100 0000 (+128 / 2 = +64) 0100 0000 SHR 1 = 0010 0000 (+64 / 2 = +32) Sensownie. A gdyby traktować to jako liczbę ze znakiem, to +128 / 2 192... Także, 80h to po prostu 80h, ale jak chcesz, aby działało z sensem, musisz określić, co jest tym sensem - czy ta liczba ma być traktowana jako ze znakiem (czyli od -2^(n-1) do +2^(n-1)-1, a najstarszy bit mówi o znaku +/-), czy po prostu od 0 do +2^n-1. -- Pozdrawiam/Regards - Bogdan (GNU/Linux & FreeDOS) Kurs asemblera x86 (DOS, GNU/Linux): http://bogdro.evai.pl Grupy dyskusyjne o asm: pl.comp.lang.asm alt.pl.asm alt.pl.asm.win32 www.Xiph.org www.TorProject.org Soft(EN): http://bogdro.evai.pl/soft
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: "Bogdan (bogdro)
Date: Tue, 12 Feb 2019 21:29
Date: Tue, 12 Feb 2019 21:29
24 lines
1067 bytes
1067 bytes
W dniu 12.02.2019 o 17:04, DMR pisze: > A tak przy okazji I/O - dzisiaj gadam do smartfona, a on wszystko rozumie, Apple ma Siri... I to wszystko na jakimś tam ARM. > > A tu człowiek się boksuje z jakimś spritf... > Mało tego, temat wcale nie jest zamknięty: > > https://dl.acm.org/citation.cfm?id192369 Ciekawe, ciekawe. Cóż, tak, wymyślane są coraz to nowe algorytmy. No i nie od razu Siri zbudowano ;). > Faktem jest, że jak "głupi" smartfon coś źle zrozumie, to można mu wyperswadować, tak czy siak, ale jak output "niechcący" poprzestawia cyferki, to może się skończyć katastrofą... ;-) Ta, prędkość i droga hamowania samochodu, dawka leku, ciśnienie krwi, kwota przelewu, tak, "ciekawych" miejsc na popełnienie błędu jest wiele... ;) -- Pozdrawiam/Regards - Bogdan (GNU/Linux & FreeDOS) Kurs asemblera x86 (DOS, GNU/Linux): http://bogdro.evai.pl Grupy dyskusyjne o asm: pl.comp.lang.asm alt.pl.asm alt.pl.asm.win32 www.Xiph.org www.TorProject.org Soft(EN): http://bogdro.evai.pl/soft
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: DMR
Date: Wed, 13 Feb 2019 10:12
Date: Wed, 13 Feb 2019 10:12
65 lines
1838 bytes
1838 bytes
Poleciałem trochę po łebkach, a zapomnialem, że taki bufor z danymi można sobie łatwo sprokurować: //------------------------------------------------------------------------------ #include <stdio.h> #include <stdlib.h> //------------------------------------------------------------------------------ int main() { char *cBuffer; char *endptr; int nSize, nIterations, i; volatile double fValue; nSize = 8000000; nIterations = 1000; cBuffer = (char*)malloc(nSize + 1); cBuffer[nSize] = '\0'; // robimy sobie dane, zero na końcu sprintf(cBuffer, "%0.10f", 3.14159265358979); // Pi do 10 cyfr for (i = 12; i < nSize; i++) // osiem mega 'A'... ;-) cBuffer[i] = 'A'; // cBuffer[12] = '\0'; for (i = 0; i < nIterations; i++) // no i jazda... fValue = strtod(cBuffer, &endptr); printf("%0.10f", fValue); free(cBuffer); return 0; } //------------------------------------------------------------------------------ Wyłączenie/włączenie linijki przed pętlą zmienia czas wykonania z milisekund na długie sekundy... > Ciekawe, ciekawe. Cóż, tak, wymyślane są coraz to nowe algorytmy. Widzę, że chłopaki twardo trzymają się arytmetyki całkowitoliczbowej. To może mięć sens, ale wcale nie musi - przedzieranie się przez gąszcz tych instrukcji może być w sumie bardziej złożone, niż dwa szybkie strzały na FPU. W końcu po coś on jest... ;-) Bo tymczasem na świecie dzieją się takie cuda: https://godbolt.org/z/OvePjK Trzeba będzie się nad tematem pochylić, może jeszcze przed emeryturą... ;-)
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: "Bogdan (bogdro)
Date: Wed, 13 Feb 2019 20:38
Date: Wed, 13 Feb 2019 20:38
60 lines
1666 bytes
1666 bytes
W dniu 12.02.2019 o 21:23, Bogdan (bogdro) pisze: [...] >> call _QueryPerformanceCounter@4 >> subl $4, %esp >> movl $2137483647, %ecx >> leal 143(%esp), %ebx >> leal 133(%esp), %edi >> movb $0, 143(%esp) >> .p2align 4,,10 >> L2: >> movl %ecx, %eax >> subl $1, %ebx >> imull %esi >> movl %ecx, %eax >> sarl $31, %eax >> sarl $2, %edx >> subl %eax, %edx >> leal (%edx,%edx,4), %eax >> addl %eax, %eax >> subl %eax, %ecx >> orl $48, %ecx >> cmpl %edi, %ebx >> movb %cl, (%ebx) >> movl %edx, %ecx >> jne L2 >> leal 72(%esp), %eax >> movl %eax, (%esp) >> call _QueryPerformanceCounter@4 >> subl $4, %esp >> >> >> Zakładam, że cała akcja dzieje się pomiędzy dwoma wywołaniami QueryPerformanceCounter. > > > Tak, poza "subl $4, %esp", rzecz jasna (usuwanie argumentów ze stosu). > > >> Da się tu coś istotnego wywalić? >> Na moje oko (skłute składnią AT&T :D) kompilator wyłapał i uwzględnił sąsiadujące modulo i dzielenie przez 10. >> A wtedy - nic tu się nie urwie (ponad poziom szumu). > > > No, można byłoby przestać używać pamięci, a wziąć jakiś nieużywany > rejestr, np. EBP. Autokorekta: za bardzo się przykleiłem do poprzedniej funkcji, wyliczającej tylko modulo, a ta funkcja przerabia liczbę na znaki. Oczywiście jedno odniesienie do pamięci jest tu potrzebne - do miejsca, gdzie będzie wynik. [...] -- Pozdrawiam/Regards - Bogdan (GNU/Linux & FreeDOS) Kurs asemblera x86 (DOS, GNU/Linux): http://bogdro.evai.pl Grupy dyskusyjne o asm: pl.comp.lang.asm alt.pl.asm alt.pl.asm.win32 www.Xiph.org www.TorProject.org Soft(EN): http://bogdro.evai.pl/soft
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: DMR
Date: Sun, 17 Feb 2019 12:17
Date: Sun, 17 Feb 2019 12:17
89 lines
3267 bytes
3267 bytes
> Także u mnie nie obserwuję "efektu sekund". Pewnie problem z glibc > (tak, zdarzają się takie). Z tego co doczytałem, pod Windowsem te funkcje obsługuje biblioteka MSVC Runtime - i pewnie tam są bugi... I dobrze, bo była okazja do rozkminy tematu. :-) W ogóle chętnie przesiadłbym się na Linuksa, ale trzyma mnie CAD i Excel, do którego mam cały worek makr. A już najlepiej, żeby był z podpiętym CDE - tyle, że chodziłby na nim co najwyżej soft, który bym sobie sam napisał... :-P > zrobić swoje funkcje, lepiej pasujące do danych Popełniłem wstępnie funkcję konwertującą double na stringa. Dzieli liczbę na część rzeczywistą i ułamkową, a potem wyświetla do czterech miejsc po przecinku w ustawi buforze, ale narazie nie chodzi o finezję, tylko o przetestowanie rozwiązania: //------------------------------------------------------------------------------ inline void DoubleToString(double dValue, char* cBuffer) { double dRnd; unsigned int iReal, val; char *ptr, *dot; ptr = cBuffer + 15; dot = cBuffer + 10; *ptr = '\0'; *dot = '.'; dRnd = dValue + 0.00005; // zaokrąglenie czwartej cyfry iReal = (int)dRnd; val = (int)((dRnd - iReal) * 10000.0); // część ułamkowa *) while (val) { *--ptr = char(val % 10) | '0'; val /= 10; } while (--ptr > dot) // zera do przecinka *ptr = '0'; val = iReal; // część rzeczywista while (val) { *--ptr = char(val % 10) | '0'; val /= 10; } while (ptr > cBuffer) // spacje do początku *--ptr = ' '; } //------------------------------------------------------------------------------ W porównaniu do sprintf() wykonuje swoją pracę... piętnastokrotnie szybciej, więc tu jest jak najbardziej git! :-) Od zwycięzcy: https://github.com/miloyip/dtoa-benchmark jest ponad trzykrotnie szybsza - nic zresztą dziwnego, bo tamten algorytm używa int-ów 64-bitowych, co w programie 32-bitowym musi kosztować. Boję się tylko o jedno - że w linijce *) w jakimś perfidnym przypadku (liczby praktycznie całkowitej) pojawią się mikroskopijne błędy zaokrągleń, które jednak spowodują "przekręcenie" części ułamkowej względem rzeczywistej i zamiast 10.0000 wyjdzie 10.9999, czy jakoś tak. Póki co katuję algorytm paczkami po kilkaset milionów liczb wygenerowanych według rozmaitych kluczy i porównuję wynik ze sprintf() - do tej pory NIGDY nie wywalił błędu (jedyne co kilka razy wyłapał, to różnicę na czwartym miejscu po przecinku dla liczb XXXXXX.XXXX5000...). Ale że tu nie ma miejsca na żadne tego typu wypadki, to się boję... ;-) No i znowu - miało być krótko... ;-) Jest fajna instrukcja FISTTP, ale dopiero od późnego Pentium 4.
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: "Bogdan (bogdro)
Date: Sun, 17 Feb 2019 16:24
Date: Sun, 17 Feb 2019 16:24
177 lines
3520 bytes
3520 bytes
W dniu 13.02.2019 o 19:12, DMR pisze: > Poleciałem trochę po łebkach, a zapomnialem, że taki bufor z danymi można sobie łatwo sprokurować: > > > //------------------------------------------------------------------------------ > #include <stdio.h> > #include <stdlib.h> > //------------------------------------------------------------------------------ > int main() > { > char *cBuffer; > char *endptr; > int nSize, nIterations, i; > volatile double fValue; > > nSize = 8000000; > nIterations = 1000; > > > cBuffer = (char*)malloc(nSize + 1); > > cBuffer[nSize] = '\0'; // robimy sobie dane, zero na końcu > sprintf(cBuffer, "%0.10f", 3.14159265358979); // Pi do 10 cyfr > for (i = 12; i < nSize; i++) // osiem mega 'A'... ;-) > cBuffer[i] = 'A'; > > // cBuffer[12] = '\0'; > > for (i = 0; i < nIterations; i++) // no i jazda... > fValue = strtod(cBuffer, &endptr); > > printf("%0.10f", fValue); > > > free(cBuffer); > > > return 0; > } > //------------------------------------------------------------------------------ > > > Wyłączenie/włączenie linijki przed pętlą zmienia czas wykonania z milisekund na długie sekundy... [...] Z ciekawości sprawdziłem. Linijka zakomentowana (czyli parsuje cały bufor), bez -O: $ time ./strtodtest 3.1415926536 real 0m0,032s user 0m0,028s sys 0m0,004s $ time ./strtodtest 3.1415926536 real 0m0,033s user 0m0,025s sys 0m0,008s $ time ./strtodtest 3.1415926536 real 0m0,033s user 0m0,029s sys 0m0,004s Linijka zakomentowana, -O1: $ time ./strtodtest 3.1415926536 real 0m0,010s user 0m0,003s sys 0m0,006s $ time ./strtodtest 3.1415926536 real 0m0,010s user 0m0,006s sys 0m0,004s $ time ./strtodtest 3.1415926536 real 0m0,009s user 0m0,002s sys 0m0,007s Linijka zakomentowana, -O2: $ time ./strtodtest 3.1415926536 real 0m0,009s user 0m0,002s sys 0m0,007s $ time ./strtodtest 3.1415926536 real 0m0,009s user 0m0,006s sys 0m0,003s $ time ./strtodtest 3.1415926536 real 0m0,009s user 0m0,004s sys 0m0,005s Linijka zakomentowana, -O3: $ time ./strtodtest 3.1415926536 real 0m0,006s user 0m0,001s sys 0m0,005s $ time ./strtodtest 3.1415926536 real 0m0,006s user 0m0,001s sys 0m0,005s $ time ./strtodtest 3.1415926536 real 0m0,006s user 0m0,001s sys 0m0,005s Linijka odkomentowana, -O3: $ time ./strtodtest 3.1415926536 real 0m0,007s user 0m0,001s sys 0m0,005s $ time ./strtodtest 3.1415926536 real 0m0,007s user 0m0,000s sys 0m0,007s $ time ./strtodtest 3.1415926536 real 0m0,007s user 0m0,003s sys 0m0,004s Także u mnie nie obserwuję "efektu sekund". Pewnie problem z glibc (tak, zdarzają się takie). $ gcc --version gcc (GCC) 7.4.0 20181206 (OpenMandriva) Copyright (C) 2017 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ rpm -q glibc glibc-2.27-9-omv2015.0.x86_64 $ uname -a Linux syriusz 4.16.13-desktop-1omv #1 SMP Wed May 30 21:45:03 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux -- Pozdrawiam/Regards - Bogdan (GNU/Linux & FreeDOS) Kurs asemblera x86 (DOS, GNU/Linux): http://bogdro.evai.pl Grupy dyskusyjne o asm: pl.comp.lang.asm alt.pl.asm alt.pl.asm.win32 www.Xiph.org www.TorProject.org Soft(EN): http://bogdro.evai.pl/soft
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: "Bogdan (bogdro)
Date: Tue, 19 Feb 2019 19:58
Date: Tue, 19 Feb 2019 19:58
59 lines
2095 bytes
2095 bytes
W dniu 17.02.2019 o 21:17, DMR pisze: [...] > inline void DoubleToString(double dValue, char* cBuffer) > { > double dRnd; > unsigned int iReal, val; > char *ptr, *dot; > > ptr = cBuffer + 15; > dot = cBuffer + 10; > *ptr = '\0'; > *dot = '.'; > > dRnd = dValue + 0.00005; // zaokrąglenie czwartej cyfry > iReal = (int)dRnd; > > val = (int)((dRnd - iReal) * 10000.0); // część ułamkowa *) > while (val) > { > *--ptr = char(val % 10) | '0'; > val /= 10; > } [...] > Boję się tylko o jedno - że w linijce *) w jakimś perfidnym przypadku (liczby praktycznie całkowitej) pojawią się mikroskopijne błędy zaokrągleń, które jednak spowodują "przekręcenie" części ułamkowej względem rzeczywistej i zamiast 10.0000 wyjdzie 10.9999, czy jakoś tak. Fakt, trochę bym się bał wyświetlać liczbę, którą sam zmieniłem w kodzie jako tą, którą dostałem. A że lubię znajdować przypadki wyjątkowe, niech dValue = 1,999999 (pomijając fakt, czy da się taką liczbę dokładnie reprezentować binarnie, pewnie dowolna bliska liczba też wystarczy). Wtedy, jeśli dobrze liczę: - dRnd = dValue + 0.00005 = 2,000049 - iReal = (int)dRnd = 2 - dRnd - iReal = 0,000049 - (dRnd - iReal) * 10000.0 = 4,9 - val = (int)((dRnd - iReal) * 10000.0) = 4 - wyświetlasz "4" i dopełniasz zerami, - wyświetlasz iReal (2) - dostałeś 1,999999 jako parametr, wyświetliłeś 2,00004 :) Pewnie da się coś lepszego wymyślić, ale na szybko: iReal = ((int)(dValue * 100000 + 5)) / 100000; // część całkowita val = ((dValue - (int)dValue) * 100000 + 5) / 10; W części ułamkowej mnożymy przez sto (a nie dziesięć) tysięcy, dodajemy 5, aby zaokrąglić, i dzielimy przez 10, aby dostać 4 cyfry "zza przecinka" jako liczbę całkowitą. -- Pozdrawiam/Regards - Bogdan (GNU/Linux & FreeDOS) Kurs asemblera x86 (DOS, GNU/Linux): http://bogdro.evai.pl Grupy dyskusyjne o asm: pl.comp.lang.asm alt.pl.asm alt.pl.asm.win32 www.Xiph.org www.TorProject.org Soft(EN): http://bogdro.evai.pl/soft
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: DMR
Date: Wed, 20 Feb 2019 00:53
Date: Wed, 20 Feb 2019 00:53
98 lines
4250 bytes
4250 bytes
> - dostałeś 1,999999 jako parametr, wyświetliłeś 2,00004 :) Na początku dodałem 0.00005, więc niby co innego miałbym otrzymać? ;-) A dodałem tę piątkę na piątym, żeby sobie po "urżnięciu" int-em uporządkować CZTERY miejsca po przecinku. Cztery, bo akurat tak chcę, gdybym chciał pięciu, dodałbym 0.000005 itd. Ale nie w tym problem... > jeśli dobrze liczę Źle i do tego tendencyjnie... ;-) > - dRnd - iReal = 0,000049 > - (dRnd - iReal) * 10000.0 = 4,9 (dRnd - iReal) * 10000.0 = 0.49 val = (int)((dRnd - iReal) * 10000.0) = 0 Wyświetlam iReal i "dopełniam" czterema zerami. Czyli ostatecznie wyszła konwersja: 1.99999 -> 2.0000 Tu wszystko gra i koliduje. :-) > (pomijając fakt, czy da się taką liczbę dokładnie > reprezentować binarnie, pewnie dowolna bliska liczba > też wystarczy). O, to, to właśnie! Boję się, że jakiś przestawiony bit gdzieś na n-tym miejscu rozwali mi cały sens wyliczenia ułamkowej "reszty": F = (dRnd - iReal) * 10000.0 To znaczy - już się tak nie boję, bo mnie to gadulstwo naprawdę inspiruje. ;-) Najłatwiej byłoby przejść na 64 bity - tam int ma "pojemność" 18 cyfr, więc obsłużyłby doubla w pełnym zakresie dokładności - wystarczyłoby go wymnożyć do wszystkich cyfr przed przecinkiem, JEDNYM CIĘCIEM przerobić na int-a i zrobić "normalne" itoa, wstawiając tylko przecinek gdzie tam trzeba. Niestety, int 32-bitowy "mieści" tylko 9 cyfr, więc jeśli cyfr ma być więcej, to cięcia muszą być dwa - niezależne od siebie, i może to narobić kłopotów... W "standardowej" sytuacji ewentualne błędy zaokrągleń zmienią mi co najwyżej ostatnią cyfrę F, więc nie jest to żaden problem, tym bardziej, że konwertowana wartość dValue balansująca na granicy połówki obcinanej cyfry w pełni taki efekt rozgrzesza. Mniej ciekawie wygląda to dla wartości dValue w zakresie liczb całkowitych, gdzie teoretycznie dRnd = iReal, ale ze względu na różnice reprezentacji ta równość niekoniecznie musi zachodzić ściśle. Sama liczba 0.00005 przecież też nie ma ścisłej reprezentacji: http://www.binaryconvert.com/result_double.html?decimal=048046048048048048053 Jeżeli po "obcięciu" części całkowitej wartość reszty F będzie praktycznie równa zeru, to nie ma problemu - int ucina w kierunku zera, więc śladową "fluktuację" reszty F i tak dociągnie do zera, niezależnie od jej znaku. Gorzej dla liczb "prawie całkowitych" (typu 3714.9999999999999997...) gdzie int może prawidłowo obciąć część całkowitą 3714, ale reszta 0.9999999999999997... za sprawą jakiegoś zabłąkanego bitu przeskoczy na 1.0000000000000001... Optymistyczne jest to, że taki przypadek można łatwo wyłapać (dla czterech cyfr: val > 9999). A może znowu niepotrzebnie się martwię? Trochę się to wszystko komplikuje, więc może lepiej powrócić do "czystej" koncepcji "ręcznego" rozprawienia się z problemem: http://0x80.pl/articles/convert-float-to-integer.html Ale o ile w przypadku float-ów nie ma problemu, to w double mantysa ma długość 52 bitów, więc znowu - na 64 bitach można zastosować kalkę rozwiązania, natomiast przy 32 bitach problem się piętrzy - tzn. na pewno jest rozwiązywalny, tylko wcale nie jest pewne, że te obejścia nie przegrają już na starcie z "natywnym" zachowaniem koprocesora. Wypadałoby chyba zmienić tytuł wątku... ;-)
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: DMR
Date: Thu, 06 Jun 2019 02:27
Date: Thu, 06 Jun 2019 02:27
108 lines
2651 bytes
2651 bytes
W wolnej chwili zmajstrowałem sobie prostą "biblioteczkę" z podstawowymi funkcjami matematycznymi. Posłużyłem się przy tym "gotowcami" zamieszczonymi na stronie: https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html Sqrt liczy się tylko symbolicznie szybciej niż sqrt z biblioteki math.h, pozostałe funkcje są szybsze od 2x wzwyż. Za Chiny nie mogę rozkminić tych literek między dwukropkami, ale wprowadziłem pewne korekty, per analogiam - i (chyba) działa.... ;-) W przypadku "jednoargumentowych" wyrażeń sugerowana jest składnia: asm ("foo" : "=&t" (a) : "f" (b)); Przy okazji testów szybkości zauważyłem jednak, że jeśli w ten sposób zakoduję funkcje Sin() i Cos(), to w przypadku wywołania w kodzie programu: sin = Sin(a); cos = Cos(a); poprawnie liczy się kosinus, ale w przypadku sinusa wychodzą jakieś głupoty. Wywołanie typu: tan = Sin(a) / Cos(a) w ogóle się wywala, a przecież mam tego używać... Poprzez analogię do przykładu z fsincos i fyl2xp1 do zmieniłem "f" na "0" i wywaliłem ampersanda sprzed t: asm ("foo" : "=t" (a) : "0" (b)); No i teraz wszystko gra! A dlaczego? Nie mam pojęcia... ;-) //------------------------------------------------------------------------------ inline double Sqrt(double x) { double s; asm ("fsqrt" : "=t" (s) : "0" (x)); return s; } //------------------------------------------------------------------------------ inline double Sin(double a) { double s; asm ("fsin" : "=t" (s) : "0" (a)); return s; } //------------------------------------------------------------------------------ inline double Cos(double a) { double c; asm ("fcos" : "=t" (c) : "0" (a)); return c; } //------------------------------------------------------------------------------ inline void SinCos(double a, double *sin, double *cos) { double s, c; asm ("fsincos" : "=t" (c), "=u" (s) : "0" (a)); *sin = s; *cos = c; } //------------------------------------------------------------------------------ inline double Tan(double a) { double t, o; asm ("fptan" : "=t" (o), "=u" (t) : "0" (a)); return t; } //------------------------------------------------------------------------------ inline double ATan(double y, double x) { double a; asm ("fpatan" : "=t" (a) : "0" (x), "u" (y) : "st(1)"); return a; } //------------------------------------------------------------------------------
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: "Bogdan (bogdro)
Date: Sat, 08 Jun 2019 23:26
Date: Sat, 08 Jun 2019 23:26
177 lines
5194 bytes
5194 bytes
W dniu 06.06.2019 o 11:27, DMR pisze: > W wolnej chwili zmajstrowałem sobie prostą "biblioteczkę" z podstawowymi funkcjami matematycznymi. Posłużyłem się przy tym "gotowcami" zamieszczonymi na stronie: https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html > > Sqrt liczy się tylko symbolicznie szybciej niż sqrt z biblioteki math.h, pozostałe funkcje są szybsze od 2x wzwyż. > > Za Chiny nie mogę rozkminić tych literek między dwukropkami, ale wprowadziłem pewne korekty, per analogiam - i (chyba) działa... ;-) Witamy. Jako źródła polecam: - http://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html - podręcznik do GCC, np. http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/, sekcje 5.34 i 5.35, czy też https://gcc.gnu.org/onlinedocs/gcc-9.1.0/gcc/Using-Assembly-Language-with-C.html#Using-Assembly-Language-with-C Generalnie składnia to asm ( "statements" : output_registers : input_registers : clobbered_registers); Clobber oznacza rejestry "przypadkowo" psute (jako efekt uboczny) przez dany blok czy instrukcję. Mistrzem w tym nie jestem, ale może coś zgadnę (składnia trochę "kaleczy oczy" ;) ). Także w nierozumieniu tej składni nie jesteś sam :). Zawsze możesz też przekopać pliki nagłówkowe glibc pod kątem występowania instrukcji koprocesora. Poszukałem po "fsqrt" i znalazłem /usr/include/bits/mathinline.h. > W przypadku "jednoargumentowych" wyrażeń sugerowana jest składnia: > > asm ("foo" : "=&t" (a) : "f" (b)); Wyjście = zmienna "a", early-clobber, pobranie z st(0) (https://gcc.gnu.org/onlinedocs/gcc-9.1.0/gcc/Machine-Constraints.html#Machine-Constraints). Wejście = zmienna "b", float (por. ta sama strona). > Przy okazji testów szybkości zauważyłem jednak, że jeśli w ten sposób zakoduję funkcje Sin() i Cos(), to w przypadku wywołania w kodzie programu: > > sin = Sin(a); > cos = Cos(a); > > poprawnie liczy się kosinus, ale w przypadku sinusa wychodzą jakieś głupoty. > Wywołanie typu: > > tan = Sin(a) / Cos(a) > > w ogóle się wywala, a przecież mam tego używać... > > Poprzez analogię do przykładu z fsincos i fyl2xp1 do zmieniłem "f" na "0" i wywaliłem ampersanda sprzed t: > > asm ("foo" : "=t" (a) : "0" (b)); > > > No i teraz wszystko gra! > > A dlaczego? Nie mam pojęcia... ;-) Early-clobber mogło być niepotrzebne. Wyjście jest zapisane do zmiennej "a" - OK. Co do "0" - cytat z https://gcc.gnu.org/onlinedocs/gcc-9.1.0/gcc/Extended-Asm.html#Extended-Asm: Input constraints can also be digits (for example, "0"). This indicates that the specified input must be in the same place as the output constraint at the (zero-based) index in the output constraint list. Czyli parametr ma być w tym samym miejscu, gdzie pierwszy parametr wyjściowy. W skrócie: wkładasz wartość do st(0) i wyciągasz też z st(0). To pewnie pomogło. Może asm ("foo" : "=t" (a) : "t" (b)); też by zadziałało. > //------------------------------------------------------------------------------ > inline double Sqrt(double x) > { > double s; > > asm ("fsqrt" : "=t" (s) : "0" (x)); > > return s; > } > //------------------------------------------------------------------------------ > inline double Sin(double a) > { > double s; > > asm ("fsin" : "=t" (s) : "0" (a)); > > return s; > } > //------------------------------------------------------------------------------ > inline double Cos(double a) > { > double c; > > asm ("fcos" : "=t" (c) : "0" (a)); > > return c; > } Trzy razy odczyt i zapis z st(0) - wygląda dobrze. > //------------------------------------------------------------------------------ > inline void SinCos(double a, double *sin, double *cos) > { > double s, c; > > asm ("fsincos" : "=t" (c), "=u" (s) : "0" (a)); > > *sin = s; > *cos = c; > } > //------------------------------------------------------------------------------ > inline double Tan(double a) > { > double t, o; > > asm ("fptan" : "=t" (o), "=u" (t) : "0" (a)); > > return t; > } Dwa razy parametr w st(0), a wynik z st(0) i st(1). W drugim przypadku odrzucamy st(0) (zawierające wartość 1). Tak się też zastanawiam, czy st(7) nie powinno być na clobber list, sądząc po opisie działania instrukcji. > //------------------------------------------------------------------------------ > inline double ATan(double y, double x) > { > double a; > > asm ("fpatan" : "=t" (a) : "0" (x), "u" (y) : "st(1)"); > > return a; > } > //------------------------------------------------------------------------------ > Parametry w st(0) - x - i w st(1) - y, a wynik z st(0) do zmiennej "a". Tutaj też się zastanawiam, czy to raczej st(7) nie powinno być na clobber list zamiast st(1). To działa w sensie składniowym? Clobber nie powinien mieć procenta - %st(1)? Czy może przełączyłeś na składnię intel? -- Pozdrawiam/Regards - Bogdan (GNU/Linux & FreeDOS) Kurs asemblera x86 (DOS, GNU/Linux): http://bogdro.evai.pl Grupy dyskusyjne o asm: pl.comp.lang.asm alt.pl.asm alt.pl.asm.win32 www.Xiph.org www.TorProject.org Soft(EN): http://bogdro.evai.pl/soft
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: DMR
Date: Mon, 10 Jun 2019 09:05
Date: Mon, 10 Jun 2019 09:05
37 lines
791 bytes
791 bytes
> Może > > asm ("foo" : "=t" (a) : "t" (b)); > > też by zadziałało. Próbowałem... ;-) Kompilator się pruje, że ma być "&" i koniec. > To działa w sensie składniowym? Jak najbardziej. Pod linkiem jest "bezalkoholowo". > Clobber nie powinien mieć procenta - %st(1)? > Czy może przełączyłeś na składnię intel? Ja już nawet nie wnikam... To ustrojstwo robi se co chce. :D > Mistrzem w tym nie jestem, ale może coś zgadnę (składnia trochę > "kaleczy oczy" ;) ). Także w nierozumieniu tej składni nie jesteś sam :) Czas chyba wprowadzić nową dyscyplinę - programowanie bez znajomości języka. :D
Re: [1/3 NTG] C - Optymalizacje wstawkami
Author: DMR
Date: Sun, 12 Jan 2020 09:27
Date: Sun, 12 Jan 2020 09:27
21 lines
1052 bytes
1052 bytes
Pół roku minęło, warto by coś napisać. :-) > Boję się, że jakiś przestawiony bit gdzieś na n-tym miejscu rozwali mi > cały sens wyliczenia ułamkowej "reszty": F = (dRnd - iReal) * 10000.0 A jeśli zamiast przez 10000.0 wymnożę resztę przez 9999..99, to z punktu widzenia FPU będzie to to samo, za to ja zabezpieczę się przed ewentualnym "przepełnieniem" ułamkowej reszty do/ponad 1.000000000000. Oczywiście, wszystko to kosztem zakwalifikowania liczb typu xxx.999951 jako xxx.9999, no ale, powiedzmy szczerze - Who cares... ;-) Źródłem "moralnego niepokoju" może być tu jedynie fakt usilnego dążenia do zabezpieczenia się przed wystąpieniem sytuacji, która z punktu widzenia logiki/techniki być może nie ma szans zaistnieć, tzn. - czy w jakimś skrajnie perfidnym przypadku może zajść: (Double - (int)Double) * 1Ex >= 1Ex ?
Thread Navigation
This is a paginated view of messages in the thread with full content displayed inline.
Messages are displayed in chronological order, with the original post highlighted in green.
Use pagination controls to navigate through all messages in large threads.
Back to All Threads