C#: Создает ли Deconstruct (...) дополнительные назначения нежелательной почты в скомпилированном выводе? - PullRequest
2 голосов
/ 06 мая 2020

Я проверял, вызывает ли деконструкция создание экземпляров дополнительных объектов в куче, поскольку я делаю что-то в области, где мне нужно иметь как можно меньшее давление G C. Это код, который я пробовал:

using System;

public struct Pair
{
    public int A;
    public int B;

    public Pair(int a, int b) 
    {
        A = a;
        B = b;
    }

    public void Deconstruct(out int a, out int b)
    {
        a = A;
        b = B;
    }
}

public class Program
{
    public static void Main()
    {
        Pair pair = new Pair(1, 2);

        // Line of interest
        (int a, int b) = pair;

        Console.WriteLine(a + " " + b);
    }
}

Я прогнал его через SharpLab , чтобы увидеть, что C# делает для меня, и он делает следующее:

public static void Main()
{
    Pair pair = new Pair(1, 2);
    Pair pair2 = pair;
    int a;
    int b;
    pair2.Deconstruct(out a, out b);
    int num = a;
    int num2 = b;
    Console.WriteLine(num.ToString() + " " + num2.ToString());
}

Это ответило на мой первоначальный вопрос о том, нужно ли мне беспокоиться о дополнительных выделениях ... но, что еще более интересно, режим выпуска (поскольку выше приведен отладочный) имеет:

public static void Main()
{
    int a;
    int b;
    new Pair(1, 2).Deconstruct(out a, out b);
    int num = a;
    int num2 = b;
    Console.WriteLine(num.ToString() + " " + num2.ToString());
}

Однако это может быть уменьшено до (это я делаю дополнительную обрезку переменных num и num2):

public static void Main()
{
    int a;
    int b;
    new Pair(1, 2).Deconstruct(out a, out b);
    Console.WriteLine(a.ToString() + " " + b.ToString());
}

Это интересный вопрос, так как нет возможности дополнительного выделения стека двух целых чисел будет означать что угодно с точки зрения производительности моей программы. Хотя ради интереса я попытался посмотреть на IL Main и получить:

// Methods
.method public hidebysig static 
    void Main () cil managed 
{
    // Method begins at RVA 0x2074
    // Code size 54 (0x36)
    .maxstack 3
    .locals init (
        [0] int32,
        [1] int32,
        [2] valuetype Pair,
        [3] int32,
        [4] int32
    )

    IL_0000: ldc.i4.1
    IL_0001: ldc.i4.2
    IL_0002: newobj instance void Pair::.ctor(int32, int32)
    IL_0007: stloc.2
    IL_0008: ldloca.s 2
    IL_000a: ldloca.s 3
    IL_000c: ldloca.s 4
    IL_000e: call instance void Pair::Deconstruct(int32&, int32&)
    IL_0013: ldloc.3
    IL_0014: stloc.0
    IL_0015: ldloc.s 4
    IL_0017: stloc.1
    IL_0018: ldloca.s 0
    IL_001a: call instance string [System.Private.CoreLib]System.Int32::ToString()
    IL_001f: ldstr " "
    IL_0024: ldloca.s 1
    IL_0026: call instance string [System.Private.CoreLib]System.Int32::ToString()
    IL_002b: call string [System.Private.CoreLib]System.String::Concat(string, string, string)
    IL_0030: call void [System.Console]System.Console::WriteLine(string)
    IL_0035: ret
} // end of method Program::Main

, а JIT ASM -

Program.Main()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: push edi
    L0004: push esi
    L0005: push ebx
    L0006: sub esp, 0x20
    L0009: lea edi, [ebp-0x28]
    L000c: call 0x68233ac
    L0011: mov eax, ebp
    L0013: mov [ebp-0x14], eax
    L0016: push 0x3
    L0018: mov dword [ebp-0x20], 0x6cce29c
    L001f: mov eax, esp
    L0021: mov [ebp-0x1c], eax
    L0024: lea eax, [0x146004df]
    L002a: mov [ebp-0x18], eax
    L002d: mov byte [esi+0x8], 0x0
    L0031: call dword [0x6cce680]
    L0037: mov byte [esi+0x8], 0x1
    L003b: cmp dword [0x621e5188], 0x0
    L0042: jz L0049
    L0044: call 0x62023890
    L0049: xor eax, eax
    L004b: mov [ebp-0x18], eax
    L004e: mov byte [esi+0x8], 0x1
    L0052: mov eax, [ebp-0x24]
    L0055: mov [esi+0xc], eax
    L0058: lea esp, [ebp-0xc]
    L005b: pop ebx
    L005c: pop esi
    L005d: pop edi
    L005e: pop ebp
    L005f: ret

Однако я немного не в своей лиге, когда это доходит до сборочной части.

Есть ли какие-то промежуточные звенья, как было сказано ранее? Меня интересует, были ли

int num = a;
int num2 = b;

полностью оптимизированы или нет. Меня также интересует, почему компилятор создает промежуточные звенья в выпускной версии (есть ли причина?) Или это артефакт декомпиляции из SharpLab.

1 Ответ

1 голос
/ 07 мая 2020

Это для игрового движка, где паузы G C плохие.

Что касается стоимости паузы G C, то совершенно ясно: вы делаете программирование в реальном времени . И я могу только сказать, что программирование в реальном времени и управление памятью G C несовместимы.

Возможно, вы сможете решить эту проблему, но будет еще одна. А потом еще один. А потом все больше и больше, пока вы наконец не поймете, что зашли в тупик. Чем раньше вы поймете, что зашли в тупик, тем больше работы сможете спасти.

Исторически игровые движки, особенно код рисования, представляют собой неуправляемый код, который использует прямое управление памятью. . NET Код битности агности c. Но как только вы использовали профессиональный код для рисования, вы в основном были привязаны к его битности. Однако я не могу сказать, было ли это просто инерцией (мы использовали этот движок раньше и не будем его менять только потому, что это сделали язык / среда выполнения), или это было важно для производительности.

Я также не могу сказать, как большая часть кода рисования Unity использует неуправляемый код. Но поскольку игры Unity должны быть созданы для определенных c платформ, я собираюсь предположить: больше, чем ничего. Так что вполне может быть, что при создании игровых движков невозможно не войти в неуправляемый код.

...