сжатый вектор / класс массива со случайным доступом к данным

я хотел бы создать класс «сжатый массив» / «сжатый вектор» (подробности ниже), который обеспечивает произвольный доступ к данным с более или менее постоянным временем.

«более или менее постоянное время» означает, что, хотя время доступа к элементу не является постоянным, оно не должно Я продолжаю увеличиваться, когда я приближаюсь к определенной точке массива. Т.е. контейнер не должен делать значительно больше вычислений (например, «распаковать все заново, чтобы получить последний элемент» и «почти ничего не делать, чтобы получить первый»), чтобы получить один элемент. Вероятно, это может быть достигнуто путем разбиения массива на куски сжатых данных. Т.е. доступ к одному элементу должен занять «среднее время» + - некоторое отклонение. Я мог бы сказать, что я хочу, чтобы время доступа в лучшем случае и время доступа в худшем случае были относительно близки к среднему времени доступа.

Каковы мои варианты (подходящие алгоритмы / уже доступные контейнеры - если они есть)?

Сведения о контейнере:

  1. Контейнер действует как линейный массив идентичных элементов (например, std :: vector)
  2. После инициализации контейнера данные становятся постоянными и никогда не изменяются. Контейнер должен обеспечивать доступ только для чтения.
  3. Контейнер должен вести себя как массив / std :: vector - то есть значения, доступные через оператор [], есть .size () и т. Д.
  4. Было бы неплохо, если бы я мог сделать это как шаблон класса.
  5. Доступ к данным должен быть более или менее постоянным. Мне не нужно одинаковое время доступа для каждого элемента, но мне не нужно распаковывать все, чтобы получить последний элемент.

Пример использования:
Двоичный поиск по данным.

Подробности данных:
1. Данные представляют собой структуры, состоящие в основном из чисел с плавающей точкой и нескольких целых. Поплавков больше, чем целых. Без строк.
2. Маловероятно, что в массиве много идентичных элементов, поэтому простое индексирование данных будет невозможно.
3. Размер одного элемента составляет менее 100 байт.
4. Общий объем данных на контейнер составляет от нескольких килобайт до нескольких мегабайт.
5. Данные не редки - это непрерывный блок элементов, все они назначены, «пустых слотов» нет.

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

Идеи / Мнения?

10
задан SigTerm 6 August 2010 в 13:48
поделиться

4 ответа

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

Что касается сжатия, у вас нет исключительного представления о структуре ваших данных, поэтому вас устраивает какое-то стандартное энтропийное кодирование . В идеале я хотел бы запустить GZIP для всего вашего массива и покончить с ним, но это приведет к потере доступа O (1), что для вас очень важно.

Решение заключается в использовании кодирования Хаффмана вместе с индексной таблицей .

Кодирование Хаффмана работает путем замены каждого входного символа (например, байта ASCII) другим символом переменной битовой длины, в зависимости от частоты появления во всем потоке. Например, символ E встречается очень часто, поэтому он получает короткую последовательность битов, тогда как «W» встречается редко и получает длинную последовательность битов.

E -> 0b10
W -> 0b11110

Теперь сожмите весь массив этим методом. К сожалению, поскольку выходные символы имеют переменную длину, вы больше не можете индексировать свои данные, как раньше: элемент номер 15 больше не находится в потоке [15 * sizeof (item)] .

К счастью, эту проблему можно решить, используя дополнительную таблицу индексов index , в которой хранится начало каждого элемента в сжатом потоке. Другими словами, сжатые данные для элемента 15 можно найти в потоке [индекс [15]] ; в индексной таблице накапливаются выходные данные переменной длины.

Итак, чтобы получить элемент 15, вы просто начинаете распаковывать байты в потоке [index [15]] .Это работает, потому что кодирование Хаффмана не делает ничего особенного для вывода, оно просто объединяет новые кодовые слова, и вы можете начать декодирование внутри потока без необходимости декодировать все предыдущие элементы.

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

10
ответ дан 3 December 2019 в 21:19
поделиться

Вы кодируете для встроенной системы и / или у вас есть сотни или тысячи этих контейнеров? В противном случае, хотя я думаю, что это интересный теоретический вопрос (+1), я подозреваю, что замедление в результате выполнения декомпрессии будет нетривиальным, и что было бы лучше использовать использование std :: вектор .

Затем, уверены ли вы, что данные, которые вы храните, достаточно избыточны, чтобы их меньшие блоки действительно можно было сжимать? Вы пробовали сохранять блоки разных размеров (возможно, степени двойки) и пробовали запускать их через gzip в качестве упражнения? Может случиться так, что любые дополнительные данные, необходимые для помощи алгоритму распаковки (в зависимости от подхода), уменьшили бы преимущества использования пространства при создании такого типа сжатого контейнера.

Если вы решите, что сжатие по-прежнему разумно, то есть как минимум пара возможностей, но ни одна из них не написана заранее. Вы можете сжать каждый отдельный элемент, сохранив указатель на кусок сжатых данных. Тогда доступ к индексу остается постоянным, нужно просто распаковать фактические данные.Возможно, использование прокси-объекта упростит и сделает распаковку данных более прозрачной (и, возможно, даже позволит вам использовать std :: vector в качестве базового контейнера).

В качестве альтернативы, std :: deque уже хранит свои данные в кусках, поэтому вы можете использовать здесь аналогичный подход. Например, std :: vector , где каждый блок содержит, скажем, 10 элементов, сжатых вместе в качестве вашего базового контейнера. Затем вы по-прежнему можете напрямую проиндексировать нужный фрагмент, распаковать его и вернуть элемент из распакованных данных. Если вы хотите, ваш содержащий объект (содержащий вектор ) может даже кэшировать один или два последних распакованных фрагмента для повышения производительности при последовательном доступе (хотя это совсем не поможет двоичному поиску).

4
ответ дан 3 December 2019 в 21:19
поделиться

Хорошо, насколько я понимаю, вам нужен какой-то шаблон аксессуара. По сути, создайте адаптер шаблона, который имеет в качестве аргумента один из ваших типов элементов, к которому он обращается изнутри через что угодно, указатель, индекс в вашем BLOB-объекте и т. Д. Сделайте адаптер похожим на указатель:

const T &operator->(void) const;

и т. Д. поскольку легче создать адаптер указателя, чем эталонный адаптер (хотя см. вектор, если вы хотите знать, как написать один из них). Обратите внимание, я сделал этот аксессуар постоянным в соответствии с вашими рекомендациями. Затем предварительно вычислите смещения при загрузке / сжатии большого двоичного объекта и заполните вектор своим шаблонным классом адаптера. Имеет ли это смысл? Если вам нужна более подробная информация, я буду рад предоставить.

Что касается алгоритма сжатия, я предлагаю вам просто провести частотный анализ байтов в вашем большом двоичном объекте, а затем запустить несжатый большой двоичный объект с помощью жестко запрограммированной кодировки Хаффмана (как было более или менее предложено ранее), фиксируя смещение каждого element и сохраняя его в вашем прокси-адаптере, который, в свою очередь, является элементами вашего массива. В самом деле, вы можете сделать все это частью некоторого класса сжатия, который сжимает и генерирует элементы, которые могут быть с самого начала вставлены в ваш вектор с обратным копированием.Опять же, ответьте, если вам нужен образец кода.

0
ответ дан 3 December 2019 в 21:19
поделиться

Я уже давно думал об этом. С теоретической точки зрения я выделил 2 возможности:

  • Легковес, потому что с помощью этого шаблона можно уменьшить количество повторений.
  • Сериализация (сжатие - это некоторая форма сериализации)

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

Второй вариант здесь лучше приспособлен, хотя в целом у него есть небольшой недостаток: недействительность указателя + проблемы с кодированием / декодированием указателя, виртуальными таблицами и т. Д. Примечательно, что это не работает, если элементы ссылаются друг на друга используя указатели вместо индексов.

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

Я бы предпочел пойти другим путем и использовать библиотеку сжатия, такую ​​как 'zlib', например, подобрать быстрый алгоритм, такой как lzo.

  • B * дерево (или вариант) с большим количеством элементов на узел (поскольку оно не перемещается), например, 1001. Каждый узел содержит сжатое представление массива элементов. Индексы не сжимаются.
  • Возможно: cache_view для доступа к контейнеру при сохранении последних 5 (или около того) распакованных узлов или чего-то подобного. Другой вариант - реализовать подсчет ссылок и сохранять данные несжатыми до тех пор, пока кто-то получит дескриптор одного из элементов в узле.

Некоторые примечания:

  • если вам нужно большое количество элементов / ключей на узел, у вас есть почти постоянное время доступа, например, с 1001, это означает, что существует только 2 уровня косвенного обращения, пока вы храните меньше миллиона элементов, 3 уровня косвенного обращения к миллиарду и т. д.
  • вы можете создать читаемый / записываемый контейнер с такой структурой. Я бы сделал так, чтобы я повторно сжимал только после того, как закончу писать узел.
3
ответ дан 3 December 2019 в 21:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: