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.
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. –
@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. –
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. –