Каков хороший алгоритм сжатия записей в заблокированном файле?

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

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

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

Требования к алгоритму:

  • Сжатие должно происходить вместо исходного файла без временного расширения файла более чем на несколько блоков от его начального размера.
  • Алгоритм не должен излишне нарушать блоки, которые уже в основном заполнены.
  • Целые блоки должны быть прочитаны или записаны из / в файл за один раз, и следует предполагать, что операция записи относительно дорога.
  • Если записи перемещаются из одного блока в другой, они должны быть добавлены в новое место перед удалением из исходного положения, чтобы в случае прерывания операции записи не были потеряны в результате «неудачного» уплотнения. (Предположим, что это временное дублирование таких записей может быть обнаружено при восстановлении).
  • Память, которую можно использовать для этой операции, может быть порядка нескольких блоков, что составляет очень небольшой процент от общего размера файла.
  • Предположим, что записи имеют порядок от 10 до 1 Кбайт со средним размером около 100 байт. Блоки фиксированного размера имеют порядок 4K или 8K, а файл - порядка 1000 блоков.

person Tall Jeff    schedule 24.09.2008    source источник


Ответы (4)


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

Прежде всего, вы, вероятно, захотите параметризовать свою проблему, определив, что вы считаете «достаточно полным» (где блок достаточно заполнен, чтобы вы не хотите его трогать), и что является «слишком пустым» (где блок имеет так много пустого места, что к нему нужно добавить больше записей). Затем вы можете классифицировать все блоки как достаточно полные, слишком пустые или частично заполненные (те, которые не являются ни достаточно полными, ни слишком пустыми). Затем вы переопределяете проблему как устранение всех слишком пустых блоков путем создания как можно большего количества достаточно полных блоков при минимальном количестве частично заполненных блоков.

Вы также захотите решить, что важнее: поместить записи в наименьшее возможное количество блоков или правильно их упаковать, но с наименьшим количеством прочитанных и записанных блоков.

Мой подход заключался бы в том, чтобы сначала пройти по всем блокам, чтобы классифицировать их все в один из трех классов, определенных выше. Для каждого блока вы также хотите отслеживать доступное в нем свободное пространство, а для слишком пустых блоков вам понадобится список всех записей и их размеров. Затем, начиная с самых больших записей в слишком пустых блоках, переместите их в частично заполненные блоки. Если вы хотите свести к минимуму операции чтения и записи, переместите их в любой из блоков, которые у вас в настоящее время есть в памяти. Если вы хотите свести к минимуму потраченное впустую пространство, найдите блок с наименьшим количеством пустого пространства, который все еще будет содержать запись, при необходимости считывая блок. Если ни один блок не будет содержать запись, создайте новый блок. Если блок в памяти достигает порога «достаточно заполнен», запишите его. Повторяйте, пока все записи в частично заполненных блоках не будут размещены.

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

person TimB    schedule 24.09.2008
comment
Спасибо, что указали на сравнение проблем с упаковкой бункера. Это полезно. Сложная часть решения - это огромный объем записей, которые нужно сканировать на первом проходе, и статистика хранения непомерно высока. Кроме того, поскольку запись обходится дорого, в некотором смысле у вас есть шанс или два переписать данный блок. - person Tall Jeff; 25.09.2008
comment
В идеале я думаю о том, что какое-то количество проходов используется для нацеливания на определенные блоки и записи для консолидации. то есть: найдите самый низкий висячий плод на каждом проходе, чтобы собрать урожай и остановитесь, когда останется слишком много времени или не останется никаких значительных оптимизаций. Еще раз спасибо! - person Tall Jeff; 25.09.2008

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

E.g.:

// records should be sorted by size in memory (probably in a balanced BST)
records = read last N blocks on disk;

foreach (block in blocks) // read from disk into memory
{
    if (block.hasBeenReadFrom())
    {
        // we read from this into records already
        // all remaining records are already in memory

        writeAllToNewBlocks(records);

        // this will leave some empty blocks on the disk that can either
        // be eliminated programmatically or left alone and filled during
        // normal operation

        foreach (record in records)
        {
            record.eraseFromOriginalLocation();
        }

        break;
    }

    while(!block.full())
    {
        moveRecords = new Array; // list of records we've moved

        size = block.availableSpace();
        record = records.extractBestFit(size);
        if (record == null)
        {
            break;
        }

        moveRecords.add(record);
        block.add(record);

        if (records.gettingLow())
        {
            records.readMoreFromDisk();
        }
    }

    if(moveRecords.size() > 0)
    {
        block.writeBackToDisk();
        foreach (record in moveRecords)
        {
            record.eraseFromOriginalLocation();
        }
    }
}

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

person Derek Park    schedule 24.09.2008
comment
Мы начали с этого подхода, но оказалось, что неравномерность размеров записей часто оставляет после себя неоптимально уплотненные блоки. С некоторой готовностью искать больше, можно найти более подходящие варианты, но затем он превратился в NP-трудный, и теперь требуется дополнительная эвристика. - person Tall Jeff; 25.09.2008
comment
Пожалуйста. Я ожидал, что настройка количества блоков, которые вы храните в памяти за один раз, поможет. Если вы храните в памяти, скажем, десять блоков записей, я бы ожидал, что в целом вы сможете довольно хорошо заполнить большинство частичных блоков. - person Derek Park; 25.09.2008
comment
Вы также можете хранить в памяти только более мелкие записи, а затем сжимать оставшиеся большие записи (чтобы со временем вы не заполняли всю память большими записями). - person Derek Park; 25.09.2008

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

См. «Алгоритмы приближения упаковки бункера: комбинаторный анализ» Коффмана и др. al.

person Rafał Dowgird    schedule 25.09.2008
comment
Спасибо! за ссылку на сравнение проблем с упаковкой контейнеров и эту статью по анализу подхода к некоторым различным подходам. - person Tall Jeff; 25.09.2008

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

Дефрагментация кучи за ограниченное время

person Richard    schedule 25.09.2008
comment
Спасибо. Однако, как вы упомянули, в этой статье не рассматриваются две динамики проблемы. 1) биновый характер блоков фиксированного размера и (2) линейная байтовая изменчивость в размерах записи. то есть: характер 2 ^ N записей в куче является ключевым элементом их решения. - person Tall Jeff; 25.09.2008