Снижение производительности из-за деконструкции кортежей в c #?

#c# #performance #.net-core #types #valuetuple

#c# #Производительность #.net-core #типы #valuetuple

Вопрос:

Если мы сделаем

 var (hello, world) = GetHelloAndWorldStrings();
if (hello == "hello" amp;amp; world == "world")
  Environment.Exit(0);
  

Влечет ли это за собой какие-либо дополнительные затраты, кроме простого выполнения следующего:

 var helloAndWorld = GetHelloAndWorldStrings();
if (helloAndWorld.Hello == "hello" amp;amp; helloAndWorld.World == "world")
  Environment.Exit(0);
  

Или это все синтаксический сахар — код, который в конечном итоге генерируется, всегда использует Item1 и Item2.

Комментарии:

1. участвуйте в скачках

2. Они всегда используют Item1 и Item2 . Имена приведены только для удобства чтения и доступны только во время компиляции. IL показывает, что они используют Item1 и Item2 независимо от того, как вы их назвали (ValueTuple — это структура с полями, подобными любым другим структурам) — например valuetype [System.Runtime]System.ValueTuple``2<int32, int32>::Item1

3. Снижения производительности нет, сгенерированный код идентичен. Вы также можете использовать if (helloAndWorld == ("hello","world"))

Ответ №1:

По сути, это один и тот же сгенерированный IL, однако с незначительными выдаваемыми различиями… скорее всего, они будут jitted и оптимизированы для точно тех же инструкций.

Учитывая

 private (String hello,string world) GetHelloAndWorldStrings() 
{ 
   return ("asdsd","sadfsdf");
}
    
...
        

public int Test1()
{
   var asd = GetHelloAndWorldStrings();
   if (asd.hello == "hello" amp;amp; asd.world == "world")
      return 1;
   return 0;
}

public int Test2()
{
   var (hello, world) = GetHelloAndWorldStrings();
   if (hello == "hello" amp;amp; world == "world")
      return 1;
   return 0;
}
  

В основном было бы выдано как

 public int Test1()
{
    ValueTuple<string, string> helloAndWorldStrings = GetHelloAndWorldStrings();
    if (helloAndWorldStrings.Item1 == "hello" amp;amp; helloAndWorldStrings.Item2 == "world")
    {
        return 1;
    }
    return 0;
}

public int Test2()
{
    ValueTuple<string, string> helloAndWorldStrings = GetHelloAndWorldStrings();
    string item = helloAndWorldStrings.Item1;
    string item2 = helloAndWorldStrings.Item2;
    if (item == "hello" amp;amp; item2 == "world")
    {
        return 1;
    }
    return 0;
}
  

Вы можете проверить IL здесь

Вот пример JIT ASM в выпуске

 C.Test1()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: push esi
    L0004: mov ecx, [0x11198648]
    L000a: mov esi, [0x1119864c]
    L0010: mov edx, [0x11198650]
    L0016: call System.String.Equals(System.String, System.String)
    L001b: test eax, eax
    L001d: je short L0038
    L001f: mov edx, [0x11198654]
    L0025: mov ecx, esi
    L0027: call System.String.Equals(System.String, System.String)
    L002c: test eax, eax
    L002e: je short L0038
    L0030: mov eax, 1
    L0035: pop esi
    L0036: pop ebp
    L0037: ret
    L0038: xor eax, eax
    L003a: pop esi
    L003b: pop ebp
    L003c: ret
  

против

 C.Test2()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: push esi
    L0004: mov ecx, [0x11198648]
    L000a: mov esi, [0x1119864c]
    L0010: mov edx, [0x11198650]
    L0016: call System.String.Equals(System.String, System.String)
    L001b: test eax, eax
    L001d: je short L0038
    L001f: mov edx, [0x11198654]
    L0025: mov ecx, esi
    L0027: call System.String.Equals(System.String, System.String)
    L002c: test eax, eax
    L002e: je short L0038
    L0030: mov eax, 1
    L0035: pop esi
    L0036: pop ebp
    L0037: ret
    L0038: xor eax, eax
    L003a: pop esi
    L003b: pop ebp
    L003c: ret
  

Короче говоря, чистая выгода от беспокойства об этом … минус 5 минут вашей жизни, которые вы никогда не вернете

Комментарии:

1. Если вы добавите 2 int свойства в набор кортежей ( one и two ), я думаю, разница станет более понятной, а JIT (JIT ASM) в release, наконец, покажет некоторые различия в сгенерированном коде ( Test1 содержит меньше инструкций).

2. @SpiritBob, вероятно, из-за копирования типа значения.

3.Да, по сравнению с простой передачей по ссылке. У меня глупый вопрос — как вы расшифровали то, Test2 что действительно выдает копию структуры кортежа значений? ( string item = helloAndWorldStrings.Item1; string item2 = helloAndWorldStrings.Item1; )

Ответ №2:

Есть третий вариант, эквивалентный первому:

   public int Test3()
  {
     var asd = GetHelloAndWorldStrings();
     if (asd == ("hello", "world"))
        return 1;
     return 0;
  }
  

Это в равной степени переводится в (sharplab.io):

 public int Test3()
{
    ValueTuple<string, string> helloAndWorldStrings = GetHelloAndWorldStrings();
    if (helloAndWorldStrings.Item1 == "hello" amp;amp; helloAndWorldStrings.Item2 == "world")
    {
        return 1;
    }
    return 0;
}
  

(sharplab.io)

 .method public hidebysig 
    instance int32 Test3 () cil managed 
{
    // Method begins at RVA 0x2064
    // Code size 47 (0x2f)
    .maxstack 2
    .locals init (
        [0] valuetype [System.Private.CoreLib]System.ValueTuple`2<string, string>
    )

    IL_0000: ldarg.0
    IL_0001: call instance valuetype [System.Private.CoreLib]System.ValueTuple`2<string, string> C::GetHelloAndWorldStrings()
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: ldfld !0 valuetype [System.Private.CoreLib]System.ValueTuple`2<string, string>::Item1
    IL_000d: ldstr "hello"
    IL_0012: call bool [System.Private.CoreLib]System.String::op_Equality(string, string)
    IL_0017: brfalse.s IL_002d

    IL_0019: ldloc.0
    IL_001a: ldfld !1 valuetype [System.Private.CoreLib]System.ValueTuple`2<string, string>::Item2
    IL_001f: ldstr "world"
    IL_0024: call bool [System.Private.CoreLib]System.String::op_Equality(string, string)
    IL_0029: brfalse.s IL_002d

    IL_002b: ldc.i4.1
    IL_002c: ret

    IL_002d: ldc.i4.0
    IL_002e: ret
} // end of method C::Test3