Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

287
Vistas
Can out and ref be used as temporary variables?

When we use out or ref inside calculations, with multiple assignments and reading from it, what drawbacks does it have? Will it hurt performance?

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;
}

Or should we better be using additional variable:

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;
    }

Update: changed function name from SomeFunction to make it more clear for intended use.

over 4 years ago · Santiago Trujillo
2 Respuestas
Responde la pregunta

0

It turns out that the more calculations we do the more the difference between the performance of both.

I believe this is expected, since here we see an extra level of indirection. ldind and stind operations used to get/set the value for out parameter (indirectly) and ldoc with stloc used to get/set values for local variables.

I think that compiler can't do any optimizations here (at least convert UseOutExtensively to DontUseOutExtensively), because this might change the behavior of the method if some other thread writes to the location of out parameter at the same time the function is executed.

Test

Let me a bit simplify your function so that we concentrate on what we're interested in only:

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;
}

So the functions don't do anything useful, they just swap the same value between the variables. Thus we don't have complex additions and conditions, only get/set an out variable and get/set a local variable.

So the test program is the following:

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);

Results:

Iterations UseOutExtensively DontUseOutExtensively
10^7 918 330
10^8 8850 3331
10^9 92009 34823

We see the more operations we perform the more the difference in performance is noticeable.

over 4 years ago · Santiago Trujillo Denunciar

0

Will it hurt performance?

There is a very slight performance difference, yes. The IL compiler and the JIT compiler can optimize a lot of things here. For example, taking E. Shcherbo's UseOutExtensively method, the x64 instructions without any optimizations looks like this:

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 

Whereas with optimizations enabled, it looks like this:

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, on the other hand, looks like this when optimized.

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

Notice how one thing that couldn't be optimized when using an out variable is the mov instructions. When using a temporary variable, the compiler can keep everything in Control Registers, which are used for math operations. Setting and accessing out variables have to move these values to and from Debug Registers, which takes a little more time.

Plugging those functions into some benchmarking LINQPad code I have on hand, you can see there's a measurable difference in performance as a result, which confirms the results E. Shcherbo noted.

enter image description here

However, that's an extremely contrived case. In something even slightly more complicated like your original code, the difference becomes far less pronounced.

enter image description here

At any rate, you're talking about a difference of milliseconds over millions of iterations, so worrying about which of these approaches to take for performance reasons is almost certainly a premature optimization.

what drawbacks does it have?

Ignoring performance, you should definitely think about how the behavior of your code is impacted by this decision.

Let's say your function threw an exception, for example: would you want the value of result to have been changed in the output, even though no value was returned?

Or what if the value of the variable passed as your out parameter is being read by other threads? Do you want that variable's value to change as the function executes? Maybe you're using that variable to track the progress of your function's execution, in which case there's value to changing the value as you go. But if not, it's probably "tidier" to avoid changing the out variable until you've got a corresponding return value to go with it.

On that note, why use an out parameter at all? You could use a ValueTuple, as suggested by quaabaam, or just a Nullable<> return value. These approaches make your function pure, which makes it more friendly to multi-threaded operations, LINQ expression syntax, lambda expressions, and so forth, with similar performance to the temporary variable approach.

enter image description here

LINQPad Benchmark

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 Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda