2014-09-26 12 views
17

Oto mój kod:Czy GCC może zoptymalizować isnan (x) || isnan (y) do isunordered (x, y)?

int f(double x, double y) 
{ 
    return std::isnan(x) || std::isnan(y); 
} 

Jeśli używasz C zamiast C++, wystarczy zastąpić std:: z __builtin_ (nie po prostu usunąć std::, ze względów przedstawionych tutaj: Why does GCC implement isnan() more efficiently for C++ <cmath> than C <math.h>?).

Oto montaż:

ucomisd %xmm0, %xmm0 ; set parity flag if x is NAN 
setp %dl   ; copy parity flag to %edx 
ucomisd %xmm1, %xmm1 ; set parity flag if y is NAN 
setp %al   ; copy parity flag to %eax 
orl  %edx, %eax ; OR one byte of each result into a full-width register 

Teraz spróbujmy alternatywny preparat, który robi to samo:

int f(double x, double y) 
{ 
    return std::isunordered(x, y); 
} 

Oto zespół do alternatywy:

xorl %eax, %eax 
ucomisd %xmm1, %xmm0 
setp %al 

Jest świetnie - przecinamy wygenerowany kod prawie na pół! Działa to ponieważustawia flagę parzystości, jeśli albo jej operandów jest NAN, więc możemy przetestować dwie wartości naraz, w stylu SIMD.

Można zobaczyć kod jak w oryginalnej wersji w środowisku naturalnym, na przykład: https://svn.r-project.org/R/trunk/src/nmath/qnorm.c

Jeśli moglibyśmy GCC wystarczająco inteligentny, aby połączyć dwie isnan() połączeń wszędzie, co byłoby całkiem fajne. Moje pytanie brzmi: czy możemy i jak? Mam pewne pojęcie o działaniu kompilatorów, ale nie wiem, gdzie w GCC można by przeprowadzić taką optymalizację. Podstawową ideą jest to, że kiedy wywoływana jest para połączeń isnan() (lub __builtin_isnan), powinna ona emitować pojedynczą instrukcję ucomisd przy użyciu dwóch operandów w tym samym czasie.

Edited by dodać jakieś badania poproszony o odpowiedź Basile Starynkevitch za:

Jeśli mogę skompilować z -fdump drzewa-all, I znajdują się dwa pliki, które wydają się istotne. Po pierwsze, *.gimple ten zawiera (i nieco więcej):

D.2229 = x unord x; 
D.2230 = y unord y; 
D.2231 = D.2229 | D.2230; 

Tutaj widzimy wyraźnie, że GCC wie to minie (x, x) do isunordered(). Jeśli chcemy zoptymalizować, przekształcając na tym poziomie, reguła będzie mniej więcej: "Zamień a unord a | b unord b na a unord b." To jest to, co masz przy kompilacji mój drugi kod C:

D.2229 = x unord y; 

Inną ciekawą pliku jest *.original:

return <retval> = (int) (x unord x || y unord y); 

to rzeczywiście cały plik bez komentarza generowane przez -fdump-tree-original. I dla lepszego kodu źródłowego wygląda to tak:

return <retval> = x unord y; 

Oczywiście ten sam rodzaj transformacji mogą być stosowane (tylko tu jest || zamiast |).

Niestety, jeśli zmodyfikujemy kod źródłowy na przykład,:

if (__builtin_isnan(x)) 
    return true; 
if (__builtin_isnan(y)) 
    return true; 
return false; 

Następnie otrzymujemy całkiem różne pliki wyjściowe Gimple i Original, chociaż ostateczny montaż jest taki sam jak poprzednio. Więc może lepiej próbować tej transformacji na późniejszym etapie procesu? Plik *.optimized (między innymi) pokazuje ten sam kod dla wersji z "if" jak dla wersji oryginalnej, więc jest to obiecujące.

+1

Oczywiście to * możliwe * - ale to nie znaczy, że jest pożądane ze względu na dodany złożoność, narzut, kod w celu utrzymania Częstotliwość optymalizacji zostanie wykorzystana itp. W każdym razie sugerowanie jej *** dla programistów GCC *** jest z pewnością kolejnym krokiem, aby ją rozważyć, a nie publikować tutaj. –

+1

@TonyD: Jeśli znasz twórcę GCC, który jest chętny i zdolny i ma czas na jego wdrożenie, absolutnie proszę przekazać to do niego lub podać mi swój adres e-mail, a zrobię to. W przeciwnym razie pytanie dotyczy tego, czy mogę zrobić to sam, bez nadmiernego wysiłku (jestem świadomy, że krzywa uczenia się takich rzeczy jest bardzo stroma). Jest już jedna na temat, przydatna tutaj zamieszczona odpowiedź, która nauczyła mnie czegoś, czego bym nie nauczył się przez zwykłe zgłoszenie tego jako błędu GCC. –

+2

W gcc-5 może to być tak proste jak '(uprość (lub (nieakonfigurowane @ 0 0) (nieuporządkowane @ 1 @ 1)) (nieuporządkowane @ 0 @ 1))' w jednym z plików .pd (również , prawdopodobnie nie dla ostatniej wersji z 'if'). Proszę złożyć PR. –

Odpowiedz

3

Istnieją dwa pytania:

  • jest optymalizacja jesteś proponując zawsze prawne w ścisłym standardu C++ 11 (nie wiem).

  • można dostosować GCC, dodając taką optymalizację: tak! Możesz go rozszerzyć, używając MELT-e. napisz własne rozszerzenie MELT robiąc to - lub z własną wtyczką GCC zakodowaną (boleśnie) w C++.

Jednak dodanie dodatkowej optymalizacji w GCC jest znacząca praca (nawet z wsadem): trzeba zrozumieć wnętrzności GCC. Prawdopodobnie jest to więcej niż tydzień pracy.

I nie jestem pewien, czy taka optymalizacja naprawdę jest warta wysiłku.

+0

Dzięki, widzę, że jesteś/autor MELT. Będzie to wyraźnie wymagało wykonania, zanim będę mógł to zrozumieć, ale uruchomiłem 'gcc -fdump-tree-all' i zredagowałem niektóre z moich ustaleń na pytanie. –

+0

Cóż, próbowałem budować MELT 1.0 (do użytku z GCC 4.7), ale sprawiło mi to wiele problemów. Najpierw potrzebowałem nowszego 'unifdef' niż mój system (mój nie obsługiwał' -o', który prawdopodobnie mógłbyś pokonać w swoim systemie kompilacji). Następnie miał niezdefiniowane odniesienie w stopieniu.to do czegoś w libgmp, więc dodałem to do Makefile. następnie narzekało na brakujące 'meltbuild-stage0-fastbuilt/warmelt-first + meltdesc.c', więc zrobiłem czysty i teraz dostaję' melt-runtime.h: 675: 24: fatal error: meltrunsup.h: Too many levels symbolicznych ogniw ". –

+0

Próbowałem także budować MELT 1.1 z GCC 4.9. Musiałem dodać '-lrt' do linii łącza dla' melt.so' (w przeciwnym razie undef-ref do 'clock_gettime'), a następnie dostałem ten sam błąd dotyczący' warmelt-first + meltdesc.c'. Następnie dostałem błąd, ponieważ polegasz na 'pstree -s', a mój system' pstree' nie ma tej opcji. Używam Ubuntu 10.04. Przepraszam, stary system, nie mogę nic z tym zrobić. Moim innym systemem jest Mac i który używa Clanga. :( –

Powiązane problemy