Самая быстрая сериализация С++?
Доброе утро,
Я ищу очень быструю двоичную сериализацию для С++. Мне нужно только сериализовать данные, содержащиеся в объектах (без указателей и т.д.). Я бы хотел, чтобы это было так же быстро, как
возможное. Если это относится к оборудованию x86, которое является приемлемым.
Я знаком с методами C для этого. В качестве теста я проверил несколько приемов. Я обнаружил, что метод C на 40% быстрее, чем лучший метод С++, который я реализовал.
Любые предложения о том, как улучшить метод С++ (или библиотеки, которые это делают)?
Что-нибудь хорошее для файлов с отображением памяти?
Спасибо
// c style writes
{
#pragma pack(1)
struct item
{
uint64_t off;
uint32_t size;
} data;
#pragma pack
clock_t start = clock();
FILE* fd = fopen( "test.c.dat", "wb" );
for ( long i = 0; i < tests; i++ )
{
data.off = i;
data.size = i & 0xFFFF;
fwrite( (char*) &data, sizeof(data), 1, fd );
}
fclose( fd );
clock_t stop = clock();
double d = ((double)(stop-start))/ CLOCKS_PER_SEC;
printf( "%8.3f seconds\n", d );
}
Около 1,6 секунды для тестов = 10000000
// c++ style ofstream writes
// define a DTO class
class test
{
public:
test(){}
uint64_t off;
uint32_t size;
friend std::ostream& operator<<( std::ostream& stream, const test& v );
};
// write to the stream
std::ostream& operator<<( std::ostream &stream, const test& v )
{
stream.write( (char*)&v.off, sizeof(v.off) );
stream.write( (char*)&v.size, sizeof(v.size) );
return stream;
}
{
test data;
clock_t start = clock();
std::ofstream out;
out.open( "test.cpp.dat", std::ios::out | std::ios::trunc | std::ios::binary );
for ( long i = 0; i < tests; i++ )
{
data.off = i;
data.size = i & 0xFFFF;
out << data;
}
out.close();
clock_t stop = clock();
double d = ((double)(stop-start))/ CLOCKS_PER_SEC;
printf( "%8.3f seconds\n", d );
}
Около 2,6 секунд для тестов = 10000000
Ответы
Ответ 1
Есть только очень мало реальных случаев, когда это имеет значение вообще. Вы только сериализуете, чтобы ваши объекты были совместимы с каким-то внешним ресурсом. Диск, сеть и т.д. Код, который передает сериализованные данные на ресурсе, всегда на порядок медленнее, чем код, необходимый для сериализации объекта. Если вы делаете код сериализации в два раза быстрее, вы делаете общую операцию не более чем на 0,5% быстрее, отдавайте или принимайте. Это не стоит ни риска, ни усилий.
Измерьте три раза, вырезайте один раз.
Ответ 2
Если выполняемая задача действительно сериализована, вы можете проверить Google Буферы протокола. Они обеспечивают быструю сериализацию классов С++. На сайте также упоминаются некоторые альтернативные библиотеки, например. boost.serialization(только для указания, что буферы протокола превосходят их в большинстве случаев, конечно;-)
Ответ 3
С++ Middleware Writer - это онлайн-альтернатива библиотекам сериализации.
В некоторых случаях это быстрее, чем библиотека сериализации в Boost.
Ответ 4
Ну, если вам нужна самая быстрая сериализация, тогда вы можете просто написать свой собственный класс сериализации и дать ему методы для сериализации каждого из типов POD.
Чем меньше безопасность вы введете, тем быстрее она будет работать, и чем сложнее будет отладка, тем не менее существует только фиксированное количество встроенных элементов, поэтому вы можете перечислить их.
class Buffer
{
public:
inline Buffer& operator<<(int i); // etc...
private:
std::deque<unsigned char> mData;
};
Я должен признать, что я не понимаю вашу проблему:
- Что вы на самом деле хотите сделать с сериализованным сообщением?
- Вы сохраните его позже?
- Вам нужно беспокоиться о совместимости вперёд/назад?
Там могут быть более эффективные подходы к сериализации.
Ответ 5
google flatbuffers, похожий на буфер протокола, но способ быстрее
https://google.github.io/flatbuffers/
https://google.github.io/flatbuffers/md__benchmarks.html
Ответ 6
Есть ли способ использовать то, что остается неизменным?
Я имею в виду, вы просто пытаетесь запустить "test.c.dat" так быстро, как можете, верно? Можете ли вы воспользоваться тем фактом, что файл не меняется между вашими попытками сериализации? Если вы пытаетесь сериализовать один и тот же файл, снова и снова, вы можете оптимизировать на основе этого. Я могу сделать первую попытку сериализации взять столько же времени, сколько и у вас, плюс крошечный бит для другой проверки, а затем, если вы попытаетесь снова запустить сериализацию на том же входе, я могу сделать мой второй запуск намного быстрее, чем в первый раз.
Я понимаю, что это может быть просто продуманный пример, но вы, похоже, сосредоточены на том, чтобы сделать язык как можно быстрее выполнив свою задачу, вместо того, чтобы задавать вопрос "нужно ли мне снова это сделать?" Каков контекст этого подхода?
Я надеюсь, что это будет полезно.
-Brian J. Stinar -
Ответ 7
Если вы находитесь в системе Unix, mmap
в файле - это способ сделать то, что вы хотите сделать.
См. http://msdn.microsoft.com/en-us/library/aa366556(VS.85).aspx для эквивалента в окнах.
Ответ 8
Большая часть производительности будет зависеть от буферов памяти и того, как вы заполняете блоки памяти перед записью на диск. И есть некоторые трюки для создания стандартных потоков С++ немного быстрее, например std::ios_base::sync_with_stdio (false);
Но ИМХО, миру не нужна другая реализация сериализации. Вот некоторые из них, которые другие люди считают, что вы, возможно, захотите изучить:
- Boost: Быстрая, сортированная библиотека С++, включая сериализацию
- protobuf: быстрая кросс-платформенная сериализация на разных языках с помощью модуля С++
- thrift: гибкая кросс-платформенная сериализация на разных языках с помощью модуля С++
Ответ 9
Так как ввод/вывод, скорее всего, будет узким местом, может помочь компактный формат. Из любопытства я попробовал следующую схему Колфера, составленную как colf -s 16 C
.
package data
type item struct {
off uint64
size uint32
}
... с сопоставимым тестом C:
clock_t start = clock();
data_item data;
void* buf = malloc(colfer_size_max);
FILE* fd = fopen( "test.colfer.dat", "wb" );
for ( long i = 0; i < tests; i++ )
{
data.off = i;
data.size = i & 0xFFFF;
size_t n = data_item_marshal( &data, buf );
fwrite( buf, n, 1, fd );
}
fclose( fd );
clock_t stop = clock();
Результаты довольно неутешительны на SSD, несмотря на то, что размер последовательного интерфейса на 40% меньше по сравнению с исходными дампами raw.
colfer took 0.520 seconds
plain took 0.320 seconds
Так как сгенерированный код довольно быстро, вряд ли вы выиграете что-нибудь с библиотеками сериализации.
Ответ 10
Как ваш C, так и ваш код на С++, вероятно, будут доминировать (по времени) с помощью ввода-вывода файлов. Я бы рекомендовал использовать файлы с отображением памяти при записи ваших данных и оставить буферизацию ввода-вывода в операционной системе. Boost.Interprocess может быть альтернативой.
Ответ 11
Чтобы действительно ответить на этот вопрос, причина медленной версии С++ заключается в том, что он слишком много раз вызывает ostream.write
, что вызывает огромное количество ненужных проверок состояния. Вы можете создать простой буфер и использовать только один write
, и вы увидите разницу.
Если ваш диск/сеть действительно достаточно быстро, чтобы не стать узким местом, flatbuffers
capnproto
- отличные варианты для этого.
В противном случае protobuf
, xxx-compact
... все, что использует varint-кодирование, может, вероятно, сериализовать эти данные на четверть оригинала размер.
HPS
из научного сообщества вычислений также является отличным вариантом для такого рода высокоструктурированных данных и, возможно, самым быстрым по скорости и наименьшим в размер сообщения в этом случае из-за его схемы кодирования.