Анализ Escape в VM.NET CLR

Есть ли какой-либо анализ эвакуации, выполняемый компилятором CLR/JIT? Например, в Java кажется, что переменная цикла объект, выделенный в цикле, который не выходит из цикла, выделяется в стеке, а не в кучу (см. Анализ Escape в Java).

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

class Foo 
{ 
   int number;
   Foo(int number) { this.number = number; }
   public override string ToString() { return number.ToString(); }
}

for (int i = 0; i < 10000000; i++)
{
   Foo foo = new Foo(i);
   Console.WriteLine(foo.ToString());
}

Ответы

Ответ 1

Если вы имеете в виду объект (new Foo(i);), то я понимаю, что нет: это никогда не выделяется в стеке; однако он будет умирать в нулевой генерации, поэтому будет очень эффективно собирать. Я не утверждаю, что знаю каждый темный и прочный угол CLI, но мне не известно ни одного сценария в С#, который приведет к тому, что в стеке будет выделен управляемый ссылочный тип (например, stackalloc действительно не учитываются и весьма специфичны). Очевидно, что в С++ у вас есть еще несколько параметров, но тогда это не управляемый экземпляр.

Интересно, что в MonoTouch/AOT он может быть собран немедленно, но это не основная виртуальная машина CLI (и для очень специфического сценария).

Что касается переменной - которая обычно находится в стеке (и повторно используется для каждой итерации цикла), но она может быть не. Например, если это "блок-блок итератора", то все не удаленные локальные переменные являются фактически полями на машине-генераторе, созданном компилятором. Чаще всего, если переменная "захвачена" (в анонимный метод или выражение лямбда, оба из которых образуют замыкания), тогда переменная преобразуется в поле в контексте захвата компилятора, и является отдельной для каждой итерации цикла (поскольку foo объявляется внутри цикла). Это означает, что каждый из них разделен на кучу.

Что касается i (переменная цикла) - если он захвачен, он становится еще интереснее

  • в С# 1.2 захватов не было, но по спецификации переменная цикла является технически пер-итерацией
  • в С# 2.0 до 4.0, переменная цикла является общей (вызывает широко известный вопрос об уходе /foreach )
  • в С# 5.0 и выше переменная цикла повторяется снова.

это только имеет значение, когда переменная захватывается, но изменяет семантику точно, как она проявляется в контексте capture

Ответ 2

Тип значения может быть выделен в стеке (не всегда), но то же самое не относится к экземплярам ссылочных типов. Фактически:

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

(Эрик Липперт: Правда о типах значений)

Также Стек представляет собой деталь реализации делает хорошее чтение.

Ответ 3

В то время как x86 JIT хорош для "inlining" valuetypes, ваш фрагмент не будет квалифицироваться, поскольку метод ToString будет виртуальным вызовом на объект в штучной упаковке. Изменить: Это может быть не так, поскольку вы не переопределяете ToString.

x64 JIT, однако, не делает этого вообще из моих экспериментов.

Edit:

Если возможно, проверьте свой код как на x86, так и на x64.