2015-06-12 10 views
8

Mam pytanie dotyczące optymalizacji kompilatora C i kiedy/jak pętle w funkcji inline są rozwijane.Pętla rozwijanie w funkcji inlined w C

Opracowuję kod liczbowy, który działa podobnie do poniższego przykładu. Zasadniczo, my_for() obliczyłby pewien rodzaj szablonu i wywołałby op(), aby zrobić coś z danymi w my_type *arg dla każdego i. Tutaj my_func() zawija , tworząc argument i wysyłając wskaźnik funkcji do my_op() ... którego zadaniem jest zmodyfikowanie i th podwójne dla każdego z() podwójne tablice arg->dest[j].

typedef struct my_type { 
    int const n; 
    double *dest[16]; 
    double const *src[16]; 
} my_type; 

static inline void my_for(void (*op)(my_type *,int), my_type *arg, int N) { 
    int i; 

    for(i=0; i<N; ++i) 
    op(arg, i); 
} 

static inline void my_op(my_type *arg, int i) { 
    int j; 
    int const n = arg->n; 

    for(j=0; j<n; ++j) 
    arg->dest[j][i] += arg->src[j][i]; 
} 

void my_func(double *dest0, double *dest1, double const *src0, double const *src1, int N) { 
    my_type Arg = { 
    .n = 2, 
    .dest = { dest0, dest1 }, 
    .src = { src0, src1 } 
    }; 

    my_for(&my_op, &Arg, N); 
} 

Działa to dobrze. Funkcje są tak proste, jak powinny, a kod jest (prawie) tak wydajny, jak zapisanie wszystkiego w linii w jednej funkcji i rozwinięcie pętli j, bez żadnego rodzaju my_type Arg.

Oto zamieszanie: jeśli ustawię int const n = 2; zamiast int const n = arg->n; w my_op(), kod stanie się tak szybki jak rozwinięta wersja jednofunkcyjna. Pytanie brzmi: dlaczego? Jeśli wszystko jest wpisane w my_func(), dlaczego kompilator nie widzi, że dosłownie definiuję Arg.n = 2? Co więcej, nie ma poprawy, gdy wyraźnie wykonam pętlę j, która po inlinowaniu powinna wyglądać jak szybsza int const n = 2;. Próbowałem też wszędzie używać my_type const, aby naprawdę zasygnalizować tę konstelację kompilatorowi, ale po prostu nie chce rozwinąć pętli.

W moim kodzie numerycznym stanowi to około 15% skuteczności działania. Jeśli to ma znaczenie, tam, n=4 i te pętle j pojawiają się w kilku gałęziach warunkowych w op().

Kompiluję z ICC (ICC) 12.1.5 20120612. Próbowałem #pragma unroll. Oto moje opcje kompilatora (ja przegap żadnych dobrych?):

-O3 -ipo -static -unroll-aggressive -fp-model precise -fp-model source -openmp -std=gnu99 -Wall -Wextra -Wno-unused -Winline -pedantic

Dzięki!

+0

Czy oglądasz wygenerowany kod? – unwind

+0

Jak "daleko" szukać wartości, które są znane podczas kompilacji, gdy wstawianie jest trudną decyzją. Wygląda na to, że przekroczyłeś limit kompilatora. Przekazanie 'n' jako jawnego parametru funkcji może poprawić szanse. – molbdnilo

+1

Zastanawiam się, czy nie zwiększyłoby to prędkości, jeśli wymienisz wymiary. Jak podano teraz, prawdopodobnie niewiele korzystasz z linii pamięci podręcznej i rozerwanych wypełnień (możesz też użyć memcpy, która jest już wysoce zoptymalizowana). Ponadto, wypełnianie struct intializatorem jest rozszerzeniem gcc (mam nadzieję, że jesteś tego świadomy - nie stanowi to dla mnie problemu). – Olaf

Odpowiedz

3

Oczywiście, kompilator nie jest wystarczająco "inteligentny", aby propagować stałą n i rozwinąć pętlę . Właściwie to gra to bezpieczne, ponieważ arg->n może zmienić między tworzeniem i używaniem.

Aby zapewnić stałą wydajność w różnych generacjach kompilatorów i wycisnąć maksimum z kodu, wykonaj rozwijanie ręcznie.

To, co ludzie lubią robić w takich sytuacjach (wydajność jest królem) polega na makrach.

Makra będą "inline" w kompilacjach debugowania (użyteczne) i mogą być szablonowe (do punktu) przy użyciu parametrów makr. Parametry makro, będące stałymi w czasie kompilacji, pozostają w ten sposób.

+0

C Makra preprocesora to ból szyi podczas korzystania z debuggera. Makra to makra, a nie szablony, nawet jeśli istnieją pewne typy zachowań, którymi wydają się udostępniać. Nie tworzysz makr szablonów, które po prostu podajesz, nawet jeśli używasz makr w makrach. Makra są rozszerzane przez Preprocesor C w celu wygenerowania tekstu, który następnie jest przesyłany do kompilatora C. Jest to technologia lat 70. zapożyczona z jeszcze starszych technologii makr asemblera. Szablony są wbudowane w kompilator C++, umożliwiając dostęp do funkcji kompilatora i danych niedostępnych dla Preprocesora C. –

+0

@RichardChambers: Zgadzam się na porównanie szablonów. Jednak makra OTOH pozwalają na rzeczy, których nie można zrobić przy użyciu szablonów. Takich jak tworzenie identyfikatorów, deklaracji itp. Myślę, że oba mają swoje zastosowanie, należy po prostu zachować ostrożność podczas ich używania. A ponieważ C nie ma szablonów, musisz używać makr do tego, co dostarczają. – Olaf

+0

@egur: Hmm, aby zachęcić ICC do tego, aby nie był bezpieczny, zadeklarowałem 'moje_type'' 'n' jako' const', a także instancję samej struktury. Raz podkreślone, będziesz uważał, że jest tak samo niezmienny jak 'n' w' my_op() '.Ale, jak rozumiem z tego, co powiedział Adrian, fakt, że pamięć została przydzielona dla 'Arg', więc koniecznie także dla' Arg.n', który uniemożliwia jej zoptymalizowanie. W każdym razie, wygrywasz dla pomysłu na makro. Wcześniej tylko parałem, ale w ciągu ostatnich kilku godzin oszalałem na punkcie makro i użyłem ich do wygenerowania wielu wersji tych funkcji ... i otrzymałem 15% zwrotu. – FiniteElement

2

Jest szybszy, ponieważ program nie przypisuje pamięci do zmiennej.

Jeśli nie musisz wykonywać żadnych operacji na nieznanych wartościach, są one traktowane tak, jakby były #define constant 2 z sprawdzaniem typu. Są one dodawane podczas kompilacji.

Czy mógłbyś wybrać jeden z dwóch tagów (mam na myśli C lub C++), to jest mylące, ponieważ języki traktują wartości inaczej - C traktuje je jak normalne zmienne, których wartości po prostu nie da się zmienić, oraz w C++ mają przypisaną pamięć lub nie, w zależności od kontekstu (jeśli potrzebujesz ich adresu lub jeśli chcesz je wyliczyć, gdy program jest uruchomiony, to pamięć jest przypisana).

Źródło: "Myślenie w C++". Bez dokładnego cytatu.

+0

Co masz na myśli przez "to powinno być komentarzem"? –

+1

Rozumiem. Ale nie mogę dodawać komentarzy, ponieważ wciąż nie mam 50 punktów. –

+0

Cóż, powinien to być komentarz zamiast odpowiedzi. Przynajmniej jego części (to naprawdę jest trochę niestrukturalne). – Olaf