¿Existe una sintaxis para obligar al compilador de C a usar el operando de memoria directamente?
En el viejo tiempo de asm, simplemente escribimos en la instrucción dónde tomar el operando: registro 'real' o puntero de memoria (ubicación señalada por dirección).
Pero en el pseudo-asm intrínseco para CI no veo la manera de obligar al compilador a usar el puntero de memoria en la instrucción (rechazar la carga de datos de la memoria (caché) para 'registrar', es decir, el archivo de registro de basura carga el contenido en caché y provoca la recarga con multa).
Entiendo que es fácil para el programador simplemente escribir el operando 'variable' en instinsic y dejar que el compilador decida si cargar primero desde la memoria o usarlo directamente (si es posible).
Tarea actual: quiero calcular SAD de una secuencia de bloques de 8x8 de 8 bits en la CPU AVX2 con un archivo de registro de 512 bytes (16 ymm 'registros' de 32 bytes cada uno). Por lo tanto, puede cargar 8 bloques fuente de 8x8 y 8 bits para llenar completamente el archivo de registro AVX2 disponible.
Quiero cargar bloques de origen en todos los archivos de registro y probar diferentes ubicaciones de 'ref' de la memoria contra estos bloques de origen y cada ubicación de referencia solo una vez. Así que quiero evitar que la CPU cargue bloques de referencia del caché para registrar el archivo y usar el 'operando de memoria' en instrucciones tristes.
Con asm simplemente escribimos algo como
(load all 16 ymm registers with src) vpsadbw ymm0, ymm0, [ref_base_address_register + some_offset...]Pero en el texto C con intrínseco es
__m256i src = load_src(src_pointer); __m256i ref = load_ref(ref_pointer); __m256i sad_result= _mm256_sad_epu8(src, ref)No tiene formas de señalar al compilador para usar un operando de memoria válido como
__m256i src = load_src(src_pointer); __m256i sad_result= _mm256_sad_epu8(src, *ref_pointer)O dependa del 'tamaño de la tarea' si el compilador se queda sin registros disponibles, cambiará automáticamente a la versión del operando de memoria y el programador puede escribir
__m256i sad_result=_mm256_sad_epu8(*(__m256i*)src_pointer, *(__m256i*)ref_pointer)y espera que el compilador cargue uno de los 2 operandos para registrar el archivo y usar el siguiente de la memoria?
No, no lo hay, a excepción de algunos intrínsecos específicos que tienen un operando de puntero aunque no sean carga pura o almacenamiento puro 1 .
Parte del propósito de los intrínsecos es abstraer los detalles de asignación de registros, tal como lo hace con int o double , por lo que depende del compilador mantener las cosas en los registros cuando eso es algo bueno. Esto suele suceder, así que verifique la salida de asm si le preocupa que el optimizador no haya podido plegar una carga intrínseca en un operando de fuente de memoria (por ejemplo, en https://godbolt.org/ o localmente). AVX (codificación VEX) permite plegar incluso cargas no alineadas porque, a diferencia del SSE heredado, la alineación no es necesaria de forma predeterminada.
Esto puede apestar cuando los compiladores fallan, como solían hacer muchos para _mm256_cvtepu8_epi32( _mm_loadl_epi64(p) ) - GCC solía emitir una carga movq real y un reg-reg vpmovzxbd . Solo en GCC9 y posteriores obtenemos una fuente de memoria vpmovzxbd . ( Cargar 8 caracteres de la memoria en una variable __m256 como flotantes de precisión individuales empaquetados )
O para su caso, si el compilador está derramando cosas incorrectas, la única solución es presentar un informe de error de optimización perdida y esperar una nueva versión del compilador. O para escribir una versión en asm (en línea o independiente).
Los diseñadores del modelo intrínseco también querían proporcionar intrínsecos load / loadu y store / storeu para comunicar información de alineación al compilador. (Y para float/double, para lanzar entre float* y __m128* o lo que sea.) _mm_load_si128((__m128i*)foo) es exactamente idéntico a *(__m128i*)foo y prácticamente lo mismo que acceder a un elemento de una matriz de __m128i , si el compilador no puede ver a través de la matriz y mantenerla en los registros. Consulte ¿Es `reinterpret_cast`ing entre el puntero de vector SIMD de hardware y el tipo correspondiente un comportamiento indefinido?
Los intrínsecos de carga se parecen confusamente a asm loads/store, pero en realidad son fundamentalmente diferentes cuando las optimizaciones están habilitadas.
Nota al pie 1 : AVX-512 tiene algunas instrucciones especiales que tienen características intrínsecas correspondientemente interesantes como VPMOVDB mem128 {k}, zmm2 - void _mm512_mask_cvtepi32_storeu_epi8(void * d, __mmask16 k, __m512i a); . Ser capaz de almacenar en la memoria le dio a Xeon Phi (Knight's Landing) una forma de almacenar bytes enmascarados sin AVX-512BW para vmovdqu8 .