Автор сценария Шивендра Тивари и Константинос Стаматиадис

В язык программирования D внесено много изменений, влияющих на перенос исходного кода с D1 на D2 [1]. Недавно мы перенесли наши приложения D с D1 на D2 и заметили значительное увеличение использования памяти. В этом блоге мы проведем вас через шаги, необходимые для решения проблемы с высокой потребляемой памятью с помощью топления массива. Он довольно прост в использовании, но требует знания соответствующих сценариев, в которых его можно использовать.
Введение
D - это язык программирования, разработанный на основе C ++, также известный как Dlang. Это мультипарадигмальный системный язык программирования, созданный Уолтером Брайтом в Digital Mars и выпущенный в 2001 году. Первая версия D называется D1. Он переработал некоторые основные функции C ++, а также сохранил характеристики других языков, особенно Java, Python, Ruby, C # и Eiffel [2]. Ожидается, что у вас есть базовые знания о Dlang; однако код D очень похож на хорошо известные языки высокого уровня, поэтому его должно быть довольно легко понять.
В группе dunnhumby Media мы уже более десяти лет используем язык D для реализации высокопроизводительных приложений для ставок. Системам торгов требуется высокая доступность и малое время отклика (порядка миллисекунд). В 2010 году была представлена новая версия языка D, получившая название D2. Мы мигрировали наши приложения D1 в D2, чтобы воспользоваться преимуществами различных языковых улучшений, касающихся безопасности потоков в переменных, массивах, строках, шаблонах и т. Д. Подробности функций D2 выходят за рамки этого обсуждения; однако их можно найти в [1, 4, 5]. D1 поднял следующие проблемы в наших существующих приложениях:
- Невозможно использовать новые языковые функции, пока приложения не будут перенесены на D2.
- Невозможно использовать новые внешние библиотеки, которые работают только в D2.
- Накладные расходы на обслуживание - унаследованные библиотеки должны поддерживаться в версиях D1 и D2 для раздельной поддержки приложений в D1 и D2.
Определение проблемы
Проблемы с высоким использованием памяти
После того, как мы перенесли одно из наших приложений для ставок, оно начало потреблять больше оперативной памяти, как показано на графике ниже. На рис. 1 показано использование памяти приложением в D1, которое составляет ~ 1 ГБ; однако на рис. 2 использование памяти приложением в D2 достигло ~ 1,5 ГБ.


Причина - агрессивное выделение памяти при изменении размера массива в D2
Уменьшение длины динамического массива, затем повторное увеличение его длины и добавление к нему будет повторно использовать тот же кусок памяти; это называется топтанием массива. По умолчанию в D2 отключен режим работы с массивом. D2 может агрессивно выполнять предупредительное выделение памяти для будущего использования. Мы довольно часто изменяем размеры массивов в приложениях для ставок, чтобы скорректировать размер данных и повторно использовать существующий пул памяти.
Чтобы упростить иллюстрацию проблемы, мы взяли простые примеры. В приведенном ниже фрагменте кода в строке №5 D2 не перераспределяет дополнительную память для нового массива b; вместо этого он просто указывает на ячейку памяти массива a (см. строку №8), поэтому утверждение выполняется успешно. Как только D2 выполняет строку №11 для добавления нового элемента в массив, он перераспределяет новый массив с дополнительной памятью. Проблема в том, что перераспределение памяти выполняется с учетом аналогичных возможностей операций добавления в будущем. В случае, если вы действительно не собираетесь выполнять дополнительные добавления в будущем, бесполезно выделять дополнительную память.
А теперь представьте огромное количество мест, где выполняется изменение размера массива. Еще опаснее, если выделение для каждого запроса. Наши заявки на участие в торгах получают более 400 тысяч запросов в секунду. Следовательно, такое предупредительное распределение памяти привело к огромному увеличению памяти.

Другой пример, приведенный ниже, создает аналогичное поведение после увеличения емкости массива. В строке №11 длина массива установлена равной 4, что совпадает с размером массива a. Даже после изменения размера он может использовать ту же ячейку памяти, что и массив a. Однако установка новой длины вызывает перераспределение памяти, поскольку D2 считает, что эта операция может повториться. Теперь, в результате, массивы a и b теперь указывают на разные ячейки памяти в строке №14.

Как решить проблему с памятью в D2?
По возможности используйте метод acceptSafeAppend ()
Вызов метода acceptSafeAppend () сообщает среде выполнения, что можно безопасно добавить данные в непрерывную память этого массива. Операция добавления, выполняемая в этом массиве после вызова функции acceptSafeAppend (), добавляет новые значения в тот же массив. Следует отметить, что этот метод следует использовать только тогда, когда есть уверенность в том, что использование непрерывной памяти безопасно. Также важно убедиться, что доступный объем памяти соответствует требуемому размеру. Если в целевых ячейках памяти есть значения, эти элементы будут перезаписаны.

Почему проблема не возникла в D1?
В D1 по умолчанию включен режим "топтания" массива. Поэтому проблем с перераспределением в приложениях D1 не было. Вместо этого D2 по умолчанию предотвращает выход из строя массива.
Дополнительный совет: используйте конфигурацию среды выполнения сборщика мусора (GC)
Начиная с версии 2.067, сборщик мусора теперь можно настроить через командную строку, среду или параметры, встроенные в исполняемый файл. Важно помнить об использовании правильных параметров GC при запуске приложения. Нам нужно было использовать тот же параметр, который мы использовали в D1, как показано ниже:

После использования вышеуказанного параметра GC среды выполнения и использования вызова функции acceptSafeAppend () перед изменением размера массива использование памяти резко снизилось, что аналогично приложениям, работающим в D1 (показано на рис. 3).

Вывод
Массивное топтание удобно использовать, когда вы знаете, что делаете. Включение работы с массивом дает два основных преимущества:
- Производительность - поскольку перераспределение памяти сокращается, производительность систем остается благоприятной.
- Контролируемое использование памяти - поскольку это позволяет повторно использовать существующую память, может быть полезно использовать вытеснение памяти.
С другой стороны, использование памяти имеет свои недостатки:
- Шансы на ошибку высоки. В случае ошибки это может привести к повреждению памяти, так как она может перезаписать память, которая может принадлежать другим объектам.
- При использовании «вытеснения» массива требуемый уровень управления памятью может свести на нет преимущества сборщика мусора.
В целом, мы обнаружили, что массив очень помогает в решении проблемы использования памяти в наших приложениях.
Кредит
Это улучшение - результат совместной работы. Мы хотим выразить особую благодарность нашему коллеге Стефану Коху за его помощь в этой теме.
использованная литература
1. Миграция кода D1 на язык программирования D2 - D
2. D (язык программирования) - Википедия
3. Торги в реальном времени - Википедия
4. D1-› D2 Часть 3: магический модуль | Социомантические лаборатории