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!
Czy oglądasz wygenerowany kod? – unwind
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
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