Комментарии 24
Как по мне, то, что некий абстрактный Jitter может принять абстрактное решение создавать все локальные переменные в куче не смотря на потерю производительности — это сферический конь в вакууме.
Лично для меня определение, что «значимые типы — это типы, экземпляры которых располагаются на стеке, а ссылочные — это типы, экземпляры которых располагаются в куче» вполне приемлемо. Просто оно неявно подразумевает, что речь идёт о локальных переменных.
И как иначе определять значимые типы? Что они по значению передаются? Ну так они и по ссылке могут передаваться.
Лично для меня определение, что «значимые типы — это типы, экземпляры которых располагаются на стеке, а ссылочные — это типы, экземпляры которых располагаются в куче» вполне приемлемо. Просто оно неявно подразумевает, что речь идёт о локальных переменных.
И как иначе определять значимые типы? Что они по значению передаются? Ну так они и по ссылке могут передаваться.
-5
Ну вообще об этом и статья :) Лично Ваше определение ссылочных и значимых типов строится на решении которое принимает программа с закрытым исходным кодом по правилам, которые не специфицированы, что само по себе уже не подразумевает точности определения, в то же время передавать значимые типы по ссылке или по значению Вы решаете явно через ключевые слова.
Я в общем согласен с автором в том, что детали реализации не могут лежать в основе определения концепции.
Я в общем согласен с автором в том, что детали реализации не могут лежать в основе определения концепции.
+3
определение, что «значимые типы — это типы, экземпляры которых располагаются на стеке, а ссылочные — это типы, экземпляры которых располагаются в куче» вполне приемлемо
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(); //Где память?
}
0
Локальные значения вообще могут быть на регистрах и ни разу не попасть в стек.
0
могут быть на регистрах и ни разу не попасть в стек.Ага, теоретически )
А вот практически джитер при наличии хотя бы одной переменной генерирует пролог, выделяет под переменные место в стеке, запихивает в стек инициализирующие значения и только после этого может использовать регистры.
Вот здесь я кое-что исследовал на эту тему.
Это было во-первых, а во-вторых сказанное — оффтопик к моему комменту.
0
| Наиболее значимой характеристикой значимого типа является не то как он располагается в памяти, а то как они ведут себя с точки зрения семантики: «значимые типы» всегда передаются «по значению», т.е. копируются.
Ну, так то ссылочные типы тоже по значению передаются.
А вообще, у нас есть спецификация языка, в которой все просто и понятно:
«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.»
0
Объекты reference типов передаются всё-таки по ссылке без копирования. Не совсем понял что Вы хотели сказать. Идея статьи не протеворечит приведённой вами части спецификации.
0
Не противоречит, но только больше запутывает того, кто не понимает.
Переменные ссылочного типа передаются по значению (по значению ссылки). Для передачи по ссылке есть ключевое слово 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 {}
+1
Я изменил формулировку в тексте статьи на "«объекты значимых типов» всегда передаются «по значению», т.е. копируются", но мне кажется что вопрос был не в этом. Исходя из контекста автор подразумевает что объект — это кусок памяти на куче или в стеке занятый полями объекта рассматриваемого типа, а не переменная для ссылочных типов (если говорить грубо указатель на адрес в куче) которая сам по себе значимого типа.
0
В одной статье по игровому движку 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% производительности.
Как вы оцениваете эффективность такой оптимизации? Стоит ли заморачиваться?
0
А вы померяйте :)
0
Игра динамичная? Если шахматы, то не стоит.
А вообще разработчики если рекомендуют, то они то знают свои слабые места, если есть тормоза — то наверное стоит.
А вообще разработчики если рекомендуют, то они то знают свои слабые места, если есть тормоза — то наверное стоит.
0
не такая большая разница — динамичная игра или нет, важна частота кадров. Важно, будет ли хоть сколько-то значительный позитивный эффект от отказа от локальных переменных в пользу полей класса.
И это предложение делали не разработчики Юнити, а в некоторых статьях по Юнити на хабре, когда говорили про оптимизацию.
В Юнити используется Mono, и ещё не самая новая.
Здесь я спрашиваю в надежде, что знатоки C# смогут что-то подсказать.
И это предложение делали не разработчики Юнити, а в некоторых статьях по Юнити на хабре, когда говорили про оптимизацию.
В Юнити используется Mono, и ещё не самая новая.
Здесь я спрашиваю в надежде, что знатоки C# смогут что-то подсказать.
0
Отличный антипример к данной статье :) Если проблема реально существует, то это именно то где детали реализации конкнретного рантайма на конкретной системе будут играть роль и спецификация C# ничем не поможет :)
0
В шахматах или какой-нибудь нединамичной игре ты не заметишь разницы в частоте кадров. Можно даже не игнорировать эппловское требование и не повышать частоту с 30 до 60 кадров, для шахмат, кроссвордов и прочих шашек это не надо.
0
Бред.
Стековые переменные значимых типов располагаются во время компиляции. Изначально методы, содержащие локальные (стековые) переменные содержат пролог, формирующий кадр стека.
Не должно быть разницы.
Первый неявный параметр метода находится в 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]
0
Может, там подразумеваются переменные объектных типов, а не банальные int/float?
0
нет, там предлагали именно все локальные переменные сделать полями класса. И была ещё какая-то фраза про то, что сборщик мусора замучается эти переменные вычищать. Хотя сборщик мусора совсем не про то.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Детали реализации стека — часть первая