En C #, ¿hay alguna diferencia de rendimiento significativa para usar UInt32 frente a Int32?

StackOverflow https://stackoverflow.com/questions/306602

  •  08-07-2019
  •  | 
  •  

Pregunta

Estoy transfiriendo una aplicación existente a C # y quiero mejorar el rendimiento siempre que sea posible. Muchos contadores de bucle y referencias de matriz existentes se definen como System.UInt32, en lugar del Int32 que hubiera utilizado.

¿Hay alguna diferencia de rendimiento significativa para usar UInt32 vs Int32?

¿Fue útil?

Solución

No creo que haya consideraciones de rendimiento, aparte de la posible diferencia entre aritmética con y sin signo a nivel de procesador, pero en ese momento creo que las diferencias son discutibles.

La mayor diferencia está en el cumplimiento de CLS ya que los tipos sin firmar no son compatibles con CLS ya que no todos los idiomas los admiten.

Otros consejos

La respuesta corta es " No. Cualquier impacto en el rendimiento será insignificante " ;.

La respuesta correcta es "Depende".

Una mejor pregunta es: "¿Debo usar uint cuando estoy seguro de que no necesito un signo?"

La razón por la que no puede dar un definitivo "sí" o "no" con respecto al rendimiento se debe a que la plataforma objetivo determinará en última instancia el rendimiento. Es decir, el rendimiento está dictado por el procesador que vaya a ejecutar el código y las instrucciones disponibles. Su código .NET se compila en Intermediate Language (IL o Bytecode). Estas instrucciones son compiladas en la plataforma de destino por compilador Just-In-Time (JIT) como parte del Common Language Runtime (CLR). No puede controlar ni predecir qué código se generará para cada usuario.

Entonces, sabiendo que el hardware es el árbitro final del rendimiento, la pregunta es: "¿Qué tan diferente es el código que genera .NET para un entero con signo y sin signo?" y "¿La diferencia afecta mi aplicación y mis plataformas de destino?"

La mejor manera de responder estas preguntas es realizar una prueba.

class Program
{
  static void Main(string[] args)
  {
    const int iterations = 100;
    Console.WriteLine(
Signed:      00:00:00.5066966

Unsigned:    00:00:00.5052279
quot;Signed: {Iterate(TestSigned, iterations)}"); Console.WriteLine(<*>quot;Unsigned: {Iterate(TestUnsigned, iterations)}"); Console.Read(); } private static void TestUnsigned() { uint accumulator = 0; var max = (uint)Int32.MaxValue; for (uint i = 0; i < max; i++) ++accumulator; } static void TestSigned() { int accumulator = 0; var max = Int32.MaxValue; for (int i = 0; i < max; i++) ++accumulator; } static TimeSpan Iterate(Action action, int count) { var elapsed = TimeSpan.Zero; for (int i = 0; i < count; i++) elapsed += Time(action); return new TimeSpan(elapsed.Ticks / count); } static TimeSpan Time(Action action) { var sw = new Stopwatch(); sw.Start(); action(); sw.Stop(); return sw.Elapsed; } }

Los dos métodos de prueba, TestSigned y TestUnsigned , cada uno realiza ~ 2 millones de iteraciones de un incremento simple en un entero con signo y sin signo, respectivamente. El código de prueba ejecuta 100 iteraciones de cada prueba y promedia los resultados. Esto debería eliminar cualquier inconsistencia potencial. Los resultados en mi i7-5960X compilado para x64 fueron:

<*>

Estos resultados son casi idénticos, pero para obtener una respuesta definitiva, realmente necesitamos mirar el código de bytes generado para el programa. Podemos usar ILDASM como parte del .NET SDK para inspeccionar el código en el ensamblado generado por el compilador.

 Bytecode

Aquí, podemos ver que el compilador de C # favorece los enteros con signo y realmente realiza la mayoría de las operaciones de forma nativa como enteros con signo y solo trata el valor en memoria como sin signo cuando se compara para la rama (también conocido como un salto o si). A pesar de que estamos usando un número entero sin signo tanto para el iterador como para el acumulador en TestUnsigned , el código es casi idéntico al método TestSigned , excepto por una sola instrucción: IL_0016 . Un vistazo rápido a la especificación ECMA describe la diferencia:

  

blt.un.s:   Ramifique al objetivo si es menor que (sin firmar o sin ordenar), forma corta.

     

blt.s:   Ramifique al objetivo si es menor que, forma corta.

Al ser una instrucción tan común, es seguro asumir que la mayoría de los procesadores modernos de alta potencia tendrán instrucciones de hardware para ambas operaciones y es muy probable que se ejecuten en el mismo número de ciclos, pero esto no está garantizado . Un procesador de baja potencia puede tener menos instrucciones y no tener una rama para int sin firmar. En este caso, el compilador JIT puede tener que emitir múltiples instrucciones de hardware (una conversión primero, luego una rama, por ejemplo) para ejecutar la instrucción blt.un.s IL. Incluso si este es el caso, estas instrucciones adicionales serían básicas y probablemente no afectarían el rendimiento

No he hecho ninguna investigación al respecto en .NET, pero en los viejos tiempos de Win32 / C ++, si querías emitir un " firmado int " a un " firmado mucho " ;, la CPU tuvo que ejecutar una operación para extender el signo. Para emitir un " unsigned int " a un '' unsigned long '', solo tenía cosas cero en los bytes superiores. Los ahorros fueron del orden de un par de ciclos de reloj (es decir, tendrías que hacerlo miles de millones de veces para tener una diferencia perceptible)

No hay diferencia, en cuanto al rendimiento. Los cálculos enteros simples son bien conocidos y las CPU modernas están altamente optimizadas para realizarlas rápidamente.

Estos tipos de optimizaciones rara vez valen la pena. Utilice el tipo de datos más apropiado para la tarea y déjelo así. Si esto toca una base de datos, probablemente podría encontrar una docena de ajustes en el diseño de la base de datos, la sintaxis de consulta o la estrategia de indexación que compensaría una optimización de código en C # en unos cientos de órdenes de magnitud.

Va ??a asignar la misma cantidad de memoria de cualquier manera (aunque puede almacenar un valor mayor, ya que no ahorra espacio para el signo). Por lo tanto, dudo que vea una diferencia de "rendimiento", a menos que use valores grandes / valores negativos que harán que una opción u otra explote.

esto no tiene que ver realmente con el rendimiento sino con los requisitos para el contador de bucle.

Quizás haya muchas iteraciones para completar

        Console.WriteLine(Int32.MaxValue);      // Max interation 2147483647
        Console.WriteLine(UInt32.MaxValue);     // Max interation 4294967295

El int sin firmar puede estar allí por alguna razón.

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