🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: pl.comp.lang.asm
Article #2591

Re: [1/3 NTG] C - Optymalizacje wstawkami

#2591
From: Bogdan (bogdro)
Date: Wed, 06 Feb 2019 20:21
181 lines
9442 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
Technical Details
Message-ID: <5c5b41d7$0$501$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-mail
References: <30292520-12bd-49f0-b5ce-39c9af378a4c@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>
From: "Bogdan (bogdro)" <bogdan@poczta.gazeta.pl>
Openpgp: preference=signencrypt
Date: Wed, 6 Feb 2019 21:21:43 +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: <30292520-12bd-49f0-b5ce-39c9af378a4c@googlegroups.com>
Lines: 181
Message-ID: <5c5b41d7$0$501$65785112@news.neostrada.pl>
Organization: Telekomunikacja Polska
NNTP-Posting-Host: 89.64.39.243
X-Trace: 1549484504 unt-rea-b-01.news.neostrada.pl 501 89.64.39.243:61814
X-Complaints-To: abuse@news.neostrada.pl
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit