Строка интернирования

В приведенном ниже коде я проверяю равенство ссылок на объекты.

string x = "Some Text";
string y = "Some Other Text";
string z = "Some Text";

Console.WriteLine(object.ReferenceEquals(x, y)); // False
Console.WriteLine(object.ReferenceEquals(x, z)); // True
Console.WriteLine(object.ReferenceEquals(y, z)); // False

y = "Some Text";

Console.WriteLine(object.ReferenceEquals(x, y)); // True
Console.WriteLine(object.ReferenceEquals(x, z)); // True
Console.WriteLine(object.ReferenceEquals(y, z)); // True

Здесь:

  • x и z относится к одному и тому же объекту; Я могу сказать, что x интернирован и z используется версия taht. Ну, я не уверен в этом; Пожалуйста, поправьте меня, если я ошибаюсь.
  • Я изменил значение y, присвоив ему то же значение, что и x. Я думал, что здесь будет создан новый объект; но я ошибся, он использовал ту же ссылку.

Мои вопросы:

  • Использует ли .net string interns для каждой строки, которую я использую?
  • Если да, то не вредит ли производительность?
  • Если нет, то как ссылки стали такими же в приведенном выше примере?

Ответ 1

Да, константные строковые выражения в компиляторе обрабатываются с помощью ldstr, что гарантирует интернирование (через MSDN):

Общая языковая инфраструктура (CLI) гарантирует, что результат двух команд ldstr, относящихся к двум токенам метаданных, которые имеют одну и ту же последовательность символов, возвращает точно тот же строковый объект (процесс, известный как "интернирование строк" ).

Это не каждая строка; это константные строковые выражения в вашем коде. Например:

string s = "abc" + "def";

- это всего лишь одно строковое выражение - IL будет ldstr на "abcdef" (компилятор может вычислить скомпонованное выражение).

Это не повредит работе.

Строки, созданные во время выполнения, не выполняются автоматически, например:

int i = GetValue();
string s = "abc" + i;

Здесь "abc" интернирован, но "abc8" - нет. Также обратите внимание, что:

char[] chars = {'a','b','c'};
string s = new string(chars);
string t = "abc";

обратите внимание, что s и t - разные ссылки (литерал (назначенный t) интернирован, но новая строка (назначенная s) не является).

Ответ 2

Использует ли .net стили строк для каждой строки, которую я использую?

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

string x = "abc"; //interned
string y = "ab" + "c"; //interned as the same string because the
                       //compiler can work out that it the same as
                       //y = "abc" at compile time so there no need
                       //to do that concatenation at run-time. There's
                       //also no need for "ab" or "c" to exist in your
                       //compiled application at all.
string z = new StreamReader(new FileStream(@"C:\myfile.text")).ReadToEnd();
                       //z isn't interned because it isn't known at compile
                       //time. Note that @"C:\myfile.text" is interned because
                       //while we don't have a variable we can access it by
                       //it is a string in the code.

Если да, то не вредит ли производительность?

Нет, это помогает производительности:

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

public override int Compare(string x, string y)
{
    if (object.ReferenceEquals(x, y))
    {
        return 0;
    }
    if (x == null)
    {
        return -1;
    }
    if (y == null)
    {
        return 1;
    }
    return this._compareInfo.Compare(x, y, this._ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
}

Это для упорядочивания, но то же самое относится к проверкам равенства/неравенства. Чтобы проверить две строки равны или поставить их в порядке, нам необходимо выполнить операцию O (n), где n пропорционально длине строки (даже в тех случаях, когда некоторые пропуски и умность могут быть выполнены, она по-прежнему пропорциональна), Это довольно медленно для длинных строк, а сравнение строк - это то, что много приложений делает много времени - отличное место для ускорения скорости. Это также самое медленное для случая равенства (потому что в тот момент, когда мы находим разницу, мы можем вернуть значение, но равные строки должны быть полностью рассмотрены).

Все всегда равно себе, даже если вы переопределяете то, что означает "равно" (чувствительная к регистру, нечувствительность, разные культуры), все по-прежнему равно себе и если вы создаете переопределение Equals(), которое не следует за тем, что вы будет иметь ошибку). Все всегда упорядочено в той же точке, что и то, что оно равно. Это означает две вещи:

  • Мы всегда можем рассматривать что-то равное себе, не делая больше работы.
  • Мы всегда можем дать сравнительное значение 0 для сравнения чего-либо с самим собой без дополнительной работы.

Следовательно, код выше коротких сокращений в этом случае без необходимости выполнять более сложное и дорогостоящее сравнение. Там также нет нисходящей стороны, так как если бы мы не рассматривали этот случай, нам пришлось бы добавить тест для случая, когда оба значения передавались где null в любом случае.

Теперь случается так, что сравнение чего-то с самим собой довольно часто возникает естественным образом с тем, как работают определенные алгоритмы, поэтому всегда стоит делать. Тем не менее, интернирование строк увеличивает время, когда две строки, которые мы имеем в разных значениях (x и z в начале вашего вопроса, например), на самом деле одинаковы, поэтому он увеличивает частоту работы коротких работ для нас.

Это крошечная оптимизация в большинстве случаев, но мы получаем ее бесплатно, и мы получаем ее так часто, что это здорово. Практический отход от этого - если вы пишете Equals или Compare, подумайте, следует ли вам также использовать этот короткий отрезок.

В связи с этим возникает вопрос: "Должен ли я ставить все?"

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

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

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

Наконец, одна и та же базовая техника может быть выполнена по-разному. XmlReader, например, хранит строки, которые он сравнивает в NameTable, который действует как частный внутренний пул, но все это можно собрать, когда оно закончится. Вы также можете применить технику к любому ссылочному типу, который не будет изменен за время его объединения (лучший способ гарантировать, что он должен быть неизменным, чтобы он не изменился в любое время). Использование этого метода с очень большими коллекциями с большим количеством дублирования может значительно сократить использование памяти (моя самая большая экономия была не менее 16 ГБ - это могло быть больше, но сервер все время рушился примерно до того момента, когда применялась техника) и/или скорость.

Ответ 3

Строковые литералы автоматически интернируются.

Программно созданные строки не будут интернированы по умолчанию (и не будут введены пользователем строки).

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

В вашем коде, если у вас есть:

string.Format("{0} {1}", "Some", "Text")

Вы увидите, что возвращаемая ссылка не такая же, как для других литералов.

Ответ 4

Я думаю, что он снова повторится

Возможный дубликат

Сравнительное сравнение строк строки

Две разные строки" являются тем же экземпляром объекта?

Повторяется

The Common Language Infrastructure (CLI) guarantees that the result of two ldstr instructions referring to two metadata tokens that have the same sequence of characters return precisely the same string object (a process known as "string interning").