Pull to refresh

Comments 24

Как по мне, то, что некий абстрактный Jitter может принять абстрактное решение создавать все локальные переменные в куче не смотря на потерю производительности — это сферический конь в вакууме.

Лично для меня определение, что «значимые типы — это типы, экземпляры которых располагаются на стеке, а ссылочные — это типы, экземпляры которых располагаются в куче» вполне приемлемо. Просто оно неявно подразумевает, что речь идёт о локальных переменных.

И как иначе определять значимые типы? Что они по значению передаются? Ну так они и по ссылке могут передаваться.
Ну вообще об этом и статья :) Лично Ваше определение ссылочных и значимых типов строится на решении которое принимает программа с закрытым исходным кодом по правилам, которые не специфицированы, что само по себе уже не подразумевает точности определения, в то же время передавать значимые типы по ссылке или по значению Вы решаете явно через ключевые слова.
Я в общем согласен с автором в том, что детали реализации не могут лежать в основе определения концепции.
определение, что «значимые типы — это типы, экземпляры которых располагаются на стеке, а ссылочные — это типы, экземпляры которых располагаются в куче» вполне приемлемо


      private struct STruct
      {
         public int X, Y;
      }

      private class CLass
      {
         public STruct Struct;
      }

      private void Do()
      {
         CLass objecT = new CLass(); // Где будет выделен объект?
         objecT.Struct = new STruct(); //Будет ли выделена память и где она будет выделена?
      }



Как насчёт вот такого варианта?

      private class CLass
      {
         public STruct Struct = new STruct(); //Где память?
      }
Что вы понимаете под «упаковкой» значимого типа?
Боксинг (boxing)?
Локальные значения вообще могут быть на регистрах и ни разу не попасть в стек.
могут быть на регистрах и ни разу не попасть в стек.
Ага, теоретически )
А вот практически джитер при наличии хотя бы одной переменной генерирует пролог, выделяет под переменные место в стеке, запихивает в стек инициализирующие значения и только после этого может использовать регистры.

Вот здесь я кое-что исследовал на эту тему.

Это было во-первых, а во-вторых сказанное — оффтопик к моему комменту.
| Наиболее значимой характеристикой значимого типа является не то как он располагается в памяти, а то как они ведут себя с точки зрения семантики: «значимые типы» всегда передаются «по значению», т.е. копируются.

Ну, так то ссылочные типы тоже по значению передаются.

А вообще, у нас есть спецификация языка, в которой все просто и понятно:
«Assignment to a variable of a value type creates a copy of the value being assigned. This differs from assignment to a variable of a reference type, which copies the reference but not the object identified by the reference.»
Объекты reference типов передаются всё-таки по ссылке без копирования. Не совсем понял что Вы хотели сказать. Идея статьи не протеворечит приведённой вами части спецификации.
Не противоречит, но только больше запутывает того, кто не понимает.

Переменные ссылочного типа передаются по значению (по значению ссылки). Для передачи по ссылке есть ключевое слово ref.

Main()
{
C c = new C();
M1(с);
Console.Write(c == null); // false
M2(ref c);
Console.Write(c == null); // true
}

void M1(C c) { c = null; }

void M2(ref C c) { c = null; }

class C {}
Я изменил формулировку в тексте статьи на "«объекты значимых типов» всегда передаются «по значению», т.е. копируются", но мне кажется что вопрос был не в этом. Исходя из контекста автор подразумевает что объект — это кусок памяти на куче или в стеке занятый полями объекта рассматриваемого типа, а не переменная для ссылочных типов (если говорить грубо указатель на адрес в куче) которая сам по себе значимого типа.
Если уж быть совсем точным, то, переменная указывающая на объект ссылочного типа ведёт себя как объект значимого тиа, но по сути таковым не является.
В одной статье по игровому движку Unity видел совет по оптимизации, который заключается в том, что локальные переменные в часто вызываемых методах делать не локальными переменными, а полями класса.
В Юнити у игровых объектов есть метод Update(), который вызывается каждый кадр (до 60 раз в секунду). И объектов на сцене может быть сотни и тысячи, и у каждого из них каждый кадр вызывается Update(). С одной стороны, при каждом вызове метода на стеке создаются локальные переменные. Но ведь это делается очень быстро.
Предположим, что у нас 1000 объектов, и FPS 60 кадров в секунду, что приводит к 60000 вызовам метода в секунду. Если посчитать, что выделение памяти для локальных переменных занимает 1 такт 1 ГГц процессора, то только на эти выделения памяти для локальных переменных уйдёт до 60 микросекунд — что составляет 0,0006% производительности.
Как вы оцениваете эффективность такой оптимизации? Стоит ли заморачиваться?
я бы с удовольствием, да в бесплатной версии Юнити нет профайлера, а Pro-версии у меня (пока) нет.
Можно в VS померить, конечно же…
Игра динамичная? Если шахматы, то не стоит.

А вообще разработчики если рекомендуют, то они то знают свои слабые места, если есть тормоза — то наверное стоит.
не такая большая разница — динамичная игра или нет, важна частота кадров. Важно, будет ли хоть сколько-то значительный позитивный эффект от отказа от локальных переменных в пользу полей класса.
И это предложение делали не разработчики Юнити, а в некоторых статьях по Юнити на хабре, когда говорили про оптимизацию.
В Юнити используется Mono, и ещё не самая новая.
Здесь я спрашиваю в надежде, что знатоки C# смогут что-то подсказать.
Отличный антипример к данной статье :) Если проблема реально существует, то это именно то где детали реализации конкнретного рантайма на конкретной системе будут играть роль и спецификация C# ничем не поможет :)
В шахматах или какой-нибудь нединамичной игре ты не заметишь разницы в частоте кадров. Можно даже не игнорировать эппловское требование и не повышать частоту с 30 до 60 кадров, для шахмат, кроссвордов и прочих шашек это не надо.
Бред.
Стековые переменные значимых типов располагаются во время компиляции. Изначально методы, содержащие локальные (стековые) переменные содержат пролог, формирующий кадр стека.

push    ebp            ; Стандартный пролог
mov     ebp, esp

push    edi            ; 
push    esi            ; 
push    ebx            ;
                       
sub     esp, 0C        ; вершина стека выше сохранённых регистров на 3*sizeof(DWORD), т.е. будет 3 переменных


Не должно быть разницы.
Первый неявный параметр метода находится в ecx, соотв. обращение к экземплярной переменной будет выглядеть примерно вот так:
mov     eax, [ecx+8] 

а к стековой вот так:
mov     eax, [ebp-10]

Спасибо. Похоже, это и есть ответ на мой вопрос — разницы никакой не будет.
Сейчас уже не могу найти тот совет, похоже, автор убрал его из статьи. Остальные советы были дельные, а этот какой-то странный.
Может, там подразумеваются переменные объектных типов, а не банальные int/float?
нет, там предлагали именно все локальные переменные сделать полями класса. И была ещё какая-то фраза про то, что сборщик мусора замучается эти переменные вычищать. Хотя сборщик мусора совсем не про то.
локальные переменные сделать полями класса

Вероятно, подразумевается — локальные переменные объектных типов. Т.е. «MyUnitySpecialCalcHelper val;» а не «int val;'
Упоминание сборщика мусора однозначно на это указывает — им собираются только reference-типы.
Sign up to leave a comment.

Articles