Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

288
Visualizações
¿Se pueden usar out y ref como variables temporales?

Cuando usamos cálculos externos o internos , con múltiples asignaciones y lecturas, ¿qué inconvenientes tiene? ¿Perjudicará el rendimiento?

 static bool TrySomeFunction(int x, int y, out int result) { result = 8; for (int i = 0; i < x; i++) { result += result + x; if (result == y) return false; } return true; }

O deberíamos usar una variable adicional:

 static bool TrySomeFunction(int x, int y, out int result) { int temp = 8; for (int i = 0; i < x; i++) { temp += temp + x; if (temp == y) { result = 0; return false; } } result = temp; return true; }

Actualización: cambió el nombre de la función de SomeFunction para que sea más claro para el uso previsto.

over 4 years ago · Santiago Trujillo
2 Respostas
Responde à pergunta

0

Resulta que cuantos más cálculos hacemos, mayor es la diferencia entre el rendimiento de ambos.

Creo que esto es de esperar, ya que aquí vemos un nivel extra de indirección. Las operaciones ldind y stind se usan para obtener/establecer el valor del parámetro out (indirectamente) y ldoc con stloc se usa para obtener/establecer valores para las variables locales.

Creo que el compilador no puede hacer ninguna optimización aquí (al menos convertir UseOutExtensively a DontUseOutExtensively ), porque esto podría cambiar el comportamiento del método si algún otro subproceso escribe en la ubicación del parámetro out al mismo tiempo que se ejecuta la función.

Prueba

Permítanme simplificar un poco su función para que nos concentremos solo en lo que nos interesa:

 void UseOutExtensively(out int result) { result = 0; for (int i = 0; i < 100; i++) { int temp = result; result = temp; } } void DontUseOutExtensively(out int result) { int temp = 8; for (int i = 0; i < 100; i++) { int anotherTemp = temp; temp = anotherTemp; } result = temp; }

Entonces, las funciones no hacen nada útil, simplemente intercambian el mismo valor entre las variables. Por lo tanto, no tenemos adiciones y condiciones complejas, solo obtener/establecer una variable out y obtener/establecer una variable local.

Así que el programa de prueba es el siguiente:

 int Iterations = 10000000; // we'll try 10^7, 10^8 && 10^9 Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) UseOutExtensively(out int result); Console.WriteLine("Using out extensively: {0}", sw.ElapsedMilliseconds); sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) DontUseOutExtensively(out int result); Console.WriteLine("Don't use out extensively: {0}", sw.ElapsedMilliseconds);

Resultados:

iteraciones UseOutExtensively No Usar Extensivamente
10^7 918 330
10^8 8850 3331
10^9 92009 34823

Vemos que cuantas más operaciones realizamos, más se nota la diferencia en el rendimiento.

over 4 years ago · Santiago Trujillo Relatório

0

¿Perjudicará el rendimiento?

Hay una diferencia de rendimiento muy leve, eso sí. El compilador IL y el compilador JIT pueden optimizar muchas cosas aquí. Por ejemplo, tomando el método UseOutExtensively de E. Shcherbo , las instrucciones x64 sin optimizaciones se ven así:

 L0000 push rbp L0001 sub rsp, 0x30 L0005 lea rbp, [rsp+0x30] L000a xor eax, eax L000c mov [rbp-0xc], rax L0010 mov [rbp-4], eax L0013 mov [rbp+0x10], rcx L0017 mov [rbp+0x18], rdx L001b cmp dword ptr [0x7ff84f32c2f0], 0 L0022 je short L0029 L0024 call 0x00007ff8adecca10 L0029 nop L002a mov rax, [rbp+0x18] L002e xor edx, edx L0030 mov [rax], edx L0032 mov [rbp-4], edx L0035 nop L0036 jmp short L0054 L0038 nop L0039 mov rax, [rbp+0x18] L003d mov eax, [rax] L003f mov [rbp-8], eax L0042 mov rax, [rbp+0x18] L0046 mov edx, [rbp-8] L0049 mov [rax], edx L004b nop L004c mov eax, [rbp-4] L004f inc eax L0051 mov [rbp-4], eax L0054 cmp dword ptr [rbp-4], 0x64 L0058 setl al L005b movzx eax, al L005e mov [rbp-0xc], eax L0061 cmp dword ptr [rbp-0xc], 0 L0065 jne short L0038 L0067 nop L0068 add rsp, 0x30 L006c pop rbp L006d ret

Mientras que con las optimizaciones habilitadas, se ve así:

 L0000 xor eax, eax L0002 mov [rdx], eax L0004 mov ecx, [rdx] L0006 mov [rdx], ecx L0008 inc eax L000a cmp eax, 0x64 L000d jl short L0004 L000f ret

DontUseOutExtensively , por otro lado, se ve así cuando está optimizado.

 L0000 xor eax, eax L0002 inc eax L0004 cmp eax, 0x64 L0007 jl short L0002 L0009 mov dword ptr [rdx], 8 L000f ret

Observe cómo una cosa que no se pudo optimizar al usar una variable out son las instrucciones de mov . Cuando se usa una variable temporal, el compilador puede mantener todo en Registros de control, que se usan para operaciones matemáticas. La configuración y el acceso out variables tienen que mover estos valores hacia y desde los registros de depuración, lo que lleva un poco más de tiempo.

Al conectar esas funciones en un código LINQPad de evaluación comparativa que tengo a mano, puede ver que hay una diferencia medible en el rendimiento como resultado, lo que confirma los resultados que señaló E. Shcherbo.

ingrese la descripción de la imagen aquí

Sin embargo, ese es un caso extremadamente artificial. En algo incluso un poco más complicado como su código original, la diferencia se vuelve mucho menos pronunciada.

ingrese la descripción de la imagen aquí

En cualquier caso, está hablando de una diferencia de milisegundos en millones de iteraciones, por lo que preocuparse por cuál de estos enfoques tomar por razones de rendimiento es casi seguro una optimización prematura.

que inconvenientes tiene

Ignorando el rendimiento, definitivamente debe pensar en cómo el comportamiento de su código se ve afectado por esta decisión.

Digamos que su función lanzó una excepción, por ejemplo: ¿le gustaría que el valor del result se cambiara en la salida, aunque no se devolviera ningún valor?

¿O qué pasa si el valor de la variable pasada como su parámetro de out está siendo leído por otros subprocesos? ¿Quiere que el valor de esa variable cambie a medida que se ejecuta la función? Tal vez esté usando esa variable para rastrear el progreso de la ejecución de su función, en cuyo caso hay valor para cambiar el valor a medida que avanza. Pero si no, probablemente sea "más ordenado" evitar cambiar la variable out hasta que tenga un valor de retorno correspondiente.

En ese sentido, ¿por qué usar un parámetro de out ? Puede usar un ValueTuple, como lo sugiere quaabaam , o simplemente un valor de retorno Nullable<> . Estos enfoques hacen que su función sea pura , lo que la hace más compatible con operaciones de subprocesos múltiples, sintaxis de expresiones LINQ, expresiones lambda, etc., con un rendimiento similar al enfoque de variable temporal.

ingrese la descripción de la imagen aquí

Evaluación comparativa de LINQPad

 static (bool Found, int Result) SomeFunctionValueTuple(int x, int y) { int temp = 8; for (int i = 0; i < x; i++) { temp += temp + x; if (temp == y) { return (false, 0); } } return (true, temp); }
 static int? SomeFunctionNullable(int x, int y) { int temp = 8; for (int i = 0; i < x; i++) { temp += temp + x; if (temp == y) { return null; } } return temp; }
over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda