Soy consciente del éxito en el rendimiento al mezclar entradas firmadas con flotantes.
¿Es peor mezclar ints sin firmar con flotadores?
¿Hay algún golpe al mezclar con signo / sin signo sin flotadores?
¿Los diferentes tamaños (u32, u16, u8, i32, i16, i8) tienen algún efecto en el rendimiento? ¿En qué plataformas?
c++
performance
Luis
fuente
fuente
Respuestas:
La gran penalización de mezclar ints (de cualquier tipo) y flotantes se debe a que están en diferentes conjuntos de registros. Para pasar de un conjunto de registros a otro, debe escribir el valor en la memoria y volver a leerlo, lo que incurre en un bloqueo de carga-golpe-tienda .
Ir entre diferentes tamaños o firmas de entradas mantiene todo en el mismo conjunto de registros, por lo que evita la gran penalización. Puede haber penalizaciones menores debido a las extensiones de señal, etc., pero estas son mucho más pequeñas que una tienda de carga y descarga.
fuente
Sospecho que la información sobre Xbox 360 y PS3 específicamente va a estar detrás de muros solo para desarrolladores con licencia, como la mayoría de los detalles de bajo nivel. Sin embargo, podemos construir un programa x86 equivalente y desarmarlo para tener una idea general.
Primero, veamos qué costos de ampliación sin firmar:
La parte relevante se desmonta en (usando GCC 4.4.5):
Básicamente lo mismo: en un caso, movemos un byte, en el otro, movemos una palabra. Próximo:
Se convierte en:
Por lo tanto, el costo de la extensión del signo es el costo de
movsbl
más que elmovzbl
nivel de subinstrucción. Eso es básicamente imposible de cuantificar en los procesadores modernos debido a la forma en que funcionan los procesadores modernos. Todo lo demás, desde la velocidad de la memoria hasta el almacenamiento en caché de lo que estaba en la tubería de antemano, dominará el tiempo de ejecución.En los ~ 10 minutos que me llevó escribir estas pruebas, pude encontrar fácilmente un error de rendimiento real, y tan pronto como enciendo cualquier nivel de optimización del compilador, el código se vuelve irreconocible para tareas tan sencillas.
Esto no es Stack Overflow, por lo que espero que nadie aquí diga que la microoptimización no importa. Los juegos a menudo trabajan en datos que son muy grandes y muy numéricos, por lo que la atención cuidadosa a la ramificación, los lanzamientos, la programación, la alineación de la estructura, etc. puede proporcionar mejoras muy importantes. Cualquiera que haya pasado mucho tiempo optimizando el código PPC probablemente tenga al menos una historia de terror sobre las tiendas de carga y descarga. Pero en este caso, realmente no importa. El tamaño de almacenamiento de su tipo entero no afecta el rendimiento, siempre que esté alineado y quepa en un registro.
fuente
Las operaciones de enteros firmados pueden ser más caras en casi todas las arquitecturas. Por ejemplo, la división por una constante es más rápida cuando no está firmada, por ejemplo:
se optimizará para:
Pero...
se optimizará para:
o en sistemas donde la ramificación es barata,
Lo mismo vale para el módulo. Esto también es válido para los no poderes de 2 (pero el ejemplo es más complejo). Si su arquitectura no tiene una división de hardware (por ejemplo, la mayoría de ARM), las divisiones sin firma de los no concursos también son más rápidas.
En general, decirle al compilador que los números negativos no pueden resultar ayudará a la optimización de las expresiones, especialmente las utilizadas para la terminación de bucles y otras condiciones.
En cuanto a las entradas de diferentes tamaños, sí, hay un ligero impacto, pero tendrías que sopesar eso frente a mover menos memoria. En estos días, probablemente gane más al acceder a menos memoria de la que pierde con la expansión de tamaño. Estás muy lejos de la microoptimización en ese momento.
fuente
Las operaciones con int firmado o sin firmar tienen el mismo costo en los procesadores actuales (x86_64, x86, powerpc, arm). En el procesador de 32 bits, u32, u16, u8 s32, s16, s8 deben ser iguales. Puede tener penalidad con mala alineación.
Pero convertir int a float o float a int es una operación costosa. Puede encontrar fácilmente una implementación optimizada (SSE2, Neon ...).
El punto más importante es probablemente el acceso a la memoria. Si sus datos no caben en el caché L1 / L2, perderá más ciclos que la conversión.
fuente
Jon Purdy dice anteriormente (no puedo comentar) que sin firmar podría ser más lento porque no puede desbordarse. No estoy de acuerdo, la aritmética sin signo es simple aritmética moular módulo 2 con respecto al número de bits en la palabra. Las operaciones firmadas en principio pueden sufrir desbordamientos, pero generalmente están apagadas.
A veces puede hacer cosas inteligentes (pero no muy legibles) como empacar dos o más elementos de datos en un int y obtener múltiples operaciones por instrucción (aritmética de bolsillo). Pero tienes que entender lo que estás haciendo. Por supuesto, MMX te permite hacer esto naturalmente. Pero a veces, usar el tamaño de palabra compatible con HW más grande y empaquetar manualmente los datos le brinda la implementación más rápida.
Tenga cuidado con la alineación de datos. En la mayoría de las implementaciones de hardware, las cargas y tiendas no alineadas son más lentas. La alineación natural significa que, por ejemplo, una palabra de 4 bytes, la dirección es un múltiplo de cuatro, y las direcciones de palabras de ocho bytes deben ser múltiplos de ocho bytes. Esto se traslada a SSE (128 bits favorece la alineación de 16 bytes). AVX pronto extenderá estos tamaños de registro "vector" a 256 bits y luego a 512 bits. Y las cargas / tiendas alineadas serán más rápidas que las no alineadas. Para geeks HW, una operación de memoria no alineada puede abarcar cosas como la línea de caché e incluso los límites de página, por lo que HW debe tener cuidado.
fuente
Es un poco mejor usar enteros con signo para los índices de bucle, porque el desbordamiento con signo no está definido en C, por lo que el compilador supondrá que dichos bucles tienen menos casos de esquina. Esto se controla mediante el "-fstrict-overflow" de gcc (habilitado por defecto) y el efecto es probablemente difícil de notar sin leer la salida del ensamblaje.
Más allá de eso, x86 funciona mejor si no mezcla tipos, porque puede usar operandos de memoria. Si tiene que convertir tipos (signo o extensiones cero), eso significa una carga explícita y el uso de un registro.
Siga con int para las variables locales y la mayoría de esto sucederá de forma predeterminada.
fuente
Como señala Celion, la sobrecarga de convertir entre ints y flotantes tiene que ver en gran medida con la copia y conversión de valores entre registros. La única sobrecarga de entradas sin firmar en sí mismas proviene de su comportamiento envolvente garantizado, que requiere una cierta cantidad de comprobación de desbordamiento en el código compilado.
Básicamente no hay gastos generales en la conversión entre enteros con y sin signo. Los diferentes tamaños de enteros pueden ser (infinitamente) de acceso más rápido o más lento dependiendo de la plataforma. En términos generales, el tamaño del número entero más cercano al tamaño de la palabra de la plataforma será el más rápido de acceder, pero la diferencia de rendimiento general depende de muchos otros factores, especialmente el tamaño de la caché: si lo usa
uint64_t
cuando todo lo que necesita esuint32_t
, puede sea que una menor cantidad de sus datos va a caber en la memoria caché a la vez, y puede incurrir en una sobrecarga de carga.Sin embargo, es un poco excesivo pensar en esto. Si usa tipos que son apropiados para sus datos, las cosas deberían funcionar perfectamente bien, y la cantidad de energía que se obtendrá al seleccionar tipos basados en la arquitectura es insignificante de todos modos.
fuente