Почему нормально, что эта структура изменчива? Когда изменчивые структуры приемлемы? - PullRequest
34 голосов
/ 13 ноября 2011

Эрик Липперт сказал мне, что я должен «стараться всегда делать типы значений неизменяемыми» , поэтому я решил, что я должен всегда пытаться сделать типы значений неизменяемыми.

Но я только что нашел эту внутреннюю изменяемую структуру, System.Web.Util.SimpleBitVector32, в сборке System.Web, что заставляет меня думать, что должна быть веская причина для того, чтобы иметь изменяемую структуру. Я предполагаю, что причина, по которой они сделали это таким образом, заключается в том, что при тестировании он работал лучше, и они держали его внутри, чтобы не допустить его неправильного использования. Тем не менее, это предположение.

У меня есть C & P источник этой структуры. Что оправдывает проектное решение использовать изменяемую структуру? В целом, какие выгоды могут быть получены с помощью этого подхода и когда эти выгоды являются достаточно значительными, чтобы оправдать потенциальные убытки?

[Serializable, StructLayout(LayoutKind.Sequential)]
internal struct SimpleBitVector32
{
    private int data;
    internal SimpleBitVector32(int data)
    {
        this.data = data;
    }

    internal int IntegerValue
    {
        get { return this.data; }
        set { this.data = value; }
    }

    internal bool this[int bit]
    {
        get { 
            return ((this.data & bit) == bit); 
        }
        set {
            int data = this.data;
            if (value) this.data = data | bit;
            else this.data = data & ~bit;
        }
    }

    internal int this[int mask, int offset]
    {
        get { return ((this.data & mask) >> offset); }
        set { this.data = (this.data & ~mask) | (value << offset); }
    }

    internal void Set(int bit)
    {
        this.data |= bit;
    }

    internal void Clear(int bit)
    {
        this.data &= ~bit;
    }
}

Ответы [ 5 ]

18 голосов
/ 13 ноября 2011

Учитывая, что полезная нагрузка представляет собой 32-разрядное целое число, я бы сказал, что это легко можно было бы записать как неизменную структуру, вероятно, без влияния на производительность. Независимо от того, вызываете ли вы метод мутатора, который изменяет значение 32-битного поля, или заменяете 32-битную структуру новой 32-битной структурой, вы все равно выполняете те же самые операции с памятью.

Возможно, кто-то хотел что-то вроде массива (хотя на самом деле это просто биты в 32-разрядном целом числе), поэтому они решили использовать с ним синтаксис индексатора вместо менее очевидного .WithTheseBitsChanged () метод, который возвращает новую структуру. Так как он не будет использоваться непосредственно кем-либо, кроме веб-команды MS, и, вероятно, не очень многими людьми, даже внутри веб-команды, я думаю, что у них было немного больше свободы в принятии дизайнерских решений, чем у людей, создающих публичные API.

Итак, нет, вероятно, не так для производительности - вероятно, это было просто личное предпочтение программиста в стиле кодирования, и никогда не было веской причины для его изменения.

Если вы ищете рекомендации по проектированию, я бы не стал тратить слишком много времени на просмотр кода, который не был отшлифован для общественного потребления.

15 голосов
/ 13 ноября 2011
Я подозреваю, что

SimpleBitVector32 изменчиво по тем же причинам, что и BitVector32 изменчиво. На мой взгляд, директива неизменяемая - это просто директива ; однако для этого нужно иметь действительно вескую причину .

Также рассмотрим Dictionary<TKey, TValue> - я углублюсь в некоторые расширенные подробности здесь . Структура Entry словаря является изменяемой - вы можете изменить TValue в любое время. Но Entry логически представляет значение .

Изменчивость должна иметь смысл. Я согласен с @JoeWhite: кто-то хотел что-то вроде массива (хотя на самом деле это просто биты в 32-битном целом числе) ; также, что обе структуры BitVector могли бы быть ... неизменными .

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

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

Обновление
Изначально я не был уверен в своей оценке производительности, когда рассматривал изменчивый тип значения v. Неизменный. Однако, как указывает @David, Эрик Липперт пишет это :

Бывают моменты, когда вам нужно выжать каждый последний бит производительности вне системы. И в этих сценариях иногда приходится компромисс между чистым кодом, pure , надежным, понятный, предсказуемый, модифицируемый и код, который не является ни одним из выше, но невероятно быстро.

Я выделил pure , потому что изменяемая структура не соответствует чистому идеалу, что структура должна быть неизменной . У написания изменяемой структуры есть побочный эффект: понятность и предсказуемость скомпрометированы, как продолжает Эрик:

Изменяемые типы значений ... ведут себя таким образом, что многие люди находят глубоко нелогичным, и тем самым упростить написание глючного кода (или корректного кода, который легко случайно превратился в глючный код.) Но да, они очень быстрые.

Смысл Эрика в том, что вы, как дизайнер и / или разработчик, должны принять осознанное и обоснованное решение. Как вы стали информированными? Эрик объясняет это также:

Я бы рассмотрел кодирование двух эталонных решений - одно с использованием изменяемые структуры, одна из которых использует неизменяемые структуры - и запускать некоторые реалистичные ориентированные на сценарии пользователя ориентиры. Но вот в чем дело: не выбирайте более быстрый. Вместо этого решите, прежде чем запустить тест насколько медленно недопустимо медленно .

Мы знаем, что изменение типа значения на быстрее, чем создание нового типа значения; но учитывая правильность :

Если оба решения приемлемы, выберите тот, который является чистым, правильно и достаточно быстро.

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

14 голосов
/ 02 февраля 2012

На самом деле, если вы будете искать все классы, содержащие BitVector в .NET Framework, вы найдете кучу этих зверей: -)

  • System.Collections.Specialized.BitVector32 (единственный открытый ...)
  • System.Web.Util.SafeBitVector32 (потокобезопасный)
  • System.Web.Util.SimpleBitVector32
  • System.Runtime.Caching.SafeBitVector32 (потокобезопасный)
  • System.Configuration.SafeBitVector32 (потокобезопасный)
  • System.Configuration.SimpleBitVector32

И если вы посмотрите здесь , где находится источник System.Configuration.SimpleBitVector32 SSCLI (Microsoft Shared Source CLI, также известный как ROTOR), вы найдете этот комментарий:

//
// This is a cut down copy of System.Collections.Specialized.BitVector32. The
// reason this is here is because it is used rather intensively by Control and
// WebControl. As a result, being able to inline this operations results in a
// measurable performance gain, at the expense of some maintainability.
//
[Serializable()]
internal struct SimpleBitVector32

Я верю, что этим все сказано. Я думаю, что System.Web.Util один более сложный, но построен на тех же основаниях.

2 голосов
/ 16 ноября 2011

Использование структуры для 32- или 64-битного вектора, как показано здесь, разумно, с несколькими оговорками:

  1. Я бы порекомендовал использовать цикл Interlocked.CompareExchange при выполнении любых обновлений структуры, а не просто использовать обычные логические операторы напрямую. Если один поток пытается записать бит 3, в то время как другой пытается записать бит 8, ни одна операция не должна мешать другому, за исключением небольшой задержки. Использование цикла Interlocked.CompareExchange позволит избежать возможности ошибочного поведения (поток 1 читает значение, поток 2 читает старое значение, поток 1 записывает новое значение, поток 2 записывает значение, вычисленное на основе старого значения, и отменяет изменение потока 1) без необходимости какого-либо изменения. другой тип блокировки.
  2. Следует избегать членов структуры, кроме установщиков свойств, которые изменяют это. Лучше использовать статический метод, который принимает структуру в качестве ссылочного параметра. Вызов члена структуры, который изменяет «this», обычно идентичен вызову статического метода, который принимает член в качестве ссылочного параметра, как с семантической, так и с точки зрения производительности, но есть одно ключевое отличие: если кто-то пытается передать структуру только для чтения при ссылке на статический метод вы получите ошибку компилятора. Напротив, если в структуре, доступной только для чтения, вызывается метод, который модифицирует «this», не будет никаких ошибок компилятора, но предполагаемое изменение не произойдет. Поскольку даже изменяемые структуры могут рассматриваться как доступные только для чтения в определенных контекстах, гораздо лучше получить ошибку компилятора, когда это происходит, чем иметь код, который будет компилироваться, но не будет работать.

Эрик Липперт любит рассказывать о том, насколько изменчивые структуры являются злыми, но важно признать его отношение к ним: он один из людей в команде C #, который отвечает за создание таких функций поддержки языка, как замыкания и итераторы. Из-за некоторых конструктивных решений на ранних этапах создания .net надлежащая поддержка семантики типа значения затруднена в некоторых контекстах; его работа была бы намного легче, если бы не было никаких изменяемых типов значений. Я не жалею Эрика за его точку зрения, но важно отметить, что некоторые принципы, которые могут быть важны в разработке фреймворков и языков, не очень применимы к дизайну приложений.

0 голосов
/ 08 февраля 2012

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

Таким образом, структура должна быть изменяемой, иначе потребуется сложная система сериализации.

...