sum - procesador - avx2 amd
4 sumas horizontales de doble precisiĆ³n de una vez con AVX (2)
VHADD
instrucciones de VHADD
están destinadas a ser seguidas por VADD
regular. El siguiente código debería darle lo que desea:
// {a[0]+a[1], b[0]+b[1], a[2]+a[3], b[2]+b[3]}
__m256d sumab = _mm256_hadd_pd(a, b);
// {c[0]+c[1], d[0]+d[1], c[2]+c[3], d[2]+d[3]}
__m256d sumcd = _mm256_hadd_pd(c, d);
// {a[0]+a[1], b[0]+b[1], c[2]+c[3], d[2]+d[3]}
__m256d blend = _mm256_blend_pd(sumab, sumcd, 0b1100);
// {a[2]+a[3], b[2]+b[3], c[0]+c[1], d[0]+d[1]}
__m256d perm = _mm256_permute2f128_pd(sumab, sumcd, 0x21);
__m256d sum = _mm256_add_pd(perm, blend);
Esto da el resultado en 5 instrucciones. Espero tener las constantes correctas.
La permutación que propusiste es ciertamente posible de lograr, pero requiere múltiples instrucciones. Perdón por no responder esa parte de tu pregunta.
Editar: No pude resistir, aquí está la permutación completa. (Una vez más, hice todo lo posible para tratar de obtener las constantes correctas.) Puedes ver que intercambiar u[1]
y u[2]
es posible, solo requiere un poco de trabajo. Cruzar la barrera de 128 bits es difícil en la primera generación. AVX. También quiero decir que VADD
es preferible a VHADD
porque VADD
tiene el doble de rendimiento, a pesar de que está haciendo la misma cantidad de adiciones.
// {x[0],x[1],x[2],x[3]}
__m256d x;
// {x[1],x[0],x[3],x[2]}
__m256d xswap = _mm256_permute_pd(x, 0b0101);
// {x[3],x[2],x[1],x[0]}
__m256d xflip128 = _mm256_permute2f128_pd(xswap, xswap, 0x01);
// {x[0],x[2],x[1],x[3]} -- not imposssible to swap x[1] and x[2]
__m256d xblend = _mm256_blend_pd(x, xflip128, 0b0110);
// repeat the same for y
// {y[0],y[2],y[1],y[3]}
__m256d yblend;
// {x[0],x[2],y[0],y[2]}
__m256d x02y02 = _mm256_permute2f128_pd(xblend, yblend, 0x20);
// {x[1],x[3],y[1],y[3]}
__m256d x13y13 = _mm256_permute2f128_pd(xblend, yblend, 0x31);
El problema se puede describir como sigue.
Entrada
__m256d a, b, c, d
Salida
__m256d s = {a[0]+a[1]+a[2]+a[3], b[0]+b[1]+b[2]+b[3],
c[0]+c[1]+c[2]+c[3], d[0]+d[1]+d[2]+d[3]}
Trabajo que he hecho hasta ahora
Parecía bastante fácil: dos VHADD con algunos cambios intermedios, pero de hecho combinando todas las permutaciones presentadas por AVX no puede generar la permutación necesaria para lograr ese objetivo. Dejame explicar:
VHADD x, a, b => x = {a[0]+a[1], b[0]+b[1], a[2]+a[3], b[2]+b[3]}
VHADD y, c, d => y = {c[0]+c[1], d[0]+d[1], c[2]+c[3], d[2]+d[3]}
¿Pude permutar x e y de la misma manera para obtener
x1 = {a[0]+a[1], a[2]+a[3], c[0]+c[1], c[2]+c[3]}
y1 = {b[0]+b[1], b[2]+b[3], d[0]+d[1], d[2]+d[3]}
entonces
VHADD s, x1, y1 => s1 = {a[0]+a[1]+a[2]+a[3], b[0]+b[1]+b[2]+b[3],
c[0]+c[1]+c[2]+c[3], d[0]+d[1]+d[2]+d[3]}
que es el resultado que quería.
Por lo tanto, solo necesito encontrar cómo actuar
x,y => {x[0], x[2], y[0], y[2]}, {x[1], x[3], y[1], y[3]}
Desafortunadamente, llegué a la conclusión de que esto es probablemente imposible usando cualquier combinación de VSHUFPD, VBLENDPD, VPERMILPD, VPERM2F128, VUNPCKHPD, VUNPCKLPD. El quid de la cuestión es que es imposible intercambiar u [1] y u [2] en una instancia u de __m256d.
Pregunta
¿Es esto realmente un callejón sin salida? ¿O me he perdido una instrucción de permutación?
No conozco ninguna instrucción que te permita hacer ese tipo de permutación. Las instrucciones AVX típicamente operan de tal manera que los 128 bits superiores e inferiores del registro son algo independientes; no hay mucha capacidad para mezclar valores de las dos mitades. La mejor implementación que puedo pensar se basaría en la respuesta a esta pregunta :
__m128d horizontal_add_pd(__m256d x1, __m256d x2)
{
// calculate 4 two-element horizontal sums:
// lower 64 bits contain x1[0] + x1[1]
// next 64 bits contain x2[0] + x1[1]
// next 64 bits contain x1[2] + x1[3]
// next 64 bits contain x2[2] + x2[3]
__m256d sum = _mm256_hadd_pd(x1, x2);
// extract upper 128 bits of result
__m128d sum_high = _mm256_extractf128_pd(sum1, 1);
// add upper 128 bits of sum to its lower 128 bits
__m128d result = _mm_add_pd(sum_high, (__m128d) sum);
// lower 64 bits of result contain the sum of x1[0], x1[1], x1[2], x1[3]
// upper 64 bits of result contain the sum of x2[0], x2[1], x2[2], x2[3]
return result;
}
__m256d a, b, c, d;
__m128d res1 = horizontal_add_pd(a, b);
__m128d res2 = horizontal_add_pd(c, d);
// At this point:
// res1 contains a''s horizontal sum in bits 0-63
// res1 contains b''s horizontal sum in bits 64-127
// res2 contains c''s horizontal sum in bits 0-63
// res2 contains d''s horizontal sum in bits 64-127
// cast res1 to a __m256d, then insert res2 into the upper 128 bits of the result
__m256d sum = _mm256_insertf128_pd(_mm256_castpd128_pd256(res1), res2, 1);
// At this point:
// sum contains a''s horizontal sum in bits 0-63
// sum contains b''s horizontal sum in bits 64-127
// sum contains c''s horizontal sum in bits 128-191
// sum contains d''s horizontal sum in bits 192-255
Que debería ser lo que quieres Lo anterior debe poderse hacer en 7 instrucciones en total (el elenco no debería hacer nada, es solo una nota para el compilador que cambia la forma en que trata el valor en res1
), suponiendo que la función res1
horizontal_add_pd()
puede ser inlineada por su compilador y tiene suficientes registros disponibles.