2015-05-03 5 views
7

Im Linux-Kernel-Code vorbei gibt es ein Makro zu Test-Bit verwendet (Linux Version 2.6.2): ​​Differenz zwischen der Performance-Funktion, wenn Parameter wie der Kompilierung konstante oder variable

#define test_bit(nr, addr)      \ 
     (__builtin_constant_p((nr))    \ 
     ? constant_test_bit((nr), (addr))  \ 
     : variable_test_bit((nr), (addr))) 

wo constant_test_bit und variable_test_bit definiert wie:

static inline int constant_test_bit(int nr, const volatile unsigned long *addr ) 
{  
     return ((1UL << (nr & 31)) & (addr[nr >> 5])) != 0; 
} 


static __inline__ int variable_test_bit(int nr, const volatile unsigned long *addr) 
{  
     int oldbit; 

     __asm__ __volatile__(
       "btl %2,%1\n\tsbbl %0,%0" 
       :"=r" (oldbit) 
       :"m" (ADDR),"Ir" (nr)); 
     return oldbit; 
} 

ich verstehe, dass __builtin_constant_p, ob eine Variable kompilieren Zeitkonstante oder unbekannt ist zu erkennen, verwendet wird. Meine Frage ist: Gibt es einen Leistungsunterschied zwischen diesen beiden Funktionen, wenn das Argument eine Kompilierzeitkonstante ist oder nicht? Warum sollte man die C-Version verwenden und die Assembly-Version verwenden, wenn dies nicht der Fall ist?

UPDATE: Die folgende Hauptfunktion verwendet wird, um die Leistung zu testen:

konstant, Call constant_test_bit:

int main(void) { 
     unsigned long i, j = 21; 
     unsigned long cnt = 0; 
     srand(111) 
     //j = rand() % 31; 
     for (i = 1; i < (1 << 30); i++) { 
       j = (j + 1) % 28; 
       if (constant_test_bit(j, &i)) 
         cnt++; 
     } 
     if (__builtin_constant_p(j)) 
       printf("j is a compile time constant\n"); 
     return 0; 
} 

Dies korrekt gibt den Satz j a ...

Für die anderen Situationen entfernen Sie einfach die Zeile, die eine "zufällige" Nummer zu j zuweist und den Funktionsnamen entsprechend ändert ly. Wenn diese Zeile unkommentiert ist, ist die Ausgabe leer und dies wird erwartet.

ich gcc test.c -O1 verwenden zu kompilieren, und hier ist das Ergebnis:

konstant, constant_test_bit:

$ time ./a.out 

j is compile time constant 

real 0m0.454s 
user 0m0.450s 
sys  0m0.000s 

konstant, variable_test_bit (weglassen time ./a.out, gleiche gilt für die folgenden):

j is compile time constant 

real 0m0.885s 
user 0m0.883s 
sys  0m0.000s 

variabel, konstant_test_bit:

real 0m0.485s 
user 0m0.477s 
sys  0m0.007s 

Variable, variable_test_bit:

real 0m3.471s 
user 0m3.467s 
sys  0m0.000s 

Ich habe jede Version mehrmals ausgeführt wird, und die obigen Ergebnisse sind die typischen Werte von ihnen. Es scheint, dass die constant_test_bit Funktion immer schneller ist als die variable_test_bit Funktion, egal ob der Parameter eine Kompilierzeitkonstante ist oder nicht ... Für die letzten beiden Ergebnisse (wenn nicht konstant ist) ist die variable Version sogar dramatisch langsamer als die Konstante ein. Fehle ich hier etwas?

+0

Könnte sein, aber der einzige Weg, um herauszufinden, ist zu messen. – deviantfan

+0

Offensichtlich dachte jemand, dass es einen Unterschied in perf macht, oder es würde nicht 2 Versionen geben. Für die Details müssen Sie 4 Fälle berücksichtigen (eine Konstante/nicht konstant an eine der beiden Funktionen übergeben). Was denkst du passiert in jedem Fall? Haben Sie sich die erzeugte Baugruppe angesehen? –

+0

@deviantfan Ich habe die Leistungsergebnisse hinzugefügt. –

Antwort

5

Mit godbolt wir können eine experiment using of constant_test_bit tun, werden die folgenden zwei Testfunktionen gcc mit dem -O3 Flag kompiliert werden:

// Non constant expression test case 
int func1(unsigned long i, unsigned long j) 
{ 
    int x = constant_test_bit(j, &i) ; 
    return x ; 
} 

// constant expression test case 
int func2(unsigned long i) 
{ 
    int x = constant_test_bit(21, &i) ; 
    return x ; 
} 

Wir sehen das Optimierungsprogramm der Lage ist, den konstanten Ausdruck Fall auf die folgenden zu optimieren:

shrq $21, %rax 
andl $1, %eax 

während der nicht-konstanten Ausdruck Fall endet wie folgt:

sarl $5, %eax 
andl $31, %ecx 
cltq 
leaq -8(%rsp,%rax,8), %rax 
movq (%rax), %rax 
shrq %cl, %rax 
andl $1, %eax 

So kann der Optimierer viel besseren Code für den Fall des konstanten Ausdrucks erzeugen, und wir können sehen, dass der nicht konstante Fall für constant_test_bit im Vergleich zur handgerollten Baugruppe in variable_test_bit ziemlich schlecht ist und der Implementierer den konstanten Ausdruck glauben muss Fall für constant_test_bit endet als besser als:

btl %edi,8(%rsp) 
sbbl %esi,%esi 

für die meisten Fälle.

Warum Ihr Testfall scheint eine andere Schlussfolgerung zu zeigen ist, dass Ihr Testfall ist fehlerhaft. Ich konnte nicht alle Probleme ausräumen. Aber wenn wir bei this case aussehen constant_test_bit mit einem nicht konstanten Ausdruck verwenden, können wir das Optimierungs sehen ist in der Lage all Arbeiten außerhalb des Blicks zu bewegen und die Arbeit zu reduzieren, um constant_test_bit innerhalb der Schleife zu:

movq (%rax), %rdi 

sogar mit eine ältere gcc Version, aber dieser Fall ist möglicherweise nicht relevant für die Fälle, in denen test_bit verwendet wird. Es kann spezifischere Fälle geben, in denen diese Art der Optimierung nicht möglich ist.

+0

Aber warum die Variable Version verwenden, wenn der Parameter nicht konstant ist? Die Assemblierung der von gcc generierten konstanten Version scheint in diesem Fall immer noch besser zu sein als die Variable Version. –

+0

@XiangyuZhu aktualisiert meine Antwort, Ihr Test hat einige Probleme, es ist schwer, sie alle zu ärgern, aber es ist wahrscheinlich, dass der ursprüngliche Grund für diese Wahl wurde von den Gründen, die ich in meiner ersten Antwort ausgelegt. Ich würde hoffen, dass sie Benchmarks mit relevanten Fällen durchführen und sicherstellen, dass die Annahme wirklich passt, aber schwer zu wissen ist. –

+0

Ja, vielleicht ist die Art und Weise, wie mein Code zum Testen verwendet wurde, so "speziell", dass gcc in der Lage ist, sie mehr zu optimieren als 'variable_test_bit'. Ich habe den letzten Kernel-Code nachgeschlagen und herausgefunden, dass ein ähnlicher Code immer noch da ist, also muss es einen guten Grund dafür geben (mit verschiedenen Versionen der Funktion). Aber ich entscheide mich, es jetzt einfach in Ruhe zu lassen. Danke für deine Antwort. –

Verwandte Themen