Сериализация простых структур данных в C++: оценка 1d против 2d представления
Я пытаюсь выбрать простой, но эффективный подход к сериализации для некоторых больших структур данных и надеюсь получить отзывы о простых тестах, которые я только что написал, чтобы помочь мне принять решение.
Данная структура данных включает в себя несколько двумерных векторов с плавающей точкой:
vector<<vector float> > data;
Векторы компонентов в основном имеют одинаковый размер, но это не гарантия. Однако существует очень ограниченное количество вариаций в размерах компонентных векторов. Например, обычно 99% компонентных векторов будут иметь одинаковый размер, а остаток будет несколько кратным базовому размеру. Может быть возможным усечь более длинные или дополнить более короткие векторы компонентов, но это может оказать негативное влияние на производительность программы в некоторых случаях.
Эти двухмерные векторы обычно содержат несколько сотен тысяч компонентных векторов, в то время как каждый компонентный вектор обычно содержит 40 элементов.
Эти данные считываются один раз при первой загрузке программы, и после этого информация многократно повторяется, но никогда не изменяется.
С точки зрения того, как сериализовать эти данные, кажется, есть два очень очевидных варианта:
- Преобразуйте 2-й вектор в 1-й вектор и сериализуйте 1-й вектор с простым заголовком. Это должно быть очень быстрым для чтения и записи и примерно таким же, как двумерный вектор для итерации, хотя и с немного другой настройкой. К сожалению, это происходит в случае, когда векторы компонентов имеют переменную длину; итерация также потребует дополнительного бухгалтерского учета.
- Сохраните 2-ую структуру и используйте немного более сложный заголовок. Это, вероятно, будет несколько медленнее читать и писать, но мы все равно делаем это только один раз. Это позволяет нам учитывать векторы компонентов переменной длины и означает, что нет необходимости в дополнительном учете во время итерации. Существующий код, который использует эти данные, не нужно изменять.
Я написал простую тестовую программу, чтобы попытаться получить общее представление о том, как эти варианты могут быть реализованы на практике:
run-serializer.cpp: http://pastebin.com/AzWivQDU
Serializer.h: http://pastebin.com/dvbqN1EY
Serializer.cpp: http://pastebin.com/0eDCg7sE
$ g++ -o run-serializer run-serializer.cpp Serializer.cpp
$ ./run-serializer 500
Initializing tests...
Total elems: 11441408
Test binfile: test.bin
Binary write: 0.038397
Average runtimes for 500 iterations:
Binary 1d read: 0.0228143
Binary 2d read: 0.156756
1d iter time: 0.125914
2d iter time: 0.126896
Похоже, это указывает на то, что на моем ноутбуке и в моем примере реализации 2d версия занимает примерно в 8 раз больше, чем 1d версия. Не слишком удивительно, и для моих целей это разумное количество времени. Это также указывает на то, что, как и ожидалось, разница между временем 1d и временем 2d очень мала, но, возможно, 1d версия выигрывает smidgen.
Учитывая (потенциально) переменную длину компонентных векторов, я склонен придерживаться существующего двумерного подхода. Начальный процесс загрузки заметно медленнее, но все же довольно незначительный с точки зрения приложения. Похоже, итерация не является проблемой в любом случае, и двумерный подход означает, что мне не придется изменять какой-либо другой код, использующий в настоящее время эти структуры данных.
Итак, я думаю, мой настоящий вопрос:
- Являются ли эти тесты в целом разумными, и имеют ли смысл выводы, которые я из них сделал?
Извиняюсь за многословность.
1 ответ
Есть некоторые возможности для улучшения.
Таймеры более соответствуют меньшей детализации. Не складывайте время итераций, потратьте время полной операции и делайте любое деление на количество итераций после. Это также позволяет коду обращаться со временем на более высоких уровнях, где это имеет смысл. Низкоуровневый код может его вычислить, но с этим ничего не поделать.
Использование времени стены в течение всей продолжительности исполняемого файла (или N вызовов исполняемого файла) на самом деле не так уж плохо. Это грубо, но это должно усреднить с достаточно большой выборкой.
Отдельные исполняемые файлы могут уменьшить смещение от статической инициализации, возможно, используя условную компиляцию для каждой из различных стратегий.
Запустите ожидаемые размеры тестов много раз независимо, вместо того, чтобы выполнять необычные размеры меньше. Повторное выполнение тестов в одном и том же экземпляре исполняемого файла может привести к неверным результатам. Я мог видеть сценарий, где ОС, например, делала бы повторное использование памяти дешевле, чем при первом выделении.
В зависимости от системы, я ожидаю, что выделение памяти или конкуренция на диске будут доминировать во время выполнения. В ноутбуках обычно используются жесткие диски с более низкой скоростью, поэтому конкуренция за диск оказывает большее влияние. Буферизация чтения с использованием шаблона декоратора проста и эффективна для решения подобных проблем.
Если ваши требования не требуют исключительно быстрого запуска, я согласен, что 2-я версия подходит. Вы реализуете ближе к фактическим требованиям. Если бы они требовали быстрых времен, я бы сказал, что большие заголовки и ленивая загрузка, вероятно, более уместны.