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

Uint64 idx [] = {0, 20, 500, 1024, ..., 103434};

Это говорит о том, что первая строка находится в позиции 0, вторая в позиции 20, третья в позиции 500 и n-я в позиции 103434.

Позиции всегда представляют собой неотрицательные 64-битные целые числа в последовательном порядке. Хотя числа могут варьироваться в зависимости от разницы, на практике я ожидаю, что типичная разница будет в диапазоне от 2 ^ 8 до 2 ^ 20. Я ожидаю, что этот индекс будет отображен в памяти, а доступ к позициям будет осуществляться случайным образом (предполагается равномерное распределение).

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

Есть подсказки? Библиотека c была бы идеальной, но библиотека c ++ также позволила бы мне запустить некоторые начальные тесты.

Еще несколько подробностей, если вы все еще следите. Это будет использоваться для создания библиотеки, подобной cdb (http://cr.yp.to/cdb /cdbmake.html) поверх библиотеки cmph (http://cmph.sf.net) . Короче говоря, он предназначен для ассоциативной карты только для чтения на большом диске с небольшим индексом в памяти.

Поскольку это библиотека, у меня нет контроля над вводом, но типичный вариант использования, который я хочу оптимизировать, имеет миллионы сотен значений, средний размер значения в диапазонах нескольких килобайт и максимальное значение 2 ^ 31.

Для записи: если я не найду библиотеку, готовую к использованию, я намерен реализовать дельта-кодирование в блоках по 64 целых числа с начальными байтами, определяющими смещение блока. Сами блоки будут проиндексированы деревом, что даст мне время доступа O (log (n / 64)). Есть слишком много других вариантов, и я бы предпочел не обсуждать их. Я действительно с нетерпением жду возможности использовать код, а не идеи о том, как реализовать кодировку. Я буду рад поделиться со всеми, что я сделал, когда это заработает.

Я ценю вашу помощь и дайте мне знать, если у вас возникнут сомнения.

12
Davi 5 Июл 2009 в 00:20

6 ответов

Лучший ответ

Я использую fastbit (Kesheng Wu LBL.GOV), кажется, вам нужно что-то хорошее, быстрое и СЕЙЧАС, так что fastbit - это весьма конкурентоспособное усовершенствование Oracle BBC (побайтовый растровый код, berkeleydb). Он прост в настройке и очень хорош с точки зрения внешнего вида.

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

У Дэниела Лемира есть несколько библиотек для C / ++ / Java, выпущенных на code.google, Я прочитал некоторые из его статей, и они довольно хороши, несколько улучшений на fastbit и альтернативных подходах к переупорядочению столбцов с перестановками серых кодов.

Чуть не забыл, я также наткнулся на Tokyo Cabinet, хотя я не думаю, что он подойдет для моего текущего проекта. , Я мог бы подумать об этом больше, если бы я знал об этом раньше;), он имеет большую степень совместимости,

Tokyo Cabinet написан на языке C и предоставляется как API C, Perl, Ruby, Java и Lua. Tokyo Cabinet доступен на платформах, API которых соответствуют C99 и POSIX.

Как вы упомянули CDB, в тесте TC есть режим TC (несколько операционных ограничений TC поддержки для изменения производительности), в котором он превзошел CDB в 10 раз по производительности чтения и в 2 раза по записи.

Что касается требований к дельта-кодировке, я вполне уверен в bsdiff и его способности превзойти любые file.exe система исправлений содержимого, она также может иметь несколько основных интерфейсов для ваших общих нужд.

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

6
RandomNickName42 13 Авг 2009 в 15:17

У вас есть два противоречащих друг другу требования:

  1. Вы хотите сжать очень маленькие элементы (по 8 байт каждый).
  2. Вам нужен эффективный произвольный доступ к каждому элементу.

Второе требование, скорее всего, наложит фиксированную длину для каждого элемента.

0
mmx 4 Июл 2009 в 20:36

Что именно вы пытаетесь сжать? Если вы думаете об общем пространстве индекса, действительно ли стоит экономить место?

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

Для быстрого поиска индексы будут реализованы с использованием чего-то вроде B + Tree.

0
Eugene Yokota 4 Июл 2009 в 20:43

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

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

Прошу прощения за венгерскую нотацию и магические числа, разбросанные в коде. Как я уже сказал, я написал это много лет назад :-)

IndexCompressor.h

//
// index compressor class
//

#pragma once

#include "File.h"

const int IC_BUFFER_SIZE = 8192;

//
// index compressor
//
class IndexCompressor
{
private :
   File        *m_pFile;
   WA_DWORD    m_dwRecNo;
   WA_DWORD    m_dwWordNo;
   WA_DWORD    m_dwRecordCount;
   WA_DWORD    m_dwHitCount;

   WA_BYTE     m_byBuffer[IC_BUFFER_SIZE];
   WA_DWORD    m_dwBytes;

   bool        m_bDebugDump;

   void FlushBuffer(void);

public :
   IndexCompressor(void) { m_pFile = 0; m_bDebugDump = false; }
   ~IndexCompressor(void) {}

   void Attach(File& File) { m_pFile = &File; }

   void Begin(void);
   void Add(WA_DWORD dwRecNo, WA_DWORD dwWordNo);
   void End(void);

   WA_DWORD GetRecordCount(void) { return m_dwRecordCount; }
   WA_DWORD GetHitCount(void) { return m_dwHitCount; }

   void DebugDump(void) { m_bDebugDump = true; }
};

IndexCompressor.cpp

//
// index compressor class
//

#include "stdafx.h"
#include "IndexCompressor.h"

void IndexCompressor::FlushBuffer(void)
{
   ASSERT(m_pFile != 0);

   if (m_dwBytes > 0)
   {
      m_pFile->Write(m_byBuffer, m_dwBytes);
      m_dwBytes = 0;
   }
}

void IndexCompressor::Begin(void)
{
   ASSERT(m_pFile != 0);
   m_dwRecNo = m_dwWordNo = m_dwRecordCount = m_dwHitCount = 0;
   m_dwBytes = 0;
}

void IndexCompressor::Add(WA_DWORD dwRecNo, WA_DWORD dwWordNo)
{
   ASSERT(m_pFile != 0);
   WA_BYTE buffer[16];
   int nbytes = 1;

   ASSERT(dwRecNo >= m_dwRecNo);

   if (dwRecNo != m_dwRecNo)
      m_dwWordNo = 0;
   if (m_dwRecordCount == 0 || dwRecNo != m_dwRecNo)
      ++m_dwRecordCount;
   ++m_dwHitCount;

   WA_DWORD dwRecNoDelta = dwRecNo - m_dwRecNo;
   WA_DWORD dwWordNoDelta = dwWordNo - m_dwWordNo;

   if (m_bDebugDump)
   {
      TRACE("%8X[%8X] %8X[%8X] : ", dwRecNo, dwRecNoDelta, dwWordNo, dwWordNoDelta);
   }

   // 1WWWWWWW
   if (dwRecNoDelta == 0 && dwWordNoDelta < 128)
   {
      buffer[0] = 0x80 | WA_BYTE(dwWordNoDelta);
   }
   // 01WWWWWW WWWWWWWW
   else if (dwRecNoDelta == 0 && dwWordNoDelta < 16384)
   {
      buffer[0] = 0x40 | WA_BYTE(dwWordNoDelta >> 8);
      buffer[1] = WA_BYTE(dwWordNoDelta & 0x00ff);
      nbytes += sizeof(WA_BYTE);
   }
   // 001RRRRR WWWWWWWW WWWWWWWW
   else if (dwRecNoDelta < 32 && dwWordNoDelta < 65536)
   {
      buffer[0] = 0x20 | WA_BYTE(dwRecNoDelta);
      WA_WORD *p = (WA_WORD *) (buffer+1);
      *p = WA_WORD(dwWordNoDelta);
      nbytes += sizeof(WA_WORD);
   }
   else
   {
      // 0001rrww
      buffer[0] = 0x10;

      // encode recno
      if (dwRecNoDelta < 256)
      {
         buffer[nbytes] = WA_BYTE(dwRecNoDelta);
         nbytes += sizeof(WA_BYTE);
      }
      else if (dwRecNoDelta < 65536)
      {
         buffer[0] |= 0x04;
         WA_WORD *p = (WA_WORD *) (buffer+nbytes);
         *p = WA_WORD(dwRecNoDelta);
         nbytes += sizeof(WA_WORD);
      }
      else
      {
         buffer[0] |= 0x08;
         WA_DWORD *p = (WA_DWORD *) (buffer+nbytes);
         *p = dwRecNoDelta;
         nbytes += sizeof(WA_DWORD);
      }

      // encode wordno
      if (dwWordNoDelta < 256)
      {
         buffer[nbytes] = WA_BYTE(dwWordNoDelta);
         nbytes += sizeof(WA_BYTE);
      }
      else if (dwWordNoDelta < 65536)
      {
         buffer[0] |= 0x01;
         WA_WORD *p = (WA_WORD *) (buffer+nbytes);
         *p = WA_WORD(dwWordNoDelta);
         nbytes += sizeof(WA_WORD);
      }
      else
      {
         buffer[0] |= 0x02;
         WA_DWORD *p = (WA_DWORD *) (buffer+nbytes);
         *p = dwWordNoDelta;
         nbytes += sizeof(WA_DWORD);
      }
   }

   // update current setting
   m_dwRecNo = dwRecNo;
   m_dwWordNo = dwWordNo;

   // add compressed data to buffer
   ASSERT(buffer[0] != 0);
   ASSERT(nbytes > 0 && nbytes < 10);
   if (m_dwBytes + nbytes > IC_BUFFER_SIZE)
      FlushBuffer();
   CopyMemory(m_byBuffer + m_dwBytes, buffer, nbytes);
   m_dwBytes += nbytes;

   if (m_bDebugDump)
   {
      for (int i = 0; i < nbytes; ++i)
         TRACE("%02X ", buffer[i]);
      TRACE("\n");
   }
}

void IndexCompressor::End(void)
{
   FlushBuffer();
   m_pFile->Write(WA_BYTE(0));
}
0
Ferruccio 5 Июл 2009 в 01:32

Вы пропустили важную информацию о количестве строк, которые собираетесь проиндексировать.

Но учитывая, что вы говорите, что ожидаете минимальной длины индексированной строки равной 256, сохранение индексов в виде 64% влечет за собой не более 3% накладных расходов. Если общая длина строкового файла меньше 4 ГБ, вы можете использовать 32-битные индексы и понести 1,5% накладных расходов. Эти числа подсказывают мне, что если сжатие имеет значение, лучше сжимать строки, а не индексы . Для этой проблемы кажется подходящим вариантом LZ77.

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

Хотелось бы получить больше данных по проблеме:

  • Количество струн
  • Средняя длина строки
  • Максимальная длина строки
  • Средняя длина струн
  • Степень сжатия файла строк с помощью gzip
  • Разрешено ли вам изменять порядок строк для улучшения сжатия

РЕДАКТИРОВАТЬ

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

Вы просили существующие библиотеки. Что касается группировки и дельта-кодирования, я сомневаюсь, что вы многое найдете. Что касается целочисленных кодов переменной длины, я не вижу особого подхода к библиотекам C, но вы можете найти кодировки переменной длины в Perl и Python . На эту тему есть масса статей и несколько патентов, и я подозреваю, что вам придется создавать собственные. Но есть несколько простых кодов, и вы можете попробовать UTF-8 - он может кодировать целые числа без знака длиной до 32 бит, а вы можете получить код C из Plan 9 и, я уверен, многие другие источники.

0
Norman Ramsey 5 Июл 2009 в 05:53

Вы работаете в Windows? Если это так, я рекомендую создать файл mmap, используя исходное решение, предложенное вами, а затем сжать файл с помощью сжатия NTLM . Код вашего приложения никогда не знает, что файл сжат, и ОС выполняет сжатие файла за вас. Возможно, вы не думаете, что это будет очень производительным или хорошим сжатием, но я думаю, вы будете удивлены, если попробуете это.

0
brianegge 7 Июл 2009 в 01:54