Распределение типов значений

Когда вы назначаете экземпляр типа значения другому экземпляру, объект побитно копируется в целевое местоположение:

private struct Word
{
    public Word(char c) { ... }
}

public void Method(Word a)
{
    Word b = a; //a is copied and stored in b
}

Но учитывая следующий код:

private Word _word;

public void Method() {
    _word = new Word('x');
}

Я подозреваю, что сначала вычисляется правостороннее (RHS) выражение, которое создает экземпляр типа значения в стеке, а затем значение копируется и сохраняется в местоположении поля _word, которое на куче.

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

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

//1 instantiation + 10k copies
Word[] words = new Word[10000];
Word word = new Word('x');

for (int i = 0; i < 10000; i++)
    words[i] = word;


//10k instantiations + 10k copies
Word[] words = new Word[10000];

for (int i = 0; i < 10000; i++)
    words[i] = new Word('x');

Примечание. Я не пытаюсь ничего микрооптимизировать.

Редактировать. Суть моего вопроса, как выразился Ли, заключается в том, выделяются ли структуры непосредственно на месте или их нужно выделять, а затем копировать?


person dcastro    schedule 10.04.2014    source источник
comment
Я думаю, вы имели в виду, что последняя строка должна быть words[i] = new Word(); . Ответ может быть интересным, но я думаю, что если его можно оптимизировать, то так оно и будет.   -  person Weyland Yutani    schedule 10.04.2014
comment
@WeylandYutani Да, спасибо ;)   -  person dcastro    schedule 10.04.2014
comment
Если вы просто вызываете конструктор по умолчанию, тогда циклы можно полностью удалить, не так ли? Структуры уже инициализированы нулями.   -  person Lee    schedule 10.04.2014
comment
@Lee Да, но дело не в этом. Это просто пример, чтобы проиллюстрировать, что я имею в виду.   -  person dcastro    schedule 10.04.2014
comment
Возможно, это не указано, потому что вы не можете заметить разницу при однопоточном выполнении. Конечно, если бы ctor выполнял побочные эффекты, ваши два фрагмента кода не были бы эквивалентны.   -  person usr    schedule 10.04.2014
comment
@dcastro. Кажется, я все-таки ошибся. А ведь вам ответил сам человек :)   -  person Murdock    schedule 10.04.2014
comment
Каков вариант использования? Вам действительно нужно сделать несколько копий одной и той же структуры?   -  person paparazzo    schedule 10.04.2014
comment
@Blam Нет варианта использования, я спрашиваю об этом из любопытства.   -  person dcastro    schedule 10.04.2014
comment
Но вы используете термины одного, предпочитаемого другому. Для меня это подразумевает больше, чем любопытство.   -  person paparazzo    schedule 10.04.2014
comment
@Blam, это просто примеры противопоставления одной гипотезы другой ^^ Я их придумал, они не взяты из проекта.   -  person dcastro    schedule 10.04.2014
comment
Интересное любопытство. Когда вы приводите реальный или надуманный вариант использования и используете термин «предпочтительный», вы подразумеваете больше, чем любопытство без варианта использования. Предлагаю уточнить это в вопросе.   -  person paparazzo    schedule 10.04.2014
comment
@Blam Я считаю, что последний абзац, помеченный как edit, дает понять, что вопрос касается того, что происходит во время выделения/назначения, а не примеров.   -  person dcastro    schedule 10.04.2014


Ответы (2)


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

Когда вы присваиваете экземпляр типа значения переменной того же типа, значение копируется в целевое местоположение, да. Но это верно и для ссылочных типов: ссылка копируется по крупицам в целевое расположение. Референт, конечно же, остается там, где он есть.

Я подозреваю, что выражение правой стороны (RHS) оценивается первым

В спецификации указано, что левая часть оценивается для создания переменной, затем правая сторона оценивается для получения значения, а затем происходит присваивание.

В приведенных вами примерах оценка левой стороны не дает наблюдаемого побочного эффекта, и поэтому ее оценка может быть переупорядочена оптимизаторами в компиляторе C #, дрожанием или ЦП, если кто-либо из них так выберет. Но если бы у вас было что-то вроде

x[i++] = v();

тогда побочный эффект на левой стороне должен произойти до вызова на правой стороне.

Суть моего вопроса: выделяются ли структуры напрямую или их нужно выделять, а затем копировать?

В спецификации указано, что структуры размещаются во временном месте, которым на практике обычно является стек или регистр, а затем копируются в место их конечного назначения. Однако есть некоторые ситуации, в которых оптимизатор может определить, что пользователь не может заметить, происходит ли изменение «на месте» в конечном пункте назначения. Это оптимизация copy elision, и компилятор C# выполнит эту оптимизацию, если сочтет, что это сойдет ему с рук.

Подробнее читайте в моей статье на эту тему:

http://ericlippert.com/2010/10/11/debunking-another-myth-about-value-types/

person Eric Lippert    schedule 10.04.2014
comment
Спасибо за отличное объяснение, особенно за сообщение в блоге, в котором объясняется, почему структуры размещаются во временном месте (в большинстве случаев). Я полагаю, что из двух предоставленных мной примеров кода первый будет лучше, потому что он позволит избежать выделения структуры во временном местоположении снова и снова (при условии отсутствия copy elision), это правильно? ? - person dcastro; 10.04.2014
comment
О, и когда я сказал, что выражение RHS оценивается первым, я имел в виду следующее: RHS и LHS оцениваются независимо. Например, LHS не влияет на размещение структуры, что, как я теперь знаю, не обязательно верно. - person dcastro; 10.04.2014
comment
@dcastro: компилятор C # пропускает копии только в том случае, если целью является локальная переменная; в ваших примерах целью является элемент массива. Реальная экономия между вашими первым и вторым примерами, скорее всего, заключается не в копировании, а в том, чтобы избежать вызова конструктора 9999 раз. - person Eric Lippert; 10.04.2014

Являются ли предпочтительными?

Каково экономическое обоснование использования нескольких идентичных структур?
Если вам нужно несколько идентичных объектов, является ли структура лучшим выбором?

Структура (пере)инициализированная таким образом, вероятно, не является хорошим решением для примера использования
В новом массиве WordStruct выделяется и инициализируется с использованием ctor по умолчанию (без ctor)
У вас нет возможность инициализировать массив структур другим ctor
Если вам нужны идентичные структуры, то это было бы предпочтительнее

WordStruct[] WordStructS = new WordStruct[1000];
for (int i = 0; i < WordStructS.Length; i++) { WordStructS[i].C = 'x'; }

Если вам действительно нужно сделать несколько идентичных объектов, рассмотрите класс
Новый массив класса выделен, но еще не инициализирован
Вы не тратите ресурсы на инициализацию с помощью конструктора по умолчанию

WordClass[] WordClassS = new WordClass[1000];
for (int i = 0; i < WordClassS.Length; i++) { WordClassS[i] = new WordClass('x'); }

Если вы хотите обобщить глубокую копию объекта (структуры или класса), рассмотрите IConable
В случае структуры я подозреваю, что это более эффективно, чем побитовое копирование (но я не уверен)
В случае класса он сделает клон (глубокую копию), а не ссылку

public struct WordStruct : ICloneable 
{
    public char C;
    public WordStruct(char C) 
    {
        this.C = C;
    }
    public object Clone()
    {
        WordStruct newWordStruct = (WordStruct)this.MemberwiseClone();
        return newWordStruct;
    }
}

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

Я понимаю, что это интересный вопрос из любопытства
Но если это просто любопытство, то вопрос должен был остановиться на
Верны ли мои подозрения?

person paparazzo    schedule 10.04.2014