С# string interning

Я пытаюсь понять интернирование строк, и почему это не работает в моем примере. Точка примера показывает, что в примере 1 используется меньше (намного меньше памяти), так как в памяти должно быть только 10 строк. Однако в приведенном ниже коде оба примера используют примерно тот же объем памяти (виртуальный размер и рабочий набор).

Пожалуйста, совет, почему в примере 1 не используется намного меньше памяти? Благодаря

Пример 1:

        IList<string> list = new List<string>(10000);

        for (int i = 0; i < 10000; i++)
        {
            for (int k = 0; k < 10; k++)
            {
                list.Add(string.Intern(k.ToString()));
            }

        }

        Console.WriteLine("intern Done");
        Console.ReadLine();

Пример 2:

        IList<string> list = new List<string>(10000);

        for (int i = 0; i < 10000; i++)
        {
            for (int k = 0; k < 10; k++)
            {
                list.Add(k.ToString());
            }

        }

        Console.WriteLine("intern Done");
        Console.ReadLine();

Ответ 1

Из msdn Во-вторых, чтобы ставить строку, вы должны сначала создать строку. Память, используемая объектом String, все равно должна быть выделена, хотя в конечном итоге память будет собрана в мусор.

Ответ 2

Проблема в том, что ToString() все равно будет выделять новую строку, а затем ставить ее. Если сборщик мусора не запускается для сбора этих "временных" строк, то использование памяти будет одинаковым.

Кроме того, длина ваших строк довольно короткая. 10 000 строк, которые в основном имеют только один символ, - это разница в памяти около 20 КБ, которую вы, вероятно, не заметите. Попробуйте использовать более длинные строки (или их намного больше) и собирать мусор перед проверкой использования памяти.

Вот пример, который показывает разницу:

class Program
{
    static void Main(string[] args)
    {
        int n = 100000;

        if (args[0] == "1")
            WithIntern(n);
        else
            WithoutIntern(n);
    }

    static void WithIntern(int n)
    {
        var list = new List<string>(n);

        for (int i = 0; i < n; i++)
        {
            for (int k = 0; k < 10; k++)
            {
                list.Add(string.Intern(new string('x', k * 1000)));
            }
        }

        GC.Collect();
        Console.WriteLine("Done.");
        Console.ReadLine();
    }

    static void WithoutIntern(int n)
    {
        var list = new List<string>(n);

        for (int i = 0; i < n; i++)
        {
            for (int k = 0; k < 10; k++)
            {
                list.Add(new string('x', k * 1000));
            }
        }

        GC.Collect();
        Console.WriteLine("Done.");
        Console.ReadLine();
    }
}

Ответ 3

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

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

Пример 1

0:005>!dumpheap -stat
...
00b6911c      137         4500 System.String
0016be60        8       480188      Free
00b684c4       14       649184 System.Object[]
Total 316 objects
0:005> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x01592dcc
generation 1 starts at 0x01592dc0
generation 2 starts at 0x01591000
ephemeral segment allocation context: none
 segment    begin allocated     size
01590000 01591000  01594dd8 0x00003dd8(15832)
Large object heap starts at 0x02591000
 segment    begin allocated     size
02590000 02591000  026a49a0 0x001139a0(1128864)
Total Size  0x117778(1144696)
------------------------------
GC Heap Size  0x117778(1144696)

Пример 2

0:006> !dumpheap -stat
...
00b684c4       14       649184 System.Object[]
00b6911c   100137      2004500 System.String
Total 100350 objects
0:006> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0179967c
generation 1 starts at 0x01791038
generation 2 starts at 0x01591000
ephemeral segment allocation context: none
 segment    begin allocated     size
01590000 01591000  0179b688 0x0020a688(2139784)
Large object heap starts at 0x02591000
 segment    begin allocated     size
02590000 02591000  026a49a0 0x001139a0(1128864)
Total Size  0x31e028(3268648)
------------------------------
GC Heap Size  0x31e028(3268648)

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

Ответ 4

Источник: https://blogs.msdn.microsoft.com/ericlippert/2009/09/28/string-interning-and-string-empty/

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

Пример:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;

вывод следующих сравнений:

Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true    
Console.WriteLine(obj == str2); // false !?

Примечание1. Объекты сравниваются по ссылке.

Примечание2: typeof (int). Имя оценивается методом отражения, поэтому оно не оценивается во время компиляции. Здесь эти сравнения выполняются во время компиляции.

Анализ результатов:

  • true, потому что они оба содержат один и тот же литерал, и поэтому код сгенерированный будет иметь только один объект, ссылающийся на "Int32". См. примечание 1.

  • true, потому что проверяется содержимое обоих значений, которое одинаково.

  • false, потому что str2 и obj не имеют одного и того же литерала. См       Примечание 2.