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(); //Где память?
}
Локальные значения вообще могут быть на регистрах и ни разу не попасть в стек.
могут быть на регистрах и ни разу не попасть в стек.Ага, теоретически )
А вот практически джитер при наличии хотя бы одной переменной генерирует пролог, выделяет под переменные место в стеке, запихивает в стек инициализирующие значения и только после этого может использовать регистры.
Вот здесь я кое-что исследовал на эту тему.
Это было во-первых, а во-вторых сказанное — оффтопик к моему комменту.
| Наиболее значимой характеристикой значимого типа является не то как он располагается в памяти, а то как они ведут себя с точки зрения семантики: «значимые типы» всегда передаются «по значению», т.е. копируются.
Ну, так то ссылочные типы тоже по значению передаются.
А вообще, у нас есть спецификация языка, в которой все просто и понятно:
«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.»
Ну, так то ссылочные типы тоже по значению передаются.
А вообще, у нас есть спецификация языка, в которой все просто и понятно:
«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 {}
Переменные ссылочного типа передаются по значению (по значению ссылки). Для передачи по ссылке есть ключевое слово 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% производительности.
Как вы оцениваете эффективность такой оптимизации? Стоит ли заморачиваться?
В Юнити у игровых объектов есть метод Update(), который вызывается каждый кадр (до 60 раз в секунду). И объектов на сцене может быть сотни и тысячи, и у каждого из них каждый кадр вызывается Update(). С одной стороны, при каждом вызове метода на стеке создаются локальные переменные. Но ведь это делается очень быстро.
Предположим, что у нас 1000 объектов, и FPS 60 кадров в секунду, что приводит к 60000 вызовам метода в секунду. Если посчитать, что выделение памяти для локальных переменных занимает 1 такт 1 ГГц процессора, то только на эти выделения памяти для локальных переменных уйдёт до 60 микросекунд — что составляет 0,0006% производительности.
Как вы оцениваете эффективность такой оптимизации? Стоит ли заморачиваться?
А вы померяйте :)
Игра динамичная? Если шахматы, то не стоит.
А вообще разработчики если рекомендуют, то они то знают свои слабые места, если есть тормоза — то наверное стоит.
А вообще разработчики если рекомендуют, то они то знают свои слабые места, если есть тормоза — то наверное стоит.
не такая большая разница — динамичная игра или нет, важна частота кадров. Важно, будет ли хоть сколько-то значительный позитивный эффект от отказа от локальных переменных в пользу полей класса.
И это предложение делали не разработчики Юнити, а в некоторых статьях по Юнити на хабре, когда говорили про оптимизацию.
В Юнити используется Mono, и ещё не самая новая.
Здесь я спрашиваю в надежде, что знатоки C# смогут что-то подсказать.
И это предложение делали не разработчики Юнити, а в некоторых статьях по Юнити на хабре, когда говорили про оптимизацию.
В Юнити используется Mono, и ещё не самая новая.
Здесь я спрашиваю в надежде, что знатоки C# смогут что-то подсказать.
Отличный антипример к данной статье :) Если проблема реально существует, то это именно то где детали реализации конкнретного рантайма на конкретной системе будут играть роль и спецификация C# ничем не поможет :)
В шахматах или какой-нибудь нединамичной игре ты не заметишь разницы в частоте кадров. Можно даже не игнорировать эппловское требование и не повышать частоту с 30 до 60 кадров, для шахмат, кроссвордов и прочих шашек это не надо.
Бред.
Стековые переменные значимых типов располагаются во время компиляции. Изначально методы, содержащие локальные (стековые) переменные содержат пролог, формирующий кадр стека.
Не должно быть разницы.
Первый неявный параметр метода находится в ecx, соотв. обращение к экземплярной переменной будет выглядеть примерно вот так:
а к стековой вот так:
Стековые переменные значимых типов располагаются во время компиляции. Изначально методы, содержащие локальные (стековые) переменные содержат пролог, формирующий кадр стека.
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?
нет, там предлагали именно все локальные переменные сделать полями класса. И была ещё какая-то фраза про то, что сборщик мусора замучается эти переменные вычищать. Хотя сборщик мусора совсем не про то.
Sign up to leave a comment.
Детали реализации стека — часть первая