Распределение кучи классов

Я провожу несколько тестов распределения и меня смущают результаты распределения.
Предположим, что это тест, в котором класс создается несколько раз, его ссылка никогда не выходит за пределы метода.

 [Benchmark]
 public void AllocateClass()
 {
     for (int i = 0; i < count; i++)
     {
         var s = new SampleClass();             
     }           
 }

Когда тесты завершаются, общий объем выделенной памяти равен нулю. Теперь второй тест, в котором ссылка выходит за пределы метода.

SampleClass s;
[Benchmark]
public void AllocateClass()
{
    for (int i = 0; i < count; i++)
    {
        s = new SampleClass();                
    }           
}

При втором тесте память распределяется как положено. Поскольку класс является ссылочным типом, я ожидаю, что он будет выделен в куче независимо от того, экранируется его ссылка или нет, это какая-то оптимизация компилятора?

ваш первый фрагмент по сути ничего не делает. Я предполагаю, что JIT-компилятор просто пропускает все выделение.

Raildex 23.08.2024 10:34

^^ Я тоже так думаю. Но я думаю, это не ваш настоящий эталон, не так ли?

Fildor 23.08.2024 10:35

Возможно, вы захотите вернуть коллекцию и избежать отложенного выполнения: benchmarkdotnet.org/articles/samples/… чтобы получить правильные тесты...

Fildor 23.08.2024 10:38

Имейте в виду, что объект можно собрать, даже если его собственный конструктор все еще работает. При условии, что можно доказать, что ничто не пытается разыменовать его позже.

Damien_The_Unbeliever 23.08.2024 10:39

Вместо использования эталонного теста (и нераскрытых параметров сборки проекта и времени выполнения...) вам следует использовать счетчики производительности GC для получения фактических данных.

Dai 23.08.2024 10:39

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

NullReference 23.08.2024 10:39

@Damien_The_Unbeliever «Имейте в виду, что объект можно собрать, даже если его собственный конструктор все еще работает». - Пожалуйста, поподробнее...

Dai 23.08.2024 10:40

«Не уверен, что вы подразумеваете под реальным тестом» - мне было интересно, не является ли это всего лишь упрощением другого теста, который дал неожиданные цифры, поскольку это и так не имеет особого смысла. Создание объекта и немедленное выход его за рамки - я ожидаю, что произойдет всякая магия компилятора/JIT, которую будет трудно отследить. Если вам нужно, чтобы его не собирали до завершения измерения, сохраните (жесткий) эталон.

Fildor 23.08.2024 10:43
Сравните. JIT действительно полностью удалил конструкцию тривиального класса.
canton7 23.08.2024 10:45

@canton7 canton7 Спасибо, так что это действительно связано с оптимизацией компилятора.

NullReference 23.08.2024 10:47

@NullReference Оптимизация времени выполнения: компилятор не делает подобных вещей. Переключите раскрывающийся список вверху на «IL», чтобы увидеть, что выдает компилятор.

canton7 23.08.2024 10:49

Также классы не обязательно размещать в куче. Традиционно так и делается, но это не гарантия. Среда выполнения начинает экспериментировать с ограниченными формами escape-анализа, которые могут выделить класс в стеке в определенных ситуациях, если можно доказать, что ссылка на него никогда не выходит за пределы текущего метода.

canton7 23.08.2024 10:51

@Dai Класс может быть освобожден/завершен, как только он достигнет точки, в которой он больше никогда не будет использоваться, независимо от того, находятся ли какие-либо ссылки на него в области видимости (область действия не имеет значения), в том числе когда выполняется один из его собственных методов. В том числе и строительство. Смотрите здесь. Это, конечно, сопряжено с множеством сложностей, связанных с освобождением неуправляемых ресурсов, пока они еще используются или еще не инициализированы, поэтому мы рекомендуем людям избегать финализаторов и использовать SafeHandles.

canton7 23.08.2024 10:55
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
13
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я предполагаю, что вы запускаете свой тест в режиме выпуска, и наблюдаемое вами поведение, скорее всего, является оптимизацией компилятора.

Вы можете использовать https://sharplab.io, чтобы провести несколько экспериментов и посмотреть сгенерированные IL и asm.

Например, JIT Asm от SharpLab в режиме Release:

Метод с исключенным распределением:

C.AllocateClass(Int32)
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: xor eax, eax
    L0005: test edx, edx
    L0007: jle short L000e
    L0009: inc eax
    L000a: cmp eax, edx
    L000c: jl short L0009
    L000e: pop ebp
    L000f: ret

Метод без удаленного кода:

C.AllocateClass(Int32)
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: sub esp, 0x18
    L0006: xor eax, eax
    L0008: mov [ebp-0xc], eax
    L000b: mov [ebp-0x10], eax
    L000e: mov [ebp-0x14], eax
    L0011: mov [ebp-0x18], eax
    L0014: mov [ebp-4], ecx
    L0017: mov [ebp-8], edx
    L001a: cmp dword ptr [0x2bb6c18c], 0
    L0021: je short L0028
    L0023: call 0x71f90f30
    L0028: nop
    L0029: xor ecx, ecx
    L002b: mov [ebp-0xc], ecx
    L002e: nop
    L002f: jmp short L0067
    L0031: nop
    L0032: mov ecx, 0x2bb6ca04
    L0037: call 0x0704300c
    L003c: mov [ebp-0x14], eax
    L003f: mov ecx, [ebp-4]
    L0042: mov ecx, [ecx+4]
    L0045: mov [ebp-0x18], ecx
    L0048: mov ecx, [ebp-0x14]
    L004b: call dword ptr [0x2bb6ca24]
    L0051: push dword ptr [ebp-0x14]
    L0054: mov ecx, [ebp-0x18]
    L0057: mov edx, [ebp-0xc]
    L005a: call System.Runtime.CompilerServices.CastHelpers.StelemRef(System.Array, IntPtr, System.Object)
    L005f: nop
    L0060: mov eax, [ebp-0xc]
    L0063: inc eax
    L0064: mov [ebp-0xc], eax
    L0067: mov ecx, [ebp-0xc]
    L006a: cmp ecx, [ebp-8]
    L006d: setl cl
    L0070: movzx ecx, cl
    L0073: mov [ebp-0x10], ecx
    L0076: cmp dword ptr [ebp-0x10], 0
    L007a: jne short L0031
    L007c: nop
    L007d: mov esp, ebp
    L007f: pop ebp
    L0080: ret

«Полагаю, вы запускаете свой тест в режиме выпуска» — BenchmarkDotnet выйдет из строя с сообщением об ошибке, если вы не работаете в режиме выпуска.

Fildor 23.08.2024 11:03

Другие вопросы по теме