2011-11-14 14 views
10

Sto usando i builtin di gcc Intel-compatibili (come __sync_fetch_and_add) per un po 'di tempo, usando il mio modello atomic. Le funzioni "__sync" sono ora ufficialmente considerate "legacy".Perché GCC std :: incremento atomico genera un assemblaggio non atomico inefficiente?

C++ 11 supporta std::atomic<> ei relativi discendenti, quindi sembra ragionevole utilizzarlo, poiché rende il mio codice conforme allo standard e il compilatore produrrà il codice migliore in entrambi i modi, in modo indipendente dalla piattaforma, ovvero quasi troppo bello per essere vero
Per inciso, dovrei solo sostituire il testo atomic con std::atomic. C'è molto in std::atomic (re: modelli di memoria) che non ho davvero bisogno, ma i parametri di default si occupano di questo.

Ora per le cattive notizie. A quanto pare, il codice generato è, da quello che posso dire, ... una schifezza totale, e nemmeno atomica. Anche un esempio minimo che incrementa una singola variabile atomica e gli output ha non meno di 5 chiamate di funzione non inline a ___atomic_flag_for_address, e __atomic_flag_clear_explicit (completamente ottimizzato), e d'altra parte, non c'è una singola istruzione atomica nel generato eseguibile.

Cosa dà? Ovviamente c'è sempre la possibilità di un bug del compilatore, ma con l'enorme numero di revisori e utenti, è improbabile che in genere cose così drastiche passino inosservate. Il che significa che probabilmente non è un bug, ma un comportamento intenzionale.

Qual è la "motivazione" alla base di così tante chiamate di funzione e come viene implementata l'atomicità senza atomicità?

As-semplice-as-it-può-get esempio:

#include <atomic> 

int main() 
{ 
    std::atomic_int a(5); 
    ++a; 
    __builtin_printf("%d", (int)a); 
    return 0; 
} 

produce il seguente .s:

movl $5, 28(%esp)  #, a._M_i 
movl %eax, (%esp)  # tmp64, 
call ___atomic_flag_for_address # 
movl $5, 4(%esp) #, 
movl %eax, %ebx #, __g 
movl %eax, (%esp)  # __g, 
call ___atomic_flag_wait_explicit  # 
movl %ebx, (%esp)  # __g, 
addl $1, 28(%esp)  #, MEM[(__i_type *)&a] 
movl $5, 4(%esp) #, 
call _atomic_flag_clear_explicit # 
movl %ebx, (%esp)  # __g, 
movl $5, 4(%esp) #, 
call ___atomic_flag_wait_explicit  # 
movl 28(%esp), %esi # MEM[(const __i_type *)&a], __r 
movl %ebx, (%esp)  # __g, 
movl $5, 4(%esp) #, 
call _atomic_flag_clear_explicit # 
movl $LC0, (%esp)  #, 
movl %esi, 4(%esp) # __r, 
call _printf # 
(...) 
.def ___atomic_flag_for_address; .scl 2; .type 32; .endef 
.def ___atomic_flag_wait_explicit; .scl 2; .type 32; .endef 
.def _atomic_flag_clear_explicit; .scl 2; .type 32; .endef 

... e le funzioni citate aspetto esempio come questo in objdump:

004013c4 <__atomic_flag_for_address>: 
mov 0x4(%esp),%edx 
mov %edx,%ecx 
shr $0x2,%ecx 
mov %edx,%eax 
shl $0x4,%eax 
add %ecx,%eax 
add %edx,%eax 
mov %eax,%ecx 
shr $0x7,%ecx 
mov %eax,%edx 
shl $0x5,%edx 
add %ecx,%edx 
add %edx,%eax 
mov %eax,%edx 
shr $0x11,%edx 
add %edx,%eax 
and $0xf,%eax 
add $0x405020,%eax 
ret  

Gli altri sono un po 'più semplice, ma non trovo una singola istruzione che sarebbe davvero essere atomica (diverso da alcune spurie xchg che sono atomica su X86, ma questi sembrano essere piuttosto NOP/padding, dal momento che è xchg %ax,%ax seguito da ret).

Non sono assolutamente sicuro di quale sia la funzione così complicata per cui è necessario e come si intende rendere tutto atomico.

+0

Quale versione di GCC stai usando? Puoi mostrare un piccolo programma che produce un codice così povero? Sto eseguendo un'istantanea 4.7 del mese scorso e sembra che produca codice decente, con le istruzioni di 'lock' in esso. –

+1

Il modello di memoria che "non hai bisogno" mi viene in mente come possibile colpevole. Com'è il tuo codice? Inoltre cosa intendi con l'ultima frase: "Come viene realizzata l'atomicità senza atomicità"? – jalf

+0

Con "modelli di memoria" intendi "ordini di memoria"? –

risposta

14

È una compilazione del compilatore inadeguata.

Controlla la tua c++config.h, è shoukld simile a questa, ma non lo fa:

/* Define if builtin atomic operations for bool are supported on this host. */ 
#define _GLIBCXX_ATOMIC_BUILTINS_1 1 

/* Define if builtin atomic operations for short are supported on this host. 
    */ 
#define _GLIBCXX_ATOMIC_BUILTINS_2 1 

/* Define if builtin atomic operations for int are supported on this host. */ 
#define _GLIBCXX_ATOMIC_BUILTINS_4 1 

/* Define if builtin atomic operations for long long are supported on this 
    host. */ 
#define _GLIBCXX_ATOMIC_BUILTINS_8 1 

Queste macro sono definite o meno a seconda configure test, che controllano supporto della macchina host per __sync_XXX funzioni. Questi test sono in libstdc++v3/acinclude.m4, AC_DEFUN([GLIBCXX_ENABLE_ATOMIC_BUILTINS] ....

dell'installazione, è evidente dal MEM[(__i_type *)&a] messi nel file di assiemi dal -fverbose-asm che il compilatore utilizza le macro da atomic_0.h, ad esempio:

#define _ATOMIC_LOAD_(__a, __x)      \ 
    ({typedef __typeof__(_ATOMIC_MEMBER_) __i_type;       \ 
    __i_type* __p = &_ATOMIC_MEMBER_;      \ 
    __atomic_flag_base* __g = __atomic_flag_for_address(__p);   \ 
    __atomic_flag_wait_explicit(__g, __x);     \ 
    __i_type __r = *__p;       \ 
    atomic_flag_clear_explicit(__g, __x);      \ 
    __r; }) 

con un compilatore correttamente costruita, con il vostro programma di esempio, c++ -m32 -std=c++0x -S -O2 -march=core2 -fverbose-asm dovrebbe produrre qualcosa del genere:

movl $5, 28(%esp) #, a.D.5442._M_i 
lock addl $1, 28(%esp) #, 
mfence 
movl 28(%esp), %eax # MEM[(const struct __atomic_base *)&a].D.5442._M_i, __ret 
mfence 
movl $.LC0, (%esp) #, 
movl %eax, 4(%esp) # __ret, 
call printf # 
+1

E indovina un po ', modificare 'C++ config.h' per contenere quelli definisce risolve il problema, dandomi esattamente la sequenza' lock addl, mfence' che hai postato sopra, che è anche quello che volevo. (Inoltrerò il problema al mio compilatore di compilatori). Grazie mille. – Damon

3

Esistono due implementazioni. Uno che utilizza le primitive __sync e una che non lo fa. Inoltre una miscela dei due che utilizza solo alcuni di quei primitivi. Quale è selezionato dipende dai macro _GLIBCXX_ATOMIC_BUILTINS_1, _GLIBCXX_ATOMIC_BUILTINS_2, _GLIBCXX_ATOMIC_BUILTINS_4 e _GLIBCXX_ATOMIC_BUILTINS_8.

Almeno il primo è necessario per l'implementazione mista, tutti sono necessari per uno completamente atomico. È seems che la loro definizione dipende dal computer di destinazione (potrebbero non essere definiti per -mi386 e devono essere definiti per -mi686).

+0

Nessuno di questi è definito qui anche se gli insn atomici sono certamente disponibili (sto compilando per '-march = core2') e funzionano senza problemi usando le funzioni' __sync'. Ho provato a definire queste macro prima di includere '' solo per vedere se questo fa la differenza, non è così. Quindi, in pratica, stai dicendo che questa è probabilmente una sorta di "implementazione di fallback del povero uomo"? In tal caso, come potrei abilitare quello reale (senza compilare il mio gcc)? – Damon

Problemi correlati