2015-06-20 2 views
32

Я просматривал дерево исходников .NET Core, сегодня и натыкался this pattern в System.Collections.Immutable.ImmutableArray<T>:Какое преимущество хранения «this» в локальной переменной в методе struct?

T IList<T>.this[int index] 
{ 
    get 
    { 
     var self = this; 
     self.ThrowInvalidOperationIfNotInitialized(); 
     return self[index]; 
    } 
    set { throw new NotSupportedException(); } 
} 

Этот шаблон (хранящий this в локальной переменной), кажется, последовательно применяется в этом файле всякий раз, когда this бы в противном случае ссылки несколько раз в том же методе, но не тогда, когда он упоминается только один раз. Поэтому я начал думать о том, какие относительные преимущества могут быть сделаны таким образом; мне кажется, что преимущество, скорее всего, связано с производительностью, поэтому я спустился по этому маршруту немного дальше ... возможно, я забываю что-то еще.

CIL, который получает испускаемый для «магазина this в местном» модель, кажется, выглядеть как ldarg.0, то ldobj UnderlyingType, то stloc.0 так, чтобы последующие ссылки приходят из ldloc.0 вместо голой ldarg.0, как это было бы просто используйте this несколько раз.

Может ldarg.0 значительно медленнее, чем ldloc.0, но не достаточно для либо C# -в-CIL перевод или джиттер, чтобы искать возможности оптимизировать это для нас, таким образом, что она имеет больше смысла писать это странно выглядящий шаблон в коде C# в любое время, когда мы в противном случае испустили бы две команды ldarg.0 в методе экземпляра структуры?

Update: или, вы знаете, я мог бы посмотрел на комментарии в верхней части этого файла, который explain именно то, что происходит ...

ответ

20

Как вы уже заметили, System.Collections.Immutable .ImmutableArray < T> является структура:

public partial struct ImmutableArray<T> : ... 
{ 
    ... 

    T IList<T>.this[int index] 
    { 
     get 
     { 
      var self = this; 
      self.ThrowInvalidOperationIfNotInitialized(); 
      return self[index]; 
     } 
     set { throw new NotSupportedException(); } 
    } 

    ... 

var self = this; создает копию структуры ссылается это. Зачем это нужно делать? source comments of this struct дать объяснение, почему это необходимо:

/// Этот тип должен быть потокобезопасным. Как структуры, он не может защитить свои собственные полей
/// от изменения из одного потока в то время как ее члены выполняются на других потоках
///, потому что могут изменить Структуры вместо просто переназначения поля, содержащее
/// эта структура. Поэтому крайне важно, чтобы
/// ** Каждый член должен только разыгрывать это ОДНИМ. **
/// Если член должен ссылаться на поле массива, это считается разыменованием этого.
/// Вызов других членов экземпляра (свойств или методов) также считается разыменованием этого.
/// Любой член, который должен использовать это более одного раза, должен вместо этого
/// назначить это локальной переменной и использовать ее для остальной части кода.
/// Это эффективно копирует одно поле в структуре в локальную переменную, так что
/// он изолирован от других потоков.

Короче говоря, если это возможно, что другие потоки вносить изменения в области структуры или изменение структуры в месте (на переназначение поле члена класса этого типа структуры, например), в то время как метод GET выполняется, и, следовательно, может вызывать плохие побочные эффекты, тогда становится необходимым, чтобы метод get сначала сделал (локальную) копию структуры перед ее обработкой.

Обновление: Пожалуйста, также прочитайте supercats answer, в котором подробно объясняются, какие условия должны выполняться, чтобы операция, такая как создание локальной копии структуры (то есть var self = this;), была потокобезопасной, и что могло произойти, если эти условия не встретил.

+0

Хм, но учитывая, что 'System.Collections.Immutable.ImmutableArray ' неизменен, если это единственное соображение, то разве это не просто шаблон для записи нескольких циклов процессора, чем в противном случае? –

+0

Теперь мне любопытно. если вы посмотрите на этот поток http://stackoverflow.com/questions/9648939/c-sharp-structs - вот почему можно установить 'this = ...', если вызов 'this' создает новый экземпляр структура? – Benj

+0

@Benj, что вы имеете в виду под «называть это»? Обратите внимание, что struct является типом значения, поэтому присваивание «=» копирует структуру ... – elgonzo

8

Структурные экземпляры в .NET всегда изменяемы, если базовое хранилище изменено и всегда неизменное, если базовое хранилище является неизменным. Возможно, что типы структуры будут «притворяться» неизменными, но .NET позволит модифицировать экземпляры структурного типа, которые могут записывать места хранения, в которых они находятся, и сами типы структуры не имеют права говорить.

Таким образом, если один имел-структуру:

struct foo { 
    String x; 
    override String ToString() { 
    String result = x; 
    System.Threading.Thread.Sleep(2000); 
    return result & "+" & x; 
    } 
    foo(String xx) { x = xx; } 
} 

и один должны были вызвать следующий метод на двух нитей с тем же массивом myFoos типа foo[]:

myFoos[0] = new foo(DateTime.Now.ToString()); 
var st = myFoos[0].ToString(); 

было бы совершенно возможно, что какой бы ни была начата нить, будет иметь значение ToString(), указывающее время, написанное его вызовом конструктора, и время, сообщаемое вызовом конструктора другого потока, вместо того, чтобы сообщать одну и ту же строку дважды. Для методов, целью которых является проверка правильности поля структуры, а затем его использование, изменение поля между валидацией и использованием приведет к использованию метода с использованием неопределенного поля. Копирование содержимого поля структуры (либо путем копирования только поля, либо путем копирования всей структуры) позволяет избежать этой опасности.

Обратите внимание, что для структур, содержащего поле типа Int64, UInt64 или Double, или которые содержат более одного поля, то возможно, что утверждение подобного var temp=this; которое происходит в одном потоке, а другой поток перезапись места, где this был сохранен, может закончиться копированием структуры, которая содержит произвольную смесь старого и нового контента. Только если структура содержит одно поле ссылочного типа или одно поле из 32-битного или меньшего примитива, гарантируется, что чтение, которое происходит одновременно с записью, даст некоторое значение, которое фактически было выполнено структурой, и даже это может иметь некоторые причуды (например, по крайней мере, в VB.NET, оператор, такой как someField = New foo("george"), может очистить someField перед вызовом конструктора).

+0

* Структурные экземпляры в .NET всегда изменяемы, если базовое хранилище изменено и всегда неизменное, если базовое хранилище неизменно. * Иногда вещи должны быть написаны персонажами огня на небе, чтобы каждый мог их прочесть. Сегодня я впервые обнаружил, что неизменяемая «структура» может быть небезопасной по потоку ... Если я думаю, что это понятно ... «int64» может подвергаться разрыву (высокая и низкая часть изменены не -at-the-same-time), если несколько потоков одновременно читают и записывают ... поэтому ясно, что любая «структура» может быть подвержена этому. – xanatos

+0

@xanatos: структура, которая содержит одно поле ссылочного типа или одно поле «короткого» примитивного типа, будет невосприимчива к разрыву.Во многих случаях, когда необходимо обернуть единственную ссылку на неизменяемый экземпляр изменяемого типа, структура может предложить лучшую производительность и лучшую семантику, чем класс [например, если 'BigInteger' были структурой с одним полем типа' int [] '(используя первые несколько элементов массива для хранения таких вещей, как кешированное хэш-значение и т. д.), тогда можно было бы использовать' default (BigInteger) ' вести себя как нулевое значение, а не как нулевую ссылку.] – supercat

+0

Да ... разрывание - это пересекающийся класс проблем, о котором мы говорим здесь ... Тем не менее странно, что в конце концов вы не может даже быть уверенным, что 'int.GetHashcode()' потокобезопасен или нет ... (это потому, что это просто 'return this'), но' short.GetHashCode', вероятно, нет, потому что это: 'return (int) ((ushort) this) | (int) this << 16; ' – xanatos