2016-04-05 41 views
5

Pracuję nad technikami optymalizacji wykonywanymi przez .NET Native Compiler. I utworzeniu pętli próbki:Dlaczego .Net Native kompiluje pętlę w odwrotnej kolejności?

 for (int i = 0; i < 100; i++) 
     { 
      Function(); 
     } 

I została skompilowana go z rodzimymi. Następnie zdemontowałem plik wyniku .dll z kodem maszynowym wewnątrz IDA. W rezultacie mam:

enter image description here

(Usunąłem kilka niepotrzebnych linii, więc nie martw się, że linie są inconsistant adres)

Rozumiem, że add esi, 0FFFFFFFFh oznacza naprawdę subtract one from esi and alter Zero Flag if needed, więc możemy przeskoczyć na początek, jeśli jeszcze nie osiągnięto zera.

Czego nie rozumiem, dlaczego kompilator powrócił do pętli?

doszedłem do wniosku, że

LOOP: 
add esi, 0FFFFFFFFh 
jnz LOOP 

jest po prostu szybsze niż na przykład

LOOP: 
inc esi 
cmp esi, 064h 
jl LOOP 

Ale to jest naprawdę z tego powodu i jest naprawdę znacząca różnica prędkości?

+0

ADD o wartości bezpośredniej jest szybsze niż INC i pomijane jest CMP ... wszystkie z nich w 3 wierszach kodu. Wtedy tak, różnica jest NAPRAWDĘ znacząca (zarówno pod względem wielkości, jak i prędkości). Wyobraź sobie, że możesz to zrobić w ~ 30000 miejscach w rzeczywistym programie ... –

+0

Tak, jest to szybsze, a generalnie optymalizatorzy zastosują dowolną optymalizację, dzięki której twój kod będzie szybszy bez zmiany semantyki twojego programu. –

+0

Jeśli chodzi o odwrócony kierunek, to być może porównanie do zera jest szybsze niż porównanie z określoną wartością? – user5226582

Odpowiedz

3

inc might be slower than add because of the partial flag update. Ponadto add wpływa na flagę zero, więc nie musisz używać innej instrukcji cmp. Po prostu skocz bezpośrednio.

Jest to jeden znanym rodzajem loop optimization

odwrócenia: odwrócenie pętli odwraca kolejność, w którym wartości są przypisane do zmiennej indeksu. Jest to subtelna optymalizacja, która może pomóc w wyeliminowaniu zależności, a tym samym umożliwić inne optymalizacje. Ponadto niektóre architektury wykorzystują konstrukcje pętlowe na poziomie języka Assemblera, które liczą się tylko w jednym kierunku (np. Spadek-skok-jeśli-nie-zero (DJNZ)).

można zobaczyć wynik dla innych kompilatorów here.

+0

'inc' jest wolniejsze niż" dodaj "o jeden cykl zegara. Porównaj je w podręczniku [Intel® 64 i IA-32 Reference Optimization Reference Manual] (http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures- optimization-manual.html) Przewiń w dół do Dodatku C, a zobaczysz czasy opóźnienia i przepustowości każdego x86/x6 4 instrukcje. 1 cykl zegara może nie wydawać się znaczący, ale jeśli masz setki lub tysiące pętli, szybko się zsumuje. – Icemanind

1

Twój wniosek jest poprawny: odwrócony cykl będzie skierowany 0 (wola cykl kończy się, gdy wartość rejestru dotrzeć 0), tak że Add ustawi flagę zerowej używany w branży warunkowego.

W ten sposób nie potrzebujesz dedykowanego Cmp, który prowadzi do: 1) optymalizacji rozmiaru 2) jest również szybszy (wniosek z decyzji programistów kompilatora i inny answer).

To dość popularna sztuczka asemblera do pisania na pętli 0. Jestem zaskoczony, że rozumiesz asemblera, ale nie wiesz (pytasz) o tym.