Что такое интернирование строк

Все о String.intern()

Тем, кто знает об этом методе лишь понаслышке, добро пожаловать под кат.

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

Сегодня я просматривал исходный код Xerces (XML-парсер, включенных в Java) и наткнулся на строку, которая меня очень удивила:

com.sun.org.apache.xerces.internal.impl.XMLScanner:395
protected final static String fVersionSymbol = «version».intern();

1) Создать множество (hash set) строк
2) Проверить, что строка (как последовательность символов), с которой вы имеете дело, уже в множестве
3) Если да, то использовать строку из множества
4) В противном случае, добавить эту строку в множество и затем использовать ее

Так почему же я так удивился, увидев
protected final static String fVersionSymbol = «version».intern();
в исходном коде Xerces? Очевидно, что эта строка будет использоваться для многократных сравнений. Имеет ли смысл интернировать ее?

Вывод? intern() является полезным методом и может сделать жизнь легче — но убедитесь, что вы используете его должным образом.

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

Большое спасибо хабраюзеру nolled, который пригласил меня в Хабрасообщество.

Update
Думаю, что следующая информация, которую я узнал из других источников будет здесь не лишней:

1. Пул строк хранится в области «Perm Gen», которая зарезервирована для non-user объектов JVM (классы и пр). Если этого не учитывать, вы можете неожиданно получить OutOfMemory Error.
2. Интернированные строки не хранятся вечно. Строки, на которых нет ссылок, также удаляются сборщиком мусора.
3. В большинстве случаев вы не получите существенного прироста производительности от использования intern() — если сравнение строк не является основной (или очень частой) операцией вашего приложения и сравниваемые строки разные по длине.

Источник

Руководство по интернированию строк в Python

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

Вступление

Тем не менее, давайте взглянем на этот пример кода:

Однако, когда мы запускаем код, это приводит к:

Примечание: Среда, в которой выполняется код, влияет на то, как работает интернирование строк. Предыдущие примеры были результатом запуска кода в виде скрипта в неинтерактивной среде с использованием текущей последней версии Python (версия 3.8.5). Поведение будет отличаться при использовании консоли/Jupiter из-за различных способов оптимизации кода или даже между различными версиями Python.

Это происходит потому, что разные среды имеют разные уровни оптимизации.

Интернирование строк

Строки-это неизменяемые объекты в Python. Это означает, что после того, как строки созданы, мы не можем изменить или обновить их. Даже если кажется, что строка была изменена, под капотом была создана копия с измененным значением и назначена переменной, в то время как исходная строка осталась прежней.

Давайте попробуем изменить строку:

Поскольку строка name неизменяема, этот код завершится ошибкой в последней строке:

Что дает нам желаемый результат:

Причина, по которой мы можем изменить символ в списке (а не в строке), заключается в том, что списки изменчивы – это означает, что мы можем изменить их элементы.

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

Это означает, что, когда мы создаем две строки с одинаковым значением – вместо выделения памяти для них обоих, только одна строка фактически фиксируется в памяти. Другой просто указывает на то же самое место в памяти.

Учитывая эту информацию, давайте вернемся к исходному примеру Hello World :

Когда b создается, Hello World находится компилятором в интернет-памяти, поэтому вместо создания другой строки b просто указывает на ранее выделенную память.

a-это b и a в данном случае.

Проверка

Как Интернируются Строки

В Python есть два способа создания строк на основе взаимодействия программиста:

Неявное интернирование

Python автоматически интернирует некоторые строки в момент их создания. Интернируется ли строка или нет, зависит от нескольких факторов:

Все пустые строки и строки длины 1 являются интернетом.

Имена функций, классов, переменных, аргументов и т. Д.

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

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

Строки, содержащие символы, отличные от ASCII, скорее всего, не будут интернированы.

Если вы помните, мы говорили, что ‘Hello World’ + letter_d был вычислен во время выполнения, и поэтому он не будет интернирован. Поскольку не существует последовательного стандарта интернирования строк, хорошим эмпирическим правилом является идея времени компиляции/времени выполнения, где вы можете предположить, что строка будет интернирована, если она может быть вычислена во время компиляции.

Явное Интернирование

Вы можете интернировать любой вид строки следующим образом:

Мы видим, что это будет работать в нашем предыдущем примере:

Теперь мы знаем, как и какие строки интернируются в Python. Остается один вопрос – почему было введено интернирование строк?

Преимущества интернирования струн

Интернирование строк имеет ряд преимуществ:

Недостатки интернирования строк

Однако интернирование строк имеет некоторые недостатки и вещи, которые следует учитывать перед использованием:

Вывод

Используя интернирование строк, вы гарантируете, что будет создан только один объект, даже если вы определяете несколько строк с одинаковым содержимым. Однако вы должны помнить о балансе между преимуществами и недостатками интернирования строк и использовать его только тогда, когда считаете, что ваша программа может принести пользу.

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

Источник

Как работает интернирование строк

1) Intern pool; (пулСтрок) Позволяет объединить строки с одинаковыми значениями в них в определенный пул памяти. В данном случае мы инициализировали две строки с одинаковыми значениями и они попали в один пул памяти. пример в моем понимании:

Почему так? и как это работает? по сути мы получаем строку с одинаковыми значениями и все они должны ссылаться на один объект. почему мы по итогу получаем разные результаты? Я понимаю, что в данном случае мы сравниваем объекты, а не содержимое. Но почему они не объединились?

Что такое интернирование строк. Смотреть фото Что такое интернирование строк. Смотреть картинку Что такое интернирование строк. Картинка про Что такое интернирование строк. Фото Что такое интернирование строк

2 ответа 2

В первом вашем примере строки интернируются на этапе компиляции. Если посмотреть в утилите ILDASM, то в окне MetaInfo в разделе User Strings будет представлен всего один экземпляр:

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

Во втором примере можно добавить строку в пул вручную:

Ответ на вопрос из комментария:

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

Чтобы не гадать, глянем сразу IL-инструкции которые генерирует компилятор:

Как видим в данном случае в первом блоке мы записали одну и ту же строку в a (IL_0006) и b (IL_000C), и в данном случае строка является интернированной, во втором же блоке только ‘a2′(IL_0017) является интернированной, b2 после конкатенации (IL_0026) и присваивания (IL_002B) таковой не является.

UPD: Следующий код также возвращает false :

Источник

String. Intern(String) Метод

Определение

Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

Извлекает системную ссылку на указанный объект String.

Параметры

Строка для поиска в пуле интернирования.

Возвращаемое значение

Исключения

Примеры

В следующем примере используются три строки, равные значению, чтобы определить, равны ли вновь созданная строка и интернированная строка.

Комментарии

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

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

В следующем примере строка s1, имеющая значение «MyTest», уже интернирована, так как это литерал в программе. System.Text.StringBuilderКласс создает новый строковый объект, имеющий то же значение, что и S1. Ссылка на эту строку присваивается S2. InternМетод выполняет поиск строки, имеющей то же значение, что и S2. Так как такая строка существует, метод возвращает ту же ссылку, которая назначена S1. Затем эта ссылка назначается S3. Ссылки S1 и S2 не равны, так как они ссылаются на разные объекты. ссылается на равенство S1 и S3, так как они ссылаются на одну и ту же строку.

Сравните этот метод с IsInterned методом.

Рекомендации по версиям

Вопросы производительности

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

Источник

Подводные камни в бассейне строк, или ещё один повод подумать перед интернированием экземпляров класса String в C#

Будучи разработчиками программного обеспечения, мы всегда хотим, чтобы написанное нами ПО работало быстро. Использование оптимального алгоритма, распараллеливание, применение различных техник оптимизации – мы будем прибегать ко всем известным нам средствам, дабы улучшить производительность софта. К одной из таких техник оптимизации можно отнести и так называемое интернирование строк. Оно позволяет уменьшить объём потребляемой процессом памяти, а также значительно сокращает время, затрачиваемое на сравнение строк. Однако, как и везде в жизни, необходимо соблюдать меру – не стоит использовать интернирование на каждом шагу. Далее в этой статье будет показано, как можно обжечься и создать своему приложению неочевидный bottleneck в виде метода String.Intern.

Что такое интернирование строк. Смотреть фото Что такое интернирование строк. Смотреть картинку Что такое интернирование строк. Картинка про Что такое интернирование строк. Фото Что такое интернирование строк

Для тех, кто мог забыть, напомню, что в C# строки являются ссылочным типом. Следовательно, сама строковая переменная – это просто ссылка, которая лежит на стеке и хранит в себе адрес, указывающий на экземпляр класса String, расположенный на куче.

Что такое интернирование строк. Смотреть фото Что такое интернирование строк. Смотреть картинку Что такое интернирование строк. Картинка про Что такое интернирование строк. Фото Что такое интернирование строк

Есть несколько версий формулы для того, чтобы посчитать, сколько байт занимает строковый объект на куче: вариант от Джона Скита и вариант, показанный в статье Тимура Гуева. На картинке выше я использовал второй вариант. Даже если эта формула верна не на 100 %, мы всё равно можем приблизительно прикинуть размер строковых объектов. К примеру, для того чтобы занять 1 Гб оперативной памяти, достаточно будет, чтобы в памяти процесса было около 4,7 миллионов строк (каждая длиной в 100 символов). Если мы полагаем, что среди строк, с которыми работает наша программа, есть большое количество дубликатов, как раз и стоит воспользоваться встроенным во фреймворк функционалом интернирования. Давайте сейчас быстро вспомним, что вообще такое интернирование строк.

Интернирование строк

Идея интернирования строк состоит в том, чтобы хранить в памяти только один экземпляр типа String для идентичных строк. При старте нашего приложения виртуальная машина создаёт внутреннюю хэш-таблицу, которая называется таблицей интернирования (иногда можно встретить название String Pool). Эта таблица хранит ссылки на каждый уникальный строковый литерал, объявленный в программе. Кроме того, используя два метода, описанных ниже, мы сами можем получать и добавлять ссылки на строковые объекты в эту таблицу. Если наше приложение работает с большим количеством строк, среди которых часто встречаются одинаковые, то нет смысла каждый раз создавать новый экземпляр класса String. Вместо этого можно просто ссылаться на уже созданный на куче экземпляр типа String, получая ссылку на него путём обращения к таблице интернирования. Виртуальная машина сама интернирует все строковые литералы, встреченные в коде (более подробно про многие хитрости интернирования можно прочитать в этой статье). А нам для работы предоставляются два метода: String.Intern и String.IsInterned.

Первый на вход принимает строку и, если идентичная строка уже имеется в таблице интернирования, возвращает ссылку на уже имеющийся на куче объект типа String. Если же такой строки ещё нет в таблице, то ссылка на этот строковый объект добавляется в таблицу интернирования и затем возвращается из метода. Метод IsInterned также на вход принимает строку и возвращает ссылку из таблицы интернирования на уже имеющийся объект. Если такого объекта нет, то возвращается null (про неинтуитивно возвращаемое значение этого метода не упомянул только ленивый).

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

Интернирование строк может дать прирост производительности при сравнении этих самых строк. Взглянем на реализацию метода String.Equals:

До вызова метода EqualsHelper, где производится посимвольное сравнение строк, метод Object.ReferenceEquals выполняет проверку на равенство ссылок. Если строки интернированы, то уже на этапе проверки ссылок на равенство метод Object.ReferenceEquals вернёт значение true при условии равенства строк (без необходимости сравнения самих строк посимвольно). Конечно, если ссылки не равны, то в итоге произойдёт вызов метода EqualsHelper и последующее посимвольное сравнение. Ведь методу Equals неизвестно, что мы работаем с интернированными строками, и возвращаемое значение false методом ReferenceEquals уже свидетельствует о различии сравниваемых строк.

Если мы точно уверены, что в конкретном месте в программе поступающие на вход строки будут гарантированно интернированы, то их сравнение можно осуществлять через метод Object.ReferenceEquals. Однако я бы не рекомендовал этот подход. Так как всё-таки всегда есть вероятность, что код в будущем изменится или будет переиспользован в другой части программы – и в него смогут попасть неинтернированные строки. В этой ситуации при сравнении двух одинаковых неинтернированных строк через метод ReferenceEquals мы посчитаем, что они неидентичны.

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

После того, как мы немного освежили память в вопросе интернирования строк, давайте же перейдём непосредственно к описанию той проблемы, с которой я столкнулся.

Краткая история того, как всё начиналось

В корпоративном bug tracker’е уже какое-то время значилась задача, в которой требовалось провести исследование: как распараллеливание процесса анализа С++ кода может сократить продолжительность анализа. Хотелось, чтобы анализатор PVS-Studio параллельно работал на нескольких машинах при анализе одного проекта. В качестве программного обеспечения, позволяющего проводить подобное распараллеливание, был выбран IncrediBuild. IncrediBuild позволяет параллельно запускать на машинах, находящихся в одной сети, различные процессы. Например, такой процесс, как компиляция исходных файлов, можно распараллелить на разные машины, доступные в компании (или в облаке), и таким образом добиться существенного сокращения времени сборки проекта. Данное программное обеспечение является довольно популярным решением среди разработчиков игр.

Моя задача заключалась в том, чтобы подобрать проект для анализа, проанализировать его с помощью PVS-Studio локально на своей машине, а затем запустить анализ уже с использованием IncrediBuild, который будет заниматься распараллеливанием процессов анализатора на машинах, доступных в расположении компании. По результатам – подвести некий итог целесообразности такого распараллеливания. Если результат окажется положительным, предлагать нашим клиентам такое решения для ускорения анализа.

Был выбран open source проект Unreal Tournament. Удалось успешно уговорить программистов посодействовать в решении задачи и установить IncrediBuild на их машины. Полученный объединённый кластер включал около 145 ядер.

Так как я анализировал проект Unreal Tournament с помощью применения системы мониторинга компиляции в PVS-Studio, то мой сценарий работы был следующим. Я запускал программу CLMonitor.exe в режиме монитора, выполнял полную сборку проекта Unreal Tournament в Visual Studio. Затем, после прохождения сборки, опять запускал CLMonitor.exe, но уже в режиме запуска анализа. В зависимости от указанного в настройках PVS-Studio значения для параметра ThreadCount, CLMonitor.exe параллельно одновременно запускает соответствующее количество дочерних процессов PVS-Studio.exe, которые и занимаются анализом каждого отдельного исходного С++ файла. Один дочерний процесс PVS-Studio.exe анализирует один исходный файл, а после завершения анализа передаёт полученные результаты обратно в CLMonitor.exe.

Всё просто: выставил параметр ThreadCount в настройках PVS-Studio, равный количеству доступных ядер (145), запустил анализ и сел в ожидании того, что сейчас увижу, как 145 процессов PVS-Studio.exe будут исполняться параллельно на удалённых машинах. У приложения IncrediBuild есть удобная система мониторинга распараллеливания Build Monitor, через которую можно наблюдать, как процессы запускаются на удалённых машинах. Что-то подобное я и наблюдал в процессе анализа:

Что такое интернирование строк. Смотреть фото Что такое интернирование строк. Смотреть картинку Что такое интернирование строк. Картинка про Что такое интернирование строк. Фото Что такое интернирование строк

Казалось, что нет ничего проще: сиди и смотри, как проходит анализ, а после просто зафиксируй время его проведения с использованием IncrediBuild и без. Реальность оказалась несколько интересней.

Обнаружение проблемы, её локализация и устранение

Пока проходил анализ, можно было переключиться на выполнение других задач или просто медитативно поглядывать на бегущие полосы процессов PVS-Studio.exe в окне Build Monitor. После завершения анализа с использованием IncrediBuild я сравнил временные показатели продолжительности анализа с результатами замеров без применения IncrediBuild. Разница ощущалась, но общий результат, как мне показалось, мог бы быть и лучше: 182 минуты на одной машине с 8 потоками и 50 минут с использованием IncrediBuild и 145 потоками. Получалось, что количество потоков возросло в 18 раз, но при этом время анализа уменьшилось всего в 3,5 раза. Напоследок я опять решил взглянуть уже на итоговый результат окна Build Monitor. Прокручивая нижний ползунок, я заметил следующие аномалии на графике:

Что такое интернирование строк. Смотреть фото Что такое интернирование строк. Смотреть картинку Что такое интернирование строк. Картинка про Что такое интернирование строк. Фото Что такое интернирование строк

Было видно, что процессы PVS-Studio.exe запускались и успешно завершались, а затем по какой-то причине происходила некая пауза между запуском следующей партии процессов PVS-Studio.exe. И так повторялось несколько раз. Пауза за паузой. И эти простои между запусками складывались в ощутимую задержку и вносили свою лепту в общее время анализа. Первое, что пришло в голову, – это вина IncrediBuild. Наверняка он выполняет какую-то свою внутреннюю синхронизацию между процессами и тормозит их запуск.

Я показал эти аномалии на графиках своему старшему коллеге. Он, в отличие от меня, не был так поспешен в выводах. Он предложил посмотреть на то, что происходит внутри нашего приложения CLMonitor.exe в момент того, как на графике начинают появляться видимые простои. Запустив анализ повторно и дождавшись первого очевидного «провала» на графике, я подключился к процессу CLMonitor.exe с помощью отладчика Visual Studio и поставил его на паузу. Открыв вкладку Threads, мы с коллегой увидели около 145 приостановленных потоков. Перемещаясь по местам в коде, где остановили своё исполнение данные потоки, мы увидели строки кода подобного содержания:

Что общего мы видим в этих строках кода? В каждой из них используется метод String.Intern. Причём его применение кажется оправданным. Дело в том, что это те места, где CLMonitor.exe обрабатывает данные, приходящие от процессов PVS-Studio.exe. Обработка происходит путём записи данных в объекты типа ErrorInfo, инкапсулирующего в себе информацию о найденной анализатором потенциальной ошибке в проверяемом коде. Интернируем мы тоже вполне разумные вещи, а именно пути до исходных файлов. Один исходный файл может содержать множество ошибок, поэтому нет смысла, чтобы объекты типа ErrorInfo содержали в себе разные строковые объекты с одинаковым содержимым. Логично просто ссылаться на один объект из кучи.

После недолгих раздумий стало понятно, что интернирование здесь применено в очень неподходящий момент. Итак, вот какую ситуацию мы наблюдали в отладчике. Пока по какой-то причине 145 потоков висели на выполнении метода String.Intern, кастомный планировщик задач LimitedConcurrencyLevelTaskScheduler внутри CLMonitor.exe не мог запустить новый поток, который в дальнейшем бы породил новый процесс PVS-Studio.exe, а IncrediBuild уже запустил бы этот процесс на удалённой машине. Ведь, с точки зрения планировщика, поток ещё не завершил своё исполнение – он выполняет преобразование полученных данных от PVS-Studio.exe в ErrorInfo с последующим интернированием. Ему всё равно, что сам процесс PVS-Studio.exe уже давно завершился и удалённые машины попросту простаивают без дела. Поток ещё активен, и установленный нами лимит в 145 потоков не даёт планировщику породить новый.

Выставление большего значения для параметра ThreadCount не решило бы проблему, так как это только увеличило бы очередь из потоков, висящих на исполнении метода String.Intern.

Убирать интернирование совсем нам не хотелось, так как это привело бы к увеличению объёма потребляемой оперативной памяти процессом CLMonitor.exe. В конечном счёте было найдено довольно простое и элегантное решение: перенести интернирование из потока, выполняющего запуск процесса PVS-Studio.exe в чуть более позднее место исполнения кода (в поток, который занимается непосредственно формированием отчёта об ошибках).

Как сказал мой коллега, обошлись хирургической правкой буквально двух строк и решили проблему с простаиванием удалённых машин. При повторных запусках анализа окно Build Monitor уже не показывало каких-либо значительных промежутков времени между запусками процессов PVS-Studio.exe. А время проведения анализа снизилось с 50 минут до 26, то есть почти в два раза. Теперь если смотреть на общий результат, который мы получили при использовании IncrediBuild и 145 доступных ядер, то общее время анализа уменьшилось в 7 раз. Это впечатляло несколько больше, нежели цифра в 3,5 раза.

String.Intern – почему так медленно, или изучаем код CoreCLR

Стоит отметить, что как только мы увидели зависания потоков на местах вызова метода String.Intern, то почти сразу подумали, что под капотом у этого метода присутствует критическая секция с каким-нибудь lock’ом. Раз каждый поток может писать в таблицу интернирования, то внутри метода String.Intern должен быть какой-нибудь механизм синхронизации, чтобы сразу несколько потоков, выполняющих запись в таблицу, не перезаписали данные друг друга. Хотелось подтвердить свои предположения, и мы решили посмотреть имплементацию метода String.Intern на ресурсе reference source. Там мы увидели, что внутри нашего метода интернирования есть вызов другого метода – Thread.GetDomain().GetOrInternString(str). Что ж, давайте посмотрим его реализацию:

В нём увидели вызов метода GetInternedString. Перейдя в тело этого метода, заметили код следующего вида:

Поток исполнения попадает в ветку else только в том случае, если метод, занимающийся поиском ссылки на объект типа String (метод GetValue) в таблице интернирования, вернёт false. Перейдём к рассмотрению кода, который представлен в ветке else. Интерес тут вызывает строка создания объекта типа CrstHolder с именем gch. Переходим в конструктор CrstHolder и видим код следующего вида:

Видим вызов метода AcquireLock. Уже хорошо. Код метода AcquireLock:

Вот, собственно, мы и видим точку входа в критическую секцию – вызов метода Enter. Оставшиеся сомнения в том, что это именно тот метод, который занимается блокировкой, у меня пропали после того, когда я прочитал оставленный к этому методу комментарий: «Acquire the lock». В дальнейшем погружении в код CoreCLR я не видел особого смысла. Получается, версия с тем, что при занесении новой записи в таблицу интернирования поток заходит в критическую секцию, вынуждая все другие потоки ожидать, когда блокировка спадёт, подтвердилась. Создание объекта типа CrstHolder, а следовательно, и заход в критическую секцию происходят сразу перед вызовом метода m_StringToEntryHashTable->InsertValue.

Блокировка пропадает сразу после того, как мы выходим из ветки else. Так как в этом случае для объекта gch вызывается его деструктор, который и вызывает метод ReleaseLock:

Когда потоков немного, то и простой в работе может быть небольшим. Но когда их количество возрастает, например до 145 (как в случае использования IncrediBuild), то каждый поток, пытающийся добавить новую запись в таблицу интернирования, временно блокирует остальные 144 потока, также пытающихся внести в неё новую запись. Результаты этих блокировок мы и наблюдали в окне Build Monitor.

Заключение

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

Благодарю за прочтение.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Ilya Gainulin. Pitfalls in String Pool, or Another Reason to Think Twice Before Interning Instances of String Class in C#.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *