Подтвердить что ты не робот

Как выполнять десериализацию данных в Haskell

Бенчмаркинг показывает, что библиотека cereal занимает 100 раз дольше, чтобы десериализовать мою структуру данных (подробнее см. ниже), чем требуется для чтения одних и тех же данных с диска:

benchmarking Read
mean: 465.7050 us, lb 460.9873 us, ub 471.0938 us, ci 0.950
std dev: 25.79706 us, lb 22.19820 us, ub 30.81870 us, ci 0.950
found 4 outliers among 100 samples (4.0%)
  4 (4.0%) high mild
variance introduced by outliers: 53.460%
variance is severely inflated by outliers

benchmarking Read + Decode
collecting 100 samples, 1 iterations each, in estimated 6.356502 s
mean: 68.85135 ms, lb 67.65992 ms, ub 70.05832 ms, ci 0.950
std dev: 6.134430 ms, lb 5.607914 ms, ub 6.755639 ms, ci 0.950
variance introduced by outliers: 74.863%
variance is severely inflated by outliers

Это также поддерживается профилированием типичного использования десериализации этой структуры данных в моей программе, где 98% времени тратится на десериализацию данных, а 1% - IO плюс основной алгоритм:

COST CENTRE                    MODULE               %time %alloc

getWord8                       Data.Serialize.Get    30.5   40.4
unGet                          Data.Serialize.Get    29.5   17.9
getWord64be                    Data.Serialize.Get    14.0   10.7
getListOf                      Data.Serialize.Get    10.2   12.8
roll                           Data.Serialize         8.2   11.5
shiftl_w64                     Data.Serialize.Get     3.4    2.9
decode                         Data.Serialize         2.9    3.1
main                           Main                   1.3    0.6

Структура данных, которую я десериализую, является IntMap [Triplet Atom], а определения типов компонентов приведены ниже:

type Triplet a = (a, a, a)

data Point = Point {
    _x :: {-# UNPACK #-} !Double ,
    _y :: {-# UNPACK #-} !Double ,
    _z :: {-# UNPACK #-} !Double }

data Atom = Atom {
    _serial :: {-# UNPACK #-} !Int    ,
    _r      :: {-# UNPACK #-} !Point  ,
    _n      :: {-# UNPACK #-} !Word64 }

Я использую по умолчанию IntMap, (,,) и [] экземпляры, предоставленные cereal, и следующие типы и экземпляры для моих пользовательских типов:

instance Serialize Point where
    put (Point x y z) = do
        put x
        put y
        put z
    get = Point <$> get <*> get <*> get

instance Serialize Atom where
    put (Atom s r n) = do
        put s
        put r
        put n
    get = Atom <$> get <*> get <*> get

Итак, мои вопросы:

  • Почему десериализация настолько медленная вообще?
  • Есть ли способ изменить мою структуру данных (т.е. IntMap/[]), чтобы ускорить десериализацию?
  • Есть ли способ изменить мои типы данных (т.е. Atom/Point), чтобы ускорить десериализацию?
  • Существуют ли более быстрые альтернативы, чем cereal внутри Haskell, или я должен хранить структуру данных в C-стране для более быстрой десериализации (т.е. использовать mmap)?

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

Edit:

Пробовал Дон использовать космический эффективный триплет, и это в четыре раза превысило скорость:

benchmarking Read
mean: 468.9671 us, lb 464.2564 us, ub 473.8867 us, ci 0.950
std dev: 24.67863 us, lb 21.71392 us, ub 28.39479 us, ci 0.950
found 2 outliers among 100 samples (2.0%)
  2 (2.0%) high mild
variance introduced by outliers: 50.474%
variance is severely inflated by outliers

benchmarking Read + Decode
mean: 15.04670 ms, lb 14.99097 ms, ub 15.10520 ms, ci 0.950
std dev: 292.7815 us, lb 278.8742 us, ub 308.1960 us, ci 0.950
variance introduced by outliers: 12.303%
variance is moderately inflated by outliers

Однако он все еще остается узким местом, использующим в 25 раз больше времени, чем IO. Кроме того, может ли кто-нибудь объяснить, почему работает Don Suggestion? Означает ли это, если бы я переключился на нечто, отличное от списка (например, массива?), Чтобы оно тоже улучшилось?

Редактировать # 2: Только что переключился на последнюю платформу Haskell и профилирование повтора для зерновых. Информация значительно более подробно, и я представил hpaste.

4b9b3361

Ответ 1

Ok. Чтобы ответить на это с резюме рекомендаций. Для быстрой десериализации данных:

  • Используйте cereal (строгий вывод bytestring) или binary (ленивый вывод bytetring)
  • Убедитесь, что вы компилируете с -O2, так как эти библиотеки полагаются на inlining для удаления служебных сообщений
  • Используйте плотные типы данных, такие как замена полиморфного кортежа с помощью распакованной специализированной формы.
  • Избегайте преобразования типов данных в списки для их сериализации. Если у вас есть байты, это позаботится. Для распакованных типов массивов вы обычно получаете очень быстрый IO, но стоит дважды проверять экземпляры
  • Вы можете использовать mmap'd IO
  • Для двухсторонних данных рассмотрим более эффективный двойной считыватель.
  • Используйте современные массивы и типы контейнеров, настроенные на производительность, с более поздними версиями GHC.