En las CPU de 32 bits, ¿es un tipo ‘entero’ más eficiente que un tipo ‘corto’?

En una CPU de 32 bits, un entero es de 4 bytes y un entero corto 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 provisto de un entero corto, ¿es más eficiente usar enteros de 4 bytes o enteros de 2 bytes?

He oído 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 hasta la CPU. Sin embargo, si estoy agregando dos enteros cortos, ¿empaqueta la CPU ambos valores en una sola pasada en paralelo (abarcando así el ancho de banda de 4 bytes del bus)?

Sí, definitivamente debes usar un entero de 32 bits en una CPU de 32 bits, de lo contrario puede enmascarar los bits no utilizados (es decir, siempre hará las matemáticas en 32 bits, luego convertirá la respuesta a 16 bits)

No hará 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 un poco 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 alguna parte, entonces use 32 bits. Sin embargo, si estás leyendo esto desde el disco, o incluso si solo tienes que pagar por una falta de caché, aún así puede ser mejor usar números enteros de 16 bits. Al igual que con todas las optimizaciones, solo hay una forma de saberlo: perfilarlo .

Si tiene una gran variedad de números, entonces vaya con el tamaño más pequeño que funcione. Será más eficiente trabajar con una matriz de cortos de 16 bits que de 32 bits, ya que obtiene el doble de la densidad de la memoria 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 insignificante en comparación con el costo de una falta de caché.

Si simplemente está utilizando variables miembro en clases mezcladas con otros tipos de datos, entonces 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á utilizando “muchos” valores enteros, es probable que el cuello de botella en su procesamiento sea ancho de banda en la memoria. Los enteros de 16 bits se agrupan más estrechamente en la memoria caché de datos y, por lo tanto, serían una ganancia de rendimiento.

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

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

Además, las CPU x86 siempre cargan una línea de caché completa de la memoria, si no están en caché, y una línea de caché es mayor que 4 bytes (para CPU de 32 bits en lugar de 8 o 16 bytes) y cargar 2 bytes de memoria es igual de rápido que cargando 4 bytes desde la memoria. Si procesa 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 que al usar entradas de 16 bits tiene acceso a una memoria cada cuatro valores, con 32 bits cada uno tiene uno cada dos valores , lo que resulta en el doble de transferencias para procesar una matriz int grande.

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, cargar un valor de 16 bits de la memoria, expandirlo a 32 bits y escribirlo en un registro. Más tarde, tienen una operación de tienda especial que toma el valor del registro y solo almacena los últimos 16 bits en memoria; Ambas operaciones necesitan solo un ciclo de CPU, al igual que una carga / almacén de 32 bits, por lo que tampoco hay 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 en la memoria directamente), este procedimiento de carga / almacenamiento se lleva a cabo de todos modos ya sea que use 32 bit ints o 16 bit ints.

La única desventaja, si encadena múltiples operaciones en una CPU de 32 bits que solo puede operar en registros completos, es que el resultado de 32 bits de la última operación debe ser “recortado” 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 ciclo de CPU único (una operación AND simple), y los comstackdores son muy buenos para descubrir cuándo es realmente necesario un recorte así y al dejarlo fuera no tendrán ninguna influencia en el resultado final. , por lo que tal corte no se realiza después de cada instrucción, solo se realiza si es realmente inevitable. Algunas CPU ofrecen varias instrucciones “mejoradas” que hacen innecesario un corte como este y he visto un montón de código en mi vida, donde esperaba tal recorte, pero al observar el código ensamblador generado, el comstackdor encontró una forma de evitarlo por completo

Entonces, si esperas una regla general aquí, tendré que decepcionarte. Tampoco se puede asegurar que las operaciones de 16 bits sean igualmente rápidas que las de 32 bits, y nadie puede asegurar que las operaciones de 32 bits siempre serán más rápidas. Depende también de qué está haciendo exactamente tu código con esos números y cómo lo está haciendo. He visto puntos de referencia donde las operaciones de 32 bits fueron 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 era cierto lo contrario. Incluso el cambio de un comstackdor a otro o la actualización de la versión del comstackdor ya pueden cambiar todo nuevamente. Solo puedo decir lo siguiente: Quien afirme que trabajar con cortometrajes es significativamente más lento que trabajar con ints, deberá proporcionar un código fuente de muestra para esa afirmación y nombrar la CPU y el comstackdor que usó para las pruebas, ya que nunca experimenté algo así en sobre los últimos 10 años. Puede haber situaciones en las que trabajar con tintas es quizás 1-5% más rápido, pero cualquier cosa inferior al 10% no es “significativa” y la pregunta es si vale la pena desperdiciar el doble de memoria en algunos casos solo porque puede comprarle 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 16 bits. Si tiene un límite de memoria (específicamente si tiene demasiados errores de caché L2), utilice los datos más pequeños que pueda incluir.

Puede averiguar cuál está usando un generador de perfiles que medirá tanto fallas de CPU como L2, como VTune de Intel . Ejecutará su aplicación 2 veces con la misma carga, y combinará las 2 ejecuciones en una vista de las zonas activas en su aplicación, y puede ver en cada línea de código cuántos ciclos se gastaron en esa línea. Si en una línea de código costosa, ve 0 caché falla, está vinculado a la CPU. Si ve toneladas de errores, está limitado a la memoria.

No escuches el consejo, pruébalo.

Probablemente esto dependerá en gran medida del hardware / comstackdor que esté utilizando. Una prueba rápida debería resumir esta pregunta. Probablemente tenga menos tiempo para escribir la prueba que escribir la pregunta aquí.

Si está operando en un gran conjunto de datos, la mayor preocupación es la huella de memoria. Un buen modelo en este caso es suponer que la CPU es infinitamente rápida, y pasar el 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 esta forma, la CPU realiza (potencialmente mucho) más trabajo (deencoding / encoding), pero el ancho de banda de la memoria se reduce sustancialmente.

Por lo tanto, si su conjunto de datos es grande, probablemente sea mejor utilizar números enteros de 16 bits. Si su lista está ordenada, puede diseñar un esquema de encoding que implique una encoding diferencial o de longitud de ejecución, lo que reducirá aún más el ancho de banda de la memoria.

Cuando diga 32 bits, supongo que quiere decir x86. La aritmética de 16 bits es bastante lenta: el prefijo de tamaño de operando hace que la deencoding sea realmente lenta. Así que no hagas que tus variables temporales sean cortas int o int16_t.

Sin embargo, x86 puede cargar eficientemente números 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 short int para matrices y campos struct, pero asegúrese de usar int o long para sus variables temp.

Sin embargo, si estoy agregando dos enteros cortos, ¿empaqueta la CPU ambos valores en una sola pasada en paralelo (abarcando así el ancho de banda de 4 bytes del bus)?

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

Si las cargas son su cuello de botella, una gran carga que puede dividir con turnos o máscaras después de la carga puede ayudar. O use SIMD para procesar datos en paralelo sin desempaquetar después de cargarlos en paralelo.

    Intereting Posts