Производительность больших плит из-за накладных расходов на размещение, инициализацию и освобождение [C ++ 11] - PullRequest
0 голосов
/ 07 февраля 2019

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

Прекрасный примерОдним из таких мелкозернистых распределителей является Макалу, и в документе очень подробно описывается, как это делается: Макалу: быстрое восстановление энергонезависимой памяти

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

У меня есть график для сравнения std::string и трех типов строк, которые я построил, которые работают быстрее: Direct, Symbiont и Head, в которых сравниваются накладные расходы на выделение, инициализацию и освобождение (для краткости ADIDOC) для сортировки этих строк в простых массивах с использованием выделения слябов в стеке.

std::string у класса есть голова с указателем на хвост, который копируется при записи (COW) в C ++ 11 (это COW меняется, я думаю, в будущем выпуске, но дополнительное детальное распределение останется, я уверен.) Головы std::string размещены в одном массиве slab,но все хвосты распределяются в разных местах в пуле памяти.

Все три других класса распределяются как единый массив slab без какого-либо детального распределения.Вот три графика для этих издержек в процессе создания инициализирующих и освобождающих 8-байтовых, 128-байтовых и 1024-байтовых строк для сортировки (затраты на сортировку измеряются отдельно и анализируются в другой публикации).

ПРИМЕЧАНИЯ.

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

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

  3. Если бы все это было сделано за mmap, можно было бы запустить процесс и выполнить mmap с MAP_POPULATE флаг перед выполнением любых трасс, чтобы выровнять игровое поле.Поскольку это в стеке, делается попытка выполнить то же самое путем выделения огромного массива и записи в его конец, и это, похоже, устанавливает все накладные расходы на выделение памяти (запись в таблице страниц / выделение PTE и пропуск TLBнакладные расходы) для последующих запусков во время процесса.См. Слайды из hotstorage 2017, для получения более подробной информации по адресу: Эффективный ввод-вывод отображаемого файла памяти для файловых систем в памяти

  4. Используемая программа SortBench.cpp измеряет различныестроки и контейнеры с идентичным кодом сортировки шаблона: все строки и контейнеры испытывают одинаковое количество операций сравнения и замены и работают быстро или медленно в зависимости от базовой физики этих строк и контейнеров.Сортировка - это просто метод определения масштабируемости строк и контейнеров.

  5. Для этих измерений числа сортировки не имеют значения, потому что это настройка для сортировки и выход после сортировки, чтоизмеряется.Для записи в каждом наборе запусков строки Direct выполняются первыми и принимают попадание в выделение памяти, а std::string s выполняются последними, как видно из файлов данных.

Расходы на быструю сортировку, инициализацию и освобождение (ADIDOC) для 8-байтовых строк

Быстрая сортировка ADIDOC для 128-байтовых строк

Быстрая сортировка ADIDOC для строк длиной 1024 байта

Измерения накладных расходов на распределение, инициализацию и освобождение были выполнены с использованием наносекундного таймера.Код для SortBench.cpp находится в GitHub: [Kaldanes R & D] (https://github.com/charlesone/Kaldanes)

Первая проблема, которую мы заметили, это, похоже, очень хорошо масштабируется для одного из них.Синяя линия - это класс строк Direct, который является просто блоком байтов строки в структуре как элемент массива.Это тот класс, который вообще не имеет инициализации, просто хранит строку.После того, как он достигает масштаба 128 000 8-байтовых строк (1 МБ), накладные расходы масштабируются произвольно плоско.Это распределитель слябов операционной системы на работе?Это была моя рабочая гипотеза, но я не думаю, что именно здесь применяется волшебная пыль, кажется, что ядро ​​делает что-то особенное для массивных объектов.

Вторая проблема заключается в том, что в последней точке134 217 728 8-байтовых строк (после чего std::string [желтая линия] исчерпывает память на моем компьютере с 15 ГБ, из-за хранения только 1 ГБ строковых данных)) класс std::string на 5 порядков хуже, чем лучший, выделенный для плитыкласс без инициализации.

Если в строках Direct использование распределителя слябов остается плоским на уровне терабайта, что может привести к разнице в восемь порядков.И еще три в петабайтном масштабе дают разницу в восемь порядков (308 дней, почти год).Параллельное использование 7400 ядер может привести к сокращению времени до одного часа, а выделение слябов, если оно будет плоским в этом масштабе, останется на 1/10 миллисекунды в одном ядре.Это необходимо проверить в масштабе на системах Gen-Z или Intel XPoint.Я бы предположил (но не проверял), что память без файловой версии mmap (предположительно с флагами MAP_NORESERVE и MAP_POPULATE) будет иметь те же преимущества в производительности.

Мой вопрос касается C ++среда выполнения и ядро, почему этот вид огромного распределения настолько произвольно быстр и при каком размере он перестает быть постоянным?

Вот информация об архитектуре:

[charles @ localhost ~]$ lscpu Архитектура: x86_64 Операционные режимы процессора: 32-разрядный, 64-разрядный Порядок байтов: Процессоры с прямым порядком байтов (байтов): 4 Список активных процессоров: 0-3 Поток на ядро:1 ядро ​​(я) на сокет: 4 сокета (ов): 1 узел (ы) NUMA: 1 идентификатор поставщика: семейство процессоров GenuineIntel: 6 модель: 58 название модели: процессор Intel (R) Core (TM) i5-3570 @ 3,40ГГц Шаг: 9 МГц ЦП: 1989.132 МГц ЦП: 3800.0000 МГц ЦП: 1600.0000 BogoMIPS: 6784.92 Виртуализация: кэш VT-x L1d: кэш-память L1i 32 КБ: кэш-память L2 32 КБ: кэш-память L3 256 КБ: 6144 КБ узла NUMA ЦП 0: 0-3 Флаги: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush DTs ACPI MMX fxsr ссе sse2 сс ХТ тм РВЕ системный вызов пх rdtscp лм constant_tsc arch_perfmon УИБ БПС rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu ПНИ PCLMULQDQ dtes64 монитор ds_cpl VMX SMX Эст tm2 SSSE3 CX16 xtpr pdcm PCID sse4_1 sse4_2 x2APIC POPCNT tsc_deadline_timer АЕС XSAVE AVX F16C rdrand lahf_lmepb tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms xsaveopt dtherm ida arat pln pts

Флаги make-файла: CPPFLAGS = -std = c ++ 11 -Ofast CXXFLAGS = -Wall

gcc /.5 о ядре Centos-7: [charles @ localhost] $ uname -a Linux localhost.localdomain 3.10.0-862.11.6.el7.x86_64 # 1 SMP вт 14 августа 21:49:04 UTC 2018 x86_64 x86_64 x86_64 GNU /Linux

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...