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

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

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();

тогава страничният ефект от лявата страна трябва да се случи преди повикването от дясната страна.

Същността на моя въпрос е: Директно разпределени ли са структурите на място или трябва да бъдат разпределени и след това копирани?

Спецификацията гласи, че структурите се разпределят на временно местоположение - което на практика обикновено е стекът или регистърът - и след това се копират до крайната им дестинация. Въпреки това, има някои ситуации, в които оптимизаторът може да определи, че е невъзможно потребителят да забележи дали мутацията се случва „на място“ на крайната дестинация. Това е оптимизация за елизия на копиране и C# компилаторът ще извърши тази оптимизация, ако прецени, че може да се измъкне.

За повече подробности вижте моята статия по темата:

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

person Eric Lippert    schedule 10.04.2014
comment
Благодаря за чудесното обяснение - особено за публикацията в блога, която обяснява защо структурите се разпределят на временно местоположение (в повечето случаи). Предполагам тогава, от двата кодови примера, които предоставих, първият би бил по-добър, защото ще избегне разпределянето на структурата във временно местоположение отново и отново (ако приемем, че няма елизия на копиране), правилно ли е ? - 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