У ядер Xeon-Phi Knights Landing есть быстрый exp2
инструкция vexp2pd
(внутренняя _mm512_exp2a23_pd
). Компилятор Intel C ++ может векторизовать exp
функция с использованием библиотеки Short Vector Math Library (SVML), которая поставляется вместе с компилятором. В частности, это вызывает функцию __svml_exp8
,
Однако, когда я прохожу через отладчик, я не вижу этого __svml_exp8
использует vexp2pd
инструкция. Это сложная функция со многими операциями FMA. Я это понимаю vexp2pd
менее точен, чем exp
но если я использую -fp-model fast=1
(по умолчанию) или fp-model fast=2
Я ожидаю, что компилятор будет использовать эту инструкцию, но это не так.
У меня два вопроса.
vexp2pd
?__svml_exp8
?Что касается второго вопроса, это то, что я сделал до сих пор.
//exp(x) = exp2(log2(e)*x)
extern "C" __m512d __svml_exp8(__m512d x) {
return _mm512_exp2a23_pd(_mm512_mul_pd(_mm512_set1_pd(M_LOG2E), x));
}
Это безопасно? Есть ли лучшее решение, например тот, который встроен в функцию? В тестовом коде ниже это примерно в 3 раза быстрее, чем если бы я не переопределил.
//https://godbolt.org/g/adI11c
//icpc -O3 -xMIC-AVX512 foo.cpp
#include <math.h>
#include <stdio.h>
#include <x86intrin.h>
extern "C" __m512d __svml_exp8(__m512d x) {
//exp(x) = exp2(log2(e)*x)
return _mm512_exp2a23_pd(_mm512_mul_pd(_mm512_set1_pd(M_LOG2E), x));
}
void foo(double * __restrict x, double * __restrict y) {
__assume_aligned(x, 64);
__assume_aligned(y, 64);
for(int i=0; i<1024; i++) y[i] = exp(x[i]);
}
int main(void) {
double x[1024], y[1024];
for(int i=0; i<1024; i++) x[i] = 1.0*i;
for(int r=0; r<1000000; r++) foo(x,y);
double sum=0;
//for(int i=0; i<1024; i++) sum+=y[i];
for(int i=0; i<8; i++) printf("%f ", y[i]); puts("");
//printf("%lf",sum);
}
ICC будет генерировать vexp2pd, но только при очень смягченных математических требованиях, как указано целевыми ключами -fimf *.
#include <math.h>
void vfoo(int n, double * a, double * r)
{
int i;
#pragma simd
for ( i = 0; i < n; i++ )
{
r[i] = exp(a[i]);
}
}
Например. скомпилировать с -xMIC-AVX512 -fimf-domain-exclusion = 1 -fimf-precision-bits = 22
..B1.12:
vmovups (%rsi,%rax,8), %zmm0
vmulpd .L_2il0floatpacket.2(%rip){1to8}, %zmm0, %zmm1
vexp2pd %zmm1, %zmm2
vmovupd %zmm2, (%rcx,%rax,8)
addq $8, %rax
cmpq %r8, %rax
jb ..B1.12
Пожалуйста, убедитесь, что понимаете значение точности, поскольку не только конечный результат имеет точность только около 22 бит, но и vexp2pd сбрасывает на ноль любые денормализованные результаты независимо от битов FTZ / DAZ, установленных в MXCSR.
На второй вопрос: «Как безопасно переопределить вызов __svml_exp8?»
Ваш подход, как правило, небезопасен. Подпрограммы SVML являются внутренними для компилятора Intel и основаны на пользовательских соглашениях о вызовах, поэтому универсальная подпрограмма с тем же именем может потенциально заглушить большее количество регистров, чем библиотечная подпрограмма, и вы можете столкнуться с трудностью отладки при несовпадении ABI.
Лучшим способом предоставления собственных векторных функций будет использование #pragma omp Declare Simd, например увидеть https://software.intel.com/en-us/node/524514 и, возможно, атрибут vector_variant, если предпочитаете кодирование с помощью встроенных функций, см. https://software.intel.com/en-us/node/523350. Только не пытайтесь переопределить стандартные математические имена, иначе вы получите ошибку.
Других решений пока нет …