Article View: pl.comp.lang.asm
Article #2593Re: [1/3 NTG] C - Optymalizacje wstawkami
From: Bogdan (bogdro)
Date: Fri, 08 Feb 2019 21:27
Date: Fri, 08 Feb 2019 21:27
163 lines
8668 bytes
8668 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
Technical Details
Message-ID:
<5c5df439$0$476$65785112@news.neostrada.pl>Path:
mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.icm.edu.pl!newsfeed.pionier.net.pl!goblin1!goblin.stu.neva.ru!newsfeed.neostrada.pl!unt-exc-02.news.neostrada.pl!unt-spo-a-01.news.neostrada.pl!news.neostrada.pl.POSTED!not-for-mailReferences:
<30292520-12bd-49f0-b5ce-39c9af378a4c@googlegroups.com> <5c5b41d7$0$501$65785112@news.neostrada.pl> <781a3ad8-10bb-42aa-939f-dc6c11efa783@googlegroups.com>Show Full Headers
Complete Article Headers
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.icm.edu.pl!newsfeed.pionier.net.pl!goblin1!goblin.stu.neva.ru!newsfeed.neostrada.pl!unt-exc-02.news.neostrada.pl!unt-spo-a-01.news.neostrada.pl!news.neostrada.pl.POSTED!not-for-mail Subject: Re: [1/3 NTG] C - Optymalizacje wstawkami Newsgroups: pl.comp.lang.asm References: <30292520-12bd-49f0-b5ce-39c9af378a4c@googlegroups.com> <5c5b41d7$0$501$65785112@news.neostrada.pl> <781a3ad8-10bb-42aa-939f-dc6c11efa783@googlegroups.com> From: "Bogdan (bogdro)" <bogdan@poczta.gazeta.pl> Openpgp: preference=signencrypt Date: Fri, 8 Feb 2019 22:27:20 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.5.0 MIME-Version: 1.0 In-Reply-To: <781a3ad8-10bb-42aa-939f-dc6c11efa783@googlegroups.com> Lines: 163 Message-ID: <5c5df439$0$476$65785112@news.neostrada.pl> Organization: Telekomunikacja Polska NNTP-Posting-Host: 89.64.39.243 X-Trace: 1549661241 unt-rea-a-01.news.neostrada.pl 476 89.64.39.243:61822 X-Complaints-To: abuse@news.neostrada.pl Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit