Con cada nueva generación de procesadores Intel, surgen nuevas y cada vez más complejas instrucciones vectoriales. Aunque la longitud del vector (512 bits) no crecerá en el futuro cercano, aparecerán nuevos tipos de datos y tipos de instrucciones. Por ejemplo, ¿quién puede entender de un vistazo lo que hace un intrínseco (y la instrucción del procesador correspondiente)?
Lógica ternaria bit a bit que proporciona la capacidad de implementar cualquier función binaria de tres operandos; la función binaria específica se especifica por valor en imm8.
__m512i _mm512_mask_ternarylogic_epi32 (__m512i src, __mmask8 k, __m512i a, __m512i b, int imm8) FOR j := 0 to 15 i := j*32 IF k[j] FOR h := 0 to 31 index[2:0] := (src[i+h] << 2) OR (a[i+h] << 1) OR b[i+h] dst[i+h] := imm8[index[2:0]] ENDFOR ELSE dst[i+31:i] := src[i+31:i] FI ENDFOR dst[MAX:512] := 0
Bien, digamos que descubrimos cómo funciona. El siguiente nivel de complejidad es el código de depuración que usa intensivamente tales intrínsecos.
Quienes usan intrínsecamente regularmente conocen un sitio tan útil: la
guía de intrínseca de Intel . Si observa cuidadosamente cómo funciona, es fácil notar que el front-end de javascript descarga el archivo data-3.xxxml, que describe en detalle todos los intrínsecos, con un código similar a Matlab. (Por ejemplo, el que copié en el título de la publicación).
Pero cuando usamos intrínsecos para acelerar el código, ¡no escribimos en Matlab, sino en C y C ++! Hace tres meses, un cliente me preguntó si hay una implementación de intrínsecos vectoriales en C para la depuración, y decidí escribir un analizador que traduzca el código de la Guía de intrínsecos a C. Resulta una biblioteca que implementa casi todos los intrínsecos para que pueda ingresar usando un depurador paso a paso ( o agregue debug printf).
Por ejemplo, una operación del título de una publicación se convierte en
for (int j = 0; j <= 15; j++) { if (k & (1 << j)) { for (int h = 0; h <= 31; h++) { int index = ((((src_vec[j] & (1 << h)) >> h) << 2) | (((a_vec[j] & (1 << h)) >> h) << 1) | ((b_vec[j] & (1 << h)) >> h)) & 0x7; dst_vec[j] = (dst_vec[j] & ~(1 << h)) | ((((imm8 & (1 << index)) >> index)) << h); } } else { dst_vec[j] = src_vec[j]; } }
Es cierto, esto es mucho más comprensible? No realmente? Bueno, acabo de elegir una función compleja como ejemplo. Por lo general, cuando depura el código con intrínsecos (por ejemplo, DSP) debe tener en cuenta tanto el algoritmo como las características de cada instrucción. Teniendo en cuenta que las instrucciones funcionan con vectores largos, y los algoritmos DSP a menudo se basan en matemáticas serias, mi cabeza no hace frente: no hay suficiente memoria y concentración a corto plazo. Sospecho que no estoy solo; varias veces incluso pensé que había encontrado un error en las instrucciones. Luego, por supuesto, cada vez resultó que estaba equivocado, y no funcionó para abrir un nuevo error FDIV. Pero si pudiera, en esos casos, depurar paso a paso dentro de las instrucciones, inmediatamente entendería bajo qué condiciones aparece un valor en el componente de mi vector que no esperaba.
Los clientes me dijeron que usan esta biblioteca para depurar funciones individuales con intrínsecos AVX-512 en una computadora portátil que solo es compatible con AVX2. Por supuesto,
Intel SDE es mucho más adecuado para esto, porque imita con gran precisión todos los conjuntos de instrucciones. Tengo un conjunto de pruebas unitarias (también generadas automáticamente) que para cada intrínseco de la biblioteca compara el resultado de su trabajo con el resultado de la ejecución de la instrucción del ensamblador correspondiente. Como corresponde a las pruebas unitarias, la mayoría funciona como se esperaba. Sin embargo, algunos intrínsecos de depuración con coma flotante (precisión doble y simple) no siempre funcionan correctamente al 100%. Yo diría que a veces es una especie de
matemática rápida . ¡Y hay diferentes mecanismos de redondeo! IEE754 tiene muchas sutilezas ...
Hay otra característica importante de usar la depuración de immintrin en lugar de SDE (que no apruebo de ninguna manera, pero no puedo detenerlo). Si compila gcc o clang con la opción, por ejemplo,
-march = nehalem , entonces gcc y clang devuelven vectores de 512 bits de las funciones en la pila de las funciones, e ICC aún los devuelve a ZMM0. Por lo tanto, el compilador Intel no se puede usar en este modo. Y gcc tiene una opción útil
-Og , que ayuda con la depuración, incluso con la depuración de immintrin.
Hay varios intrínsecos cuya acción principal es cambiar el contenido del registro, por ejemplo, o banderas. No implementé tales instrucciones. Bueno, aunque mi analizador no está listo, todavía falta la implementación de aproximadamente el 10% de los intrínsecos.
Usar immintrin debug es muy simple: no necesita cambiar la fuente, pero debe agregar una compilación condicional para incluir immintrin_dbg.h en lugar de immintrin.h en caso de compilación de depuración.
Puedes descargarlo
en github .