Я пытался выяснить проблему производительности в приложении и, наконец, сузил ее до действительно странной проблемы. Следующий фрагмент кода работает в 6 раз медленнее на процессоре Skylake (i5-6500), если команда VZEROUPPER закомментирована. Я тестировал процессоры Sandy Bridge и Ivy Bridge, и обе версии работают с одинаковой скоростью, с или без VZEROUPPER.
Теперь у меня неплохое представление о том, что делает VZEROUPPER, и я думаю, что это не должно иметь никакого значения для этого кода, когда нет кодированных инструкций VEX и нет вызовов какой-либо функции, которая может их содержать. Тот факт, что он не поддерживает другие процессоры, совместимые с AVX, похоже, поддерживает это. Так же, как и таблица 11-2 в Справочное руководство по оптимизации архитектуры Intel® 64 и IA-32
Так что происходит?
Единственная теория, которую я оставил, состоит в том, что в CPU есть ошибка, и она неправильно запускает процедуру "сохранить верхнюю половину регистров AVX", где она не должна. Или что-то еще так же странно.
Это main.cpp:
#include <immintrin.h>
int slow_function( double i_a, double i_b, double i_c );
int main()
{
    /* DAZ and FTZ, does not change anything here. */
    _mm_setcsr( _mm_getcsr() | 0x8040 );
    /* This instruction fixes performance. */
    __asm__ __volatile__ ( "vzeroupper" : : : );
    int r = 0;
    for( unsigned j = 0; j < 100000000; ++j )
    {
        r |= slow_function( 
                0.84445079384884236262,
                -6.1000481519580951328,
                5.0302160279288017364 );
    }
    return r;
}
и это slow_function.cpp:
#include <immintrin.h>
int slow_function( double i_a, double i_b, double i_c )
{
    __m128d sign_bit = _mm_set_sd( -0.0 );
    __m128d q_a = _mm_set_sd( i_a );
    __m128d q_b = _mm_set_sd( i_b );
    __m128d q_c = _mm_set_sd( i_c );
    int vmask;
    const __m128d zero = _mm_setzero_pd();
    __m128d q_abc = _mm_add_sd( _mm_add_sd( q_a, q_b ), q_c );
    if( _mm_comigt_sd( q_c, zero ) && _mm_comigt_sd( q_abc, zero )  )
    {
        return 7;
    }
    __m128d discr = _mm_sub_sd(
        _mm_mul_sd( q_b, q_b ),
        _mm_mul_sd( _mm_mul_sd( q_a, q_c ), _mm_set_sd( 4.0 ) ) );
    __m128d sqrt_discr = _mm_sqrt_sd( discr, discr );
    __m128d q = sqrt_discr;
    __m128d v = _mm_div_pd(
        _mm_shuffle_pd( q, q_c, _MM_SHUFFLE2( 0, 0 ) ),
        _mm_shuffle_pd( q_a, q, _MM_SHUFFLE2( 0, 0 ) ) );
    vmask = _mm_movemask_pd(
        _mm_and_pd(
            _mm_cmplt_pd( zero, v ),
            _mm_cmple_pd( v, _mm_set1_pd( 1.0 ) ) ) );
    return vmask + 1;
}
Функция компилируется до этого с помощью clang:
 0:   f3 0f 7e e2             movq   %xmm2,%xmm4
 4:   66 0f 57 db             xorpd  %xmm3,%xmm3
 8:   66 0f 2f e3             comisd %xmm3,%xmm4
 c:   76 17                   jbe    25 <_Z13slow_functionddd+0x25>
 e:   66 0f 28 e9             movapd %xmm1,%xmm5
12:   f2 0f 58 e8             addsd  %xmm0,%xmm5
16:   f2 0f 58 ea             addsd  %xmm2,%xmm5
1a:   66 0f 2f eb             comisd %xmm3,%xmm5
1e:   b8 07 00 00 00          mov    $0x7,%eax
23:   77 48                   ja     6d <_Z13slow_functionddd+0x6d>
25:   f2 0f 59 c9             mulsd  %xmm1,%xmm1
29:   66 0f 28 e8             movapd %xmm0,%xmm5
2d:   f2 0f 59 2d 00 00 00    mulsd  0x0(%rip),%xmm5        # 35 <_Z13slow_functionddd+0x35>
34:   00 
35:   f2 0f 59 ea             mulsd  %xmm2,%xmm5
39:   f2 0f 58 e9             addsd  %xmm1,%xmm5
3d:   f3 0f 7e cd             movq   %xmm5,%xmm1
41:   f2 0f 51 c9             sqrtsd %xmm1,%xmm1
45:   f3 0f 7e c9             movq   %xmm1,%xmm1
49:   66 0f 14 c1             unpcklpd %xmm1,%xmm0
4d:   66 0f 14 cc             unpcklpd %xmm4,%xmm1
51:   66 0f 5e c8             divpd  %xmm0,%xmm1
55:   66 0f c2 d9 01          cmpltpd %xmm1,%xmm3
5a:   66 0f c2 0d 00 00 00    cmplepd 0x0(%rip),%xmm1        # 63 <_Z13slow_functionddd+0x63>
61:   00 02 
63:   66 0f 54 cb             andpd  %xmm3,%xmm1
67:   66 0f 50 c1             movmskpd %xmm1,%eax
6b:   ff c0                   inc    %eax
6d:   c3                      retq   
Сгенерированный код отличается от gcc, но он показывает ту же проблему. Более старая версия компилятора Intel генерирует еще одну вариацию функции, которая также показывает проблему, но только если main.cpp не построена с помощью компилятора Intel, поскольку она вставляет вызовы для инициализации некоторых своих собственных библиотек, которые, вероятно, в конечном итоге выполняют VZEROUPPER где-то.
И, конечно, если все это построено с поддержкой AVX, поэтому встроенные средства превращаются в кодированные команды VEX, также нет проблем.
Я пробовал профилировать код с помощью perf в linux, и большая часть времени выполнения обычно приземляется на 1-2 команды, но не всегда одни и те же, в зависимости от версии версии кода я (gcc, clang, intel), Ускорение функции означает, что разница в производительности постепенно исчезает, поэтому похоже, что некоторые инструкции вызывают проблему.
EDIT: Здесь доступна чистая версия сборки для linux. Комментарии ниже.
    .text
    .p2align    4, 0x90
    .globl _start
_start:
    #vmovaps %ymm0, %ymm1  # This makes SSE code crawl.
    #vzeroupper            # This makes it fast again.
    movl    $100000000, %ebp
    .p2align    4, 0x90
.LBB0_1:
    xorpd   %xmm0, %xmm0
    xorpd   %xmm1, %xmm1
    xorpd   %xmm2, %xmm2
    movq    %xmm2, %xmm4
    xorpd   %xmm3, %xmm3
    movapd  %xmm1, %xmm5
    addsd   %xmm0, %xmm5
    addsd   %xmm2, %xmm5
    mulsd   %xmm1, %xmm1
    movapd  %xmm0, %xmm5
    mulsd   %xmm2, %xmm5
    addsd   %xmm1, %xmm5
    movq    %xmm5, %xmm1
    sqrtsd  %xmm1, %xmm1
    movq    %xmm1, %xmm1
    unpcklpd    %xmm1, %xmm0
    unpcklpd    %xmm4, %xmm1
    decl    %ebp
    jne    .LBB0_1
    mov $0x1, %eax
    int $0x80
Хорошо, так как, как подозревали в комментариях, использование кодированных инструкций VEX приводит к замедлению. Использование VZEROUPPER очищает его. Но это все еще не объясняет, почему.
Как я понимаю, использование VZEROUPPER подразумевает затраты на переход к старым инструкциям SSE, но не постоянное их замедление. Особенно не такой большой. Принимая во внимание накладные расходы на цикл, отношение составляет не менее 10x, возможно, больше.
Я попытался немного возиться с сборкой, а инструкции float так же плохи, как и двойные. Я не мог точно определить проблему для одной инструкции.


