2013-03-24 12 views
28

Zakładam, że obliczanie modułu liczby jest dość kosztowną operacją, przynajmniej w porównaniu z prostymi testami arytmetycznymi (takimi jak sprawdzanie, czy liczba przekracza długość tablicy). Jeśli tak jest rzeczywiście, czy skuteczniej jest zastąpić, na przykład, następujący kod:czy lepiej unikać operatora mod, kiedy to możliwe?

res = array[(i + 1) % len]; 

z następującymi? :

Pierwszy jest łatwiejszy w oczach, ale zastanawiam się, czy drugi może być bardziej wydajny. Jeśli tak, czy mogę oczekiwać, że kompilator optymalizujący zastąpi pierwszy fragment kodu drugim, gdy używany jest język skompilowany?

Oczywiście ta "optymalizacja" (jeśli rzeczywiście jest to optymalizacja) nie działa we wszystkich przypadkach (w tym przypadku działa tylko wtedy, gdy i+1 nigdy nie jest większa niż len).

+10

To może być przypadek zaginięcia lasu dla drzew. –

+1

Jeśli 'len' jest stałą czasu kompilacji, ostatni kompilator GCC (z' -02') zwykle robi sprytne rzeczy, często unikając instrukcji maszyny modułowej docelowego procesora. –

+2

To jest rodzaj optymalizacji, o której należy zapomnieć. Kompilator optymalizacyjny zrobi lepiej, niż mógłbyś. Ważniejsze jest czytelność twojego kodu. –

Odpowiedz

20

Moja ogólna porada jest następująca. Użyj dowolnej wersji, która Twoim zdaniem jest łatwiejsza dla oka, a następnie zrób profil całego systemu. Zoptymalizuj jedynie te części kodu, które profiler zaznacza jako wąskie gardła. Założę się, że dolara dolara, że ​​operator modulo nie będzie wśród nich.

Jeśli chodzi o konkretny przykład, tylko analiza porównawcza może określić, która z nich jest szybsza w danej architekturze przy użyciu określonego kompilatora. Potencjalnie zamieniasz modulo na branching, a to jest coś oczywistego, co byłoby szybsze.

+0

Na ostatnich maszynach arytmetyczna liczba całkowita jest prawie darmowa; o wiele ważniejsze są chybienia pamięci podręcznej ... które są znacznie droższe. pamięć podręczna L1 Miss zatrzymuje procesor na setki cykli, podczas których procesor może wykonać dziesiątki podziałów lub modułów; więc ostateczny koszt modułu to hałas –

+3

@BasileStarynkevitch: Cache zachowanie będzie identyczne między tymi dwoma fragmentami kodu. To, co się liczy, to to, czy wersja nr 2 używa rozgałęzienia, a jeśli tak, to jak dobrą robotę będzie odgrywał predyktor gałęzi. – NPE

+0

@Basile Starynkevitch Widziałem współczynnik około 300 między modulo vs dostęp do dużego stołu na laptopie. (Dodanie testu podzielności przez 17 do kwadratu w celu uniknięcia dostępu do tablicy było nadal korzystne.) – starblue

0

Modulo można wykonać za pomocą instrukcji pojedynczego procesora na większości architekturach (np. DIV na x86). Jednak jest to prawdopodobnie przedwczesna optymalizacja tego, czego potrzebujesz.

+14

Tylko dlatego, że istnieje jedna instrukcja dla operacji, nie oznacza to, że występuje ona w jednym cyklu zegara. –

+2

@ChrisDesjardins Zgoda, ale '%' jeśli drugi operator ma potęgę 2, może być reprezentowany jako maska ​​bitowa. – Alex

+5

Przepraszam, że musiałem przegłosować. Pracowałem z wieloma architekturami (ale nie z x86) i jeszcze nie pracowałem z takim, który realizuje mod/div w jednej instrukcji. I widziałem aplikacje, w których mod jest jednym z 10 najpopularniejszych wywołań funkcji procesora, ze względu na cały cykliczny bufor - każda "próbna" kopia, po której następuje% bufora. W moim przypadku staram się unikać modów, jeśli mogę - zwykle twierdząc, że rozmiary buforów wejściowych są podzielne przez 2, więc kompilator może zoptymalizować mod. –

16

Kilka prostych pomiarów:

#include <stdio.h> 
#include <stdlib.h> 

int main(int argc, char *argv[]) 
{ 
    int test = atoi(argv[1]); 
    int divisor = atoi(argv[2]); 
    int iterations = atoi(argv[3]); 

    int a = 0; 

    if (test == 0) { 
     for (int i = 0; i < iterations; i++) 
      a = (a + 1) % divisor; 
    } else if (test == 1) { 
     for (int i = 0; i < iterations; i++) 
      a = a + 1 == divisor ? 0 : a + 1; 
    } 

    printf("%d\n", a); 
} 

Kompilacja albo z gcc lub brzękiem z -O3 i działa time ./a.out 0 42 1000000000 (wersja modulo) lub time ./a.out 1 42 1000000000 (porównanie wersja) wyniki:

  • 6,25 sekundy runtime użytkownika dla wersji modulo,
  • 1.03 sekundy dla wersji porównawczej.

(używając gcc 5.2.1 lub szczęk 3.6.2; Intel Core i5-4690K @ 3.50GHz; 64-bitowy Linux)

Oznacza to, że prawdopodobnie jest to dobry pomysł, aby użyć Porównanie wersji .

+2

W przypadku bardziej realistycznych danych (na przykład, jeśli liczba byłaby przypadkowa) różnica nie byłaby tak duża. – user1209304

+1

Wersja porównawcza jest tylko szybsza, ponieważ wynik instrukcji if jest taki sam za każdym razem, więc predykator gałęzi robi to dobrze co czas. Jeśli randomizujesz dane wejściowe, wersja porównawcza może być nawet gorsza niż mod – Bigminimus

+1

@Bigminimus Hmm, ale wynik klauzuli if jest taki sam dla obu testów przez cały czas? –