Почему GCC std::atomic increment генерирует неэффективную неатомную сборку?

Я использую Intel-совместимые встраиваемые функции gcc (например, __sync_fetch_and_add) уже довольно давно, используя мой собственный atomic шаблон. Функции "__sync" теперь официально считаются "наследием".

C++11 поддерживает std::atomic<> и его потомков, поэтому кажется разумным использовать его вместо этого, так как это делает мой код соответствующим стандартам, а компилятор в любом случае выдаст наилучший код независимо от платформы, что почти слишком хорошо, чтобы быть правдой.
Кстати, мне бы только пришлось заменить atomic на std::atomic. В std::atomic есть много того, что мне не нужно (например, модели памяти), но об этом позаботятся параметры по умолчанию.

Теперь о плохих новостях. Как оказалось, сгенерированный код, насколько я могу судить, ... полное дерьмо, и даже совсем не атомарный. Даже минимальный пример, который увеличивает одну атомарную переменную и выводит ее, имеет не менее 5 неинлайновых вызовов функций ___atomic_flag_for_address, ___atomic_flag_wait_explicit и __atomic_flag_clear_explicit (полностью оптимизированных), а с другой стороны, в сгенерированном исполняемом файле нет ни одной атомарной инструкции.

Что же получается? Конечно, всегда есть вероятность ошибки компилятора, но при огромном количестве рецензентов и пользователей такие довольно резкие вещи вряд ли останутся незамеченными. А значит, скорее всего, это не ошибка, а намеренное поведение.

Какой "резон" в таком большом количестве вызовов функций, и как реализована без атомарность?

Простой пример:

#include <atomic>

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

производит следующие .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

... и упомянутые функции выглядят, например. например, так в 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    

Остальные несколько проще, но я не нахожу ни одной инструкции, которая действительно была бы атомарной (кроме некоторых надуманных xchg, которые являются атомарными на X86, но они, похоже, скорее NOP/padding, поскольку это xchg %ax,%ax, следующая за ret).

Я совершенно не понимаю, для чего нужна такая довольно сложная функция, и как она должна сделать что-то атомарное.

11
задан Ciro Santilli 新疆改造中心法轮功六四事件 16 June 2015 в 09:18
поделиться