Pregunta

En una CPU de 32 bits, un entero es de 4 bytes y un entero corto es de 2 bytes. Si estoy escribiendo una aplicación C / C ++ que usa muchos valores numéricos que siempre se ajustarán dentro del rango proporcionado de un entero corto, ¿es más eficiente usar enteros de 4 bytes o enteros de 2 bytes?

He escuchado que sugiere que los enteros de 4 bytes son más eficientes, ya que se ajusta al ancho de banda del bus desde la memoria a la CPU. Sin embargo, si estoy sumando dos enteros cortos, ¿la CPU empaquetará ambos valores en una sola pasada en paralelo (de modo que abarque el ancho de banda de 4 bytes del bus)?

¿Fue útil?

Solución

Sí, definitivamente deberías usar un número entero de 32 bits en una CPU de 32 bits, de lo contrario podría terminar ocultando los bits no utilizados (es decir, siempre hará los cálculos en 32 bits, luego convertirá la respuesta a 16 bits)

No realizará dos operaciones de 16 bits a la vez, pero si escribe el código usted mismo y está seguro de que no se desbordará, puede hacerlo usted mismo.

Editar : debo agregar que también depende en parte de tu definición de " eficiente " ;. Si bien podrá realizar operaciones de 32 bits más rápidamente, por supuesto, utilizará el doble de memoria.

Si se están utilizando para cálculos intermedios en un bucle interno en algún lugar, entonces use 32 bits. Sin embargo, si está leyendo esto desde el disco, o incluso si solo tiene que pagar por una falta de memoria caché, aún puede funcionar mejor usar enteros de 16 bits. Al igual que con todas las optimizaciones, solo hay una forma de saberlo: perfílelo .

Otros consejos

Si tiene una gran variedad de números, vaya con el tamaño más pequeño que funcione. Será más eficiente trabajar con una serie de cortocircuitos de 16 bits en lugar de 32 bits ya que se obtiene el doble de densidad de caché. El costo de cualquier extensión de signo que la CPU tenga que hacer para trabajar con valores de 16 bits en registros de 32 bits es insignificante en comparación con el costo de una falta de caché.

Si simplemente está utilizando variables miembro en clases combinadas con otros tipos de datos, es menos claro, ya que los requisitos de relleno probablemente eliminarán cualquier beneficio de ahorro de espacio de los valores de 16 bits.

Si estás usando " muchos " En valores enteros, es probable que el cuello de botella en su procesamiento sea ancho de banda a la memoria. Los enteros de 16 bits se empaquetan más estrechamente en la caché de datos y, por lo tanto, sería una ganancia de rendimiento.

Si tiene una gran cantidad de datos, debería leer Lo que todo programador debería Saber sobre la memoria por Ulrich Drepper. Concéntrese en el capítulo 6, sobre cómo maximizar la eficiencia de la caché de datos.

Una CPU de 32 bits es una CPU que generalmente opera internamente con valores de 32 bits, pero eso no significa que sea más lenta cuando se realiza la misma operación en un valor de 8/16 bits. x86, por ejemplo, todavía compatible con versiones anteriores hasta 8086, puede funcionar en fracciones de un registro. Eso significa que incluso si un registro tiene 32 bits de ancho, puede operar solo en los primeros 16 o en los primeros 8 bits de ese registro y no habrá ninguna desaceleración en absoluto. Este concepto incluso ha sido adoptado por x86_64, donde los registros son de 64 bits, pero aún así pueden operar solo en los primeros 32, 16 u 8 bits.

También las CPU x86 siempre cargan una línea de caché completa desde la memoria, si aún no está en la caché, y una línea de caché es más grande que 4 bytes de todos modos (para CPU de 32 bits en lugar de 8 o 16 bytes) y, por lo tanto, cargar 2 bytes desde la memoria es Igualmente rápido como cargar 4 bytes desde la memoria. Si se procesan muchos valores de la memoria, los valores de 16 bits pueden ser mucho más rápidos que los valores de 32 bits, ya que hay menos transferencias de memoria. Si una línea de caché es de 8 bytes, hay cuatro valores de 16 bits por línea de caché, pero solo dos valores de 32 bits, por lo tanto, al usar entradas de 16 bits, tiene un acceso a la memoria cada cuatro valores, usando entradas de 32 bits tiene uno cada dos valores , resultando en el doble de transferencias para procesar una gran matriz int.

Otras CPU, como PPC, por ejemplo, no pueden procesar solo una fracción de un registro, siempre procesan el registro completo. Sin embargo, estas CPU generalmente tienen operaciones de carga especiales que les permiten, por ejemplo, cargue un valor de 16 bits desde la memoria, amplíelo a 32 bits y escríbalo en un registro. Más adelante, tienen una operación de almacenamiento especial que toma el valor del registro y solo almacena los últimos 16 bits en la memoria; ambas operaciones solo necesitan un ciclo de CPU, al igual que una carga / almacenamiento de 32 bits, por lo que tampoco existe una diferencia de velocidad. Y dado que PPC solo puede realizar operaciones aritméticas en los registros (a diferencia de x86, que también puede operar directamente en la memoria), este procedimiento de carga / almacenamiento se lleva a cabo de todos modos, ya sea que utilice entradas de 32 bits o de 16 bits.

La única desventaja, si encadena múltiples operaciones en una CPU de 32 bits que solo puede funcionar en registros completos, es que el resultado de 32 bits de la última operación debe ser " recortar " a 16 bits antes de que se realice la siguiente operación, de lo contrario el resultado puede no ser correcto. Sin embargo, tal reducción es solo un solo ciclo de la CPU (una simple operación AND), y los compiladores son muy buenos para determinar cuándo es realmente necesario y la falta de influencia no tendrá ninguna influencia en el resultado final. Por lo tanto, tal recorte no se realiza después de cada instrucción, solo se realiza si es realmente inevitable. Algunas CPU ofrecen varias " mejoradas " instrucciones que hacen que tal recorte sea innecesario y he visto un montón de código en mi vida, donde había esperado tal recorte, pero al mirar el código de ensamblaje generado, el compilador encontró una manera de evitarlo por completo.

Entonces, si esperas una regla general aquí, tendré que decepcionarte. Nadie puede decir con seguridad que las operaciones de 16 bits son igualmente rápidas a las operaciones de 32 bits, ni nadie puede decir con seguridad que las operaciones de 32 bits siempre serán más rápidas. También depende qué hace exactamente tu código con esos números y cómo lo hace. He visto puntos de referencia en los que las operaciones de 32 bits eran más rápidas en ciertas CPU de 32 bits que en el mismo código con operaciones de 16 bits, sin embargo, también vi que lo contrario es cierto. Incluso el cambio de un compilador a otro o la actualización de la versión de su compilador puede que ya le dé la vuelta a todo. Solo puedo decir lo siguiente: el que diga que trabajar con pantalones cortos es significativamente más lento que trabajar con ints, deberá proporcionar un código fuente de muestra para esa reclamación y nombrar la CPU y el compilador que usó para las pruebas, ya que nunca he experimentado algo así dentro. sobre los últimos 10 años. Puede haber algunas situaciones en las que trabajar con ints sea quizás un 1-5% más rápido, pero algo por debajo del 10% no es " significativo " y la pregunta es, ¿vale la pena perder el doble de memoria en algunos casos solo porque puede comprarle un 2% de rendimiento? No lo creo.

Depende. Si está vinculado a la CPU, las operaciones de 32 bits en una CPU de 32 bits serán más rápidas que las de 16 bits. Si está enlazado a la memoria (específicamente si tiene demasiados errores de caché L2), use los datos más pequeños que pueda usar.

Puede averiguar cuál está utilizando un generador de perfiles que medirá tanto las fallas de CPU como las de L2, como VTune de Intel . Ejecutará su aplicación 2 veces con la misma carga, y combinará las 2 ejecuciones en una vista de los puntos de acceso en su aplicación, y podrá ver por cada línea de código cuántos ciclos se gastaron en esa línea. Si en una línea de código costosa, usted ve 0 errores de caché, está vinculado a la CPU. Si ves toneladas de errores, estás ligado a la memoria.

No escuches el consejo, pruébalo.

Esto probablemente dependerá en gran medida del hardware / compilador que estés usando. Una prueba rápida debería hacer el trabajo corto de esta pregunta. Probablemente menos tiempo para escribir la prueba de lo que es escribir la pregunta aquí.

Si está operando en un conjunto de datos grande, la mayor preocupación es la huella de memoria. Un buen modelo en este caso es asumir que la CPU es infinitamente rápida y pasar su tiempo preocupándose por la cantidad de datos que se deben mover a / desde la memoria. De hecho, las CPU ahora son tan rápidas que a veces es más eficiente codificar (por ejemplo, comprimir) los datos. De esa manera, la CPU hace (potencialmente mucho) más trabajo (decodificación / codificación), pero el ancho de banda de la memoria se reduce sustancialmente.

Por lo tanto, si su conjunto de datos es grande, probablemente esté mejor usando 16 enteros de bit. Si su lista está ordenada, puede diseñar un esquema de codificación que incluya codificación diferencial o de longitud de ejecución, lo que reducirá aún más el ancho de banda de la memoria.

Cuando dices 32 bits, asumiré que te refieres a x86. La aritmética de 16 bits es bastante lenta: el prefijo de tamaño de operando hace que la decodificación realmente sea lenta. Así que no haga que sus variables temporales sean cortas int o int16_t.

Sin embargo, x86 puede cargar eficientemente enteros de 16 y 8 bits en registros de 32 o 64 bits. (movzx / movsx: cero y extensión de signo). Así que siéntase libre de usar int corto para arrays y estructuras de campos, pero asegúrese de usar int o long para sus variables temporales.

  

Sin embargo, si estoy sumando dos enteros cortos, ¿la CPU empaquetará ambos valores en una sola pasada en paralelo (de modo que abarque el ancho de banda de 4 bytes del bus)?

Eso es una tontería. las instrucciones de carga / almacenamiento interactúan con el caché L1, y el factor limitante es el número de operaciones; el ancho es irrelevante p.ej. en core2: 1 carga y 1 almacenamiento por ciclo, independientemente del ancho. El caché L1 tiene una ruta de 128 o 256 bits al caché L2.

Si las cargas son su cuello de botella, una carga amplia que se divide con turnos o máscaras después de la carga puede ayudar. O utilice SIMD para procesar datos en paralelo sin descomprimirlos después de cargarlos en paralelo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top