Как избежать "спагетти указателя кучи" в динамических графиках?


Общая проблема

Предположим, вы кодируете систему, состоящую из графа, плюс правила перезаписи графа, которые могут быть активированы в зависимости от конфигурации соседних узлов. То есть у вас есть динамический график, который растет/сжимается непредсказуемо во время выполнения. Если вы наивно используете malloc, новые узлы будут распределены в произвольных позициях в памяти; через некоторое время ваша куча будет представлять собой указатель, что даст вам ужасную эффективность кэша. Есть ли легкий, инкрементный метод, чтобы узлы , которые соединяются вместе, оставались близко друг к другу в памяти?

Что я пытался

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

Твердое тело Пример

Это система, которую я пытаюсь реализовать. это краткий фрагмент кода, который я пытаюсь оптимизировать в C. это repo-прототипная, работающая реализация в JS, с ужасной эффективностью кэша (и самого языка). это видео показывает систему в действии графически.

4 27

4 ответа:

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

Вы можете посмотреть на это с точки зрения сборки мусора в полупространстве. Это нетрудно реализовать (я сделал это для интерпретатора), тем более что вы делаете это только для структур узлов фиксированного размера. Выделите из одного большого блока (называемого полупространством) памяти. Когда он становится слишком полным или фрагментированным, остановитесь и скопируйте все в другой (который вы также можете сделать больше). Фокус в том, чтобы обновить все указатели. Для этого существует очень элегантный и эффективный алгоритм под названием scan copy. Естьхорошее обсуждение этого в Корнелле . Он, по сути, сначала проходит по графу в ширину, копируя по ходу, без какого-либо дополнительного пространства, кроме того, что вы копируете. Приятным свойством алгоритма является то, что первые уровни ширины оказываются смежными после каждой копии. Если это достаточно хороший уровень локализации, вы получите его очень эффективно с помощью этого метода.

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

Вы можете malloc большой блок памяти при запуске, затем вы выделяете пространство из этого блока. Вам понадобится отдельная структура, чтобы отслеживать, что было и что не было выделено. Если вы знаете, что все выделенные структуры имеют определенный размер, который может упростить управление выделенным / свободным пространством, то есть массив индексов, в противном случае вы можете использовать связанный список указателей в свободном пространстве. Учитывая, что вы, скорее всего, будете распределять структуры по одной, вам, вероятно, не нужно беспокоиться о том, чтобы отслеживать самый маленький и/или самый большой смежный блок свободного пространства.

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

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

Это можно рассматривать как задачу разбиения графа, в которой вы пытаетесь кластеризировать связанные узлы графа в одном блоке памяти. METIS является хорошим алгоритмом разбиения графа, который, вероятно, не подходит для вашего варианта использования, потому что он требует глобальных операций по всему графу, однако два распределенных алгоритма разбиения графа, которые могут быть изменены, чтобы быть применимыми для вашего варианта использования, являются DIDIC и Ja-Be-Ja - Первые попытки минимизируйте число ребер, пересекающих секции, независимо от размера секции, в то время как последняя пытается создать секции одинакового размера. Оба алгоритма требуют только локального знания графа для кластеризации каждого узла, поэтому, если у вас есть какие-либо запасные циклы, вы можете использовать их для постепенной перебалансировки графа. фенхель - это связанный алгоритм, который работает на потоковых графах, поэтому, например, вы можете использовать фенхель или аналогичный алгоритм при первоначальном выделении узла графа, а затем использовать Дидык/Джа-быть-Джа, когда восстановление равновесия на графике.