Ошибка при использовании ускоренной сериализации с двоичным архивом
Я получаю следующую ошибку при чтении из boost::archive::binary_iarchive
в мою переменную:
test-serialization(9285,0x11c62fdc0) malloc: can't allocate region
*** mach_vm_map(size=18014398509486080) failed (error code=3)
test-serialization(9285,0x11c62fdc0) malloc: *** set a breakpoint in malloc_error_break to debug
Мой код сериализации и десериализации:
template<class Archive>
void save(Archive & archive, const helib::PubKey & pubkey, const unsigned int version){
BOOST_TEST_MESSAGE("inside save_construct_data");
archive << &(pubkey.context);
archive << pubkey.skBounds;
archive << pubkey.keySwitching;
archive << pubkey.keySwitchMap;
archive << pubkey.KS_strategy;
archive << pubkey.recryptKeyID;
}
template<class Archive>
void load_construct_data(Archive & archive, helib::PubKey * pubkey, const unsigned int version){
helib::Context * context = new helib::Context(2,3,1); //random numbers since there is no default constructor
BOOST_TEST_MESSAGE("deserializing context");
archive >> context;
std::vector<double> skBounds;
std::vector<helib::KeySwitch> keySwitching;
std::vector<std::vector<long>> keySwitchMap;
NTL::Vec<long> KS_strategy;
long recryptKeyID;
BOOST_TEST_MESSAGE("deserializing skbounds");
archive >> skBounds;
BOOST_TEST_MESSAGE("deserializing keyswitching");
archive >> keySwitching;
BOOST_TEST_MESSAGE("deserializing keyswitchmap");
archive >> keySwitchMap;
BOOST_TEST_MESSAGE("deserializing KS_strategy");
archive >> KS_strategy;
BOOST_TEST_MESSAGE("deserializing recryptKeyID");
archive >> recryptKeyID;
BOOST_TEST_MESSAGE("new pubkey");
::new(pubkey)helib::PubKey(*context);
//TODO: complete
}
template<class Archive>
void serialize(Archive & archive, helib::PubKey & pubkey, const unsigned int version){
split_free(archive, pubkey, version);
}
template<class Archive>
void load(Archive & archive, helib::PubKey & pubkey, const unsigned int version){
}
Тест, вызывающий код, выглядит следующим образом:
BOOST_AUTO_TEST_CASE(serialization_pubkey)
{
auto context = helibTestContext();
helib::SecKey secret_key(context);
secret_key.GenSecKey();
// Compute key-switching matrices that we need
helib::addSome1DMatrices(secret_key);
// Set the secret key (upcast: SecKey is a subclass of PubKey)
const helib::PubKey& original_pubkey = secret_key;
std::string filename = "pubkey.serialized";
std::ofstream os(filename, std::ios::binary);
{
boost::archive::binary_oarchive oarchive(os);
oarchive << original_pubkey;
}
helib::PubKey * restored_pubkey = new helib::PubKey(helib::Context(2,3,1));
{
std::ifstream ifs(filename, std::ios::binary);
boost::archive::binary_iarchive iarchive(ifs);
BOOST_TEST_CHECKPOINT("calling deserialization");
iarchive >> restored_pubkey;
BOOST_TEST_CHECKPOINT("done with deserialization");
//tests ommitted
}
}
Соображения:
Сериализация отлично работает с
boost::archive::text_oarchive
а такжеboost::archive::binary_oarchive
. Они создают файл размером 46M и 21M соответственно (большой, я знаю).Десериализация с помощью
boost::archive::text_iarchive
в основном остановился на исполненииarchive >> keySwitching;
Процесс автоматически завершается. Фактически это самая большая часть архива.Я решил попробовать с
boost::archive::binary_iarchive
так как файл вдвое меньше, но я получаю сообщение об ошибке, показанное в начале. Ошибка возникает при выполнении первого чтения из архива:archive >> context;
.Асимметрия между вводом и выводом (
save
а такжеload_construct_data
) потому, что я не смог найти другого способа избежать реализации сериализации производного классаhelib::PubKey
. Используя указатель наhelib::PubKey
давал мне ошибки компиляции, запрашивая сериализацию производного класса. Если есть другой способ, я весь уши.
Спасибо за помощь.
ОБНОВЛЕНИЕ:
Я реализую десериализацию для некоторых классов в криптографической библиотеке HElib, потому что мне нужно отправить зашифрованный текст по сети. Один из этих классовhelib::PubKey
. Для реализации я использую библиотеку ускоренной сериализации. Я создал суть, чтобы предоставить представление, как это предлагается в комментариях. Есть 3 файла:
- serialization.hpp, он содержит реализацию сериализации. К сожалению,
helib::PubKey
зависит от многих других классов, делающих файл довольно длинным. Все остальные классы проходят модульные тесты. Кроме того, мне пришлось внести в класс небольшую модификацию с целью его сериализации. Я обнародовал частные члены. - test-serialization.cpp, он содержит модульный тест.
- Makefile. Запуск make создает исполняемый файл тестовой сериализации.
1 ответ
vector<bool>
забастовки снова
На самом деле в моем тестовом боксе выделяется 0x1fffffffff20000 бит (это 144 петабита). Это происходит непосредственно из IndexSet::resize().
Теперь у меня есть серьезные вопросы по поводу использования HElib. std::vector<bool>
здесь (кажется, им было бы гораздо лучше подавать что-то вродеboost::icl::interval_set<>
).
Что ж. Это была настоящая погоня за гусиной (сериализацию IndexSet можно значительно улучшить). Однако настоящая проблема в том, что у вас было Undefined Behavior, потому что вы не десериализуете тот же тип, что и сериализуете.
Вы сериализуете PubKey
, но попытаться десериализовать как PubKey*
. Ухох.
Помимо этого, есть немало проблем:
Вам пришлось изменить библиотеку, чтобы сделать частных участников общедоступными. Это может легко нарушить ODR (сделать структуру классов несовместимой).
Кажется, вы относитесь к контексту как к "динамическому" ресурсу, который задействует отслеживание объектов. Это может быть жизнеспособным подходом. НО. Вам придется подумать о собственности.
Похоже, ты этого еще не делал. Например, строка в
load_construct_data
заDoublCRT
определенная утечка памяти:helib::Context * context = new helib::Context(2,3,1);
Вы никогда его не используете и никогда не освобождаете. Фактически, вы просто перезаписываете его десериализованным экземпляром, который может принадлежать или не принадлежать. Словить 22
Точно то же самое происходит в
load_construct_data
заPubKey
.хуже, в
save_construct_data
вы совершенно бесплатно копируете объекты контекста для каждогоDoubleCRT
в каждомSecKey
:auto context = polynomial->getContext(); archive << &context;
Поскольку вы подделываете это как сериализацию указателя, снова (очевидно, бесполезное) отслеживание объектов запускается, просто означает, что вы сериализуете избыточный
Context
копии, которые будут все быть просочились ип десериализации.Я бы хотел предположить, что экземпляры контекста в обоих всегда будут одинаковыми? Почему бы в любом случае не сериализовать контекст (ы) отдельно?
Фактически, я пошел и проанализировал исходный код HElib, чтобы проверить эти предположения. Оказывается, я был прав. Ничто никогда не конструирует контекст вне
std::unique_ptr<Context> buildContextFromBinary(std::istream& str); std::unique_ptr<Context> buildContextFromAscii(std::istream& str);
Как видите, они возвращают собственные указатели. Вы должны были их использовать. Возможно, даже со встроенной сериализацией, о которой я практически споткнулся.
Время перегруппироваться
Я бы использовал код сериализации из HElib (потому что, зачем изобретать колесо и делать при этом массу ошибок?). Если вы настаиваете на интеграции с Boost Serialization, можете съесть свой пирог:
template <class Archive> void save(Archive& archive, const helib::PubKey& pubkey, unsigned) {
using V = std::vector<char>;
using D = iostreams::back_insert_device<V>;
V data;
{
D dev(data);
iostreams::stream_buffer<D> sbuf(dev);
std::ostream os(&sbuf); // expose as std::ostream
helib::writePubKeyBinary(os, pubkey);
}
archive << data;
}
template <class Archive> void load(Archive& archive, helib::PubKey& pubkey, unsigned) {
std::vector<char> data;
archive >> data;
using S = iostreams::array_source;
S source(data.data(), data.size());
iostreams::stream_buffer<S> sbuf(source);
{
std::istream is(&sbuf); // expose as std::istream
helib::readPubKeyBinary(is, pubkey);
}
}
Вот и все. 24 строки кода. И его будут тестировать и поддерживать авторы библиотеки. Вы не можете победить это (ясно). Я немного изменил тесты, чтобы мы больше не злоупотребляли личными данными.
Очистка кода
Выделив помощника для написания большого двоичного объекта, мы можем реализовать различные helib
типы очень похожи:
namespace helib { // leverage ADL
template <class A> void save(A& ar, const Context& o, unsigned) {
Blob data = to_blob(o, writeContextBinary);
ar << data;
}
template <class A> void load(A& ar, Context& o, unsigned) {
Blob data;
ar >> data;
from_blob(data, o, readContextBinary);
}
template <class A> void save(A& ar, const PubKey& o, unsigned) {
Blob data = to_blob(o, writePubKeyBinary);
ar << data;
}
template <class A> void load(A& ar, PubKey& o, unsigned) {
Blob data;
ar >> data;
from_blob(data, o, readPubKeyBinary);
}
}
Для меня это элегантность.
ПОЛНЫЙ ПЕРЕЧЕНЬ
Я клонировал новую суть https://gist.github.com/sehe/ba82a0329e4ec586363eb82d3f3b9326, которая включает следующие наборы изменений:
0079c07 Make it compile locally
b3b2cf1 Squelch the warnings
011b589 Endof investigations, regroup time
f4d79a6 Reimplemented using HElib binary IO
a403e97 Bitwise reproducible outputs
Только последние два коммита содержат изменения, относящиеся к фактическим исправлениям.
Я также перечислю здесь полный код для потомков. В тестовом коде есть ряд тонких реорганизаций и аналогичных комментариев. Вам следует внимательно их прочитать, чтобы увидеть, понимаете ли вы их и соответствуете ли последствия вашим потребностям. Я оставил комментарии, описывающие, почему тестовые утверждения помогают.
файл
serialization.hpp
#ifndef EVOTING_SERIALIZATION_H #define EVOTING_SERIALIZATION_H #define BOOST_TEST_MODULE main #include <helib/helib.h> #include <boost/serialization/split_free.hpp> #include <boost/serialization/vector.hpp> #include <boost/iostreams/stream_buffer.hpp> #include <boost/iostreams/device/back_inserter.hpp> #include <boost/iostreams/device/array.hpp> namespace /* file-static */ { using Blob = std::vector<char>; template <typename T, typename F> Blob to_blob(const T& object, F writer) { using D = boost::iostreams::back_insert_device<Blob>; Blob data; { D dev(data); boost::iostreams::stream_buffer<D> sbuf(dev); std::ostream os(&sbuf); // expose as std::ostream writer(os, object); } return data; } template <typename T, typename F> void from_blob(Blob const& data, T& object, F reader) { boost::iostreams::stream_buffer<boost::iostreams::array_source> sbuf(data.data(), data.size()); std::istream is(&sbuf); // expose as std::istream reader(is, object); } } namespace helib { // leverage ADL template <class A> void save(A& ar, const Context& o, unsigned) { Blob data = to_blob(o, writeContextBinary); ar << data; } template <class A> void load(A& ar, Context& o, unsigned) { Blob data; ar >> data; from_blob(data, o, readContextBinary); } template <class A> void save(A& ar, const PubKey& o, unsigned) { Blob data = to_blob(o, writePubKeyBinary); ar << data; } template <class A> void load(A& ar, PubKey& o, unsigned) { Blob data; ar >> data; from_blob(data, o, readPubKeyBinary); } } BOOST_SERIALIZATION_SPLIT_FREE(helib::Context) BOOST_SERIALIZATION_SPLIT_FREE(helib::PubKey) #endif //EVOTING_SERIALIZATION_H
файл
test-serialization.cpp
#define BOOST_TEST_MODULE main #include <boost/test/included/unit_test.hpp> #include <helib/helib.h> #include <fstream> #include "serialization.hpp" #include <boost/archive/text_oarchive.hpp> #include <boost/archive/text_iarchive.hpp> #include <boost/archive/binary_oarchive.hpp> #include <boost/archive/binary_iarchive.hpp> helib::Context helibTestMinimalContext(){ // Plaintext prime modulus unsigned long p = 4999; // Cyclotomic polynomial - defines phi(m) unsigned long m = 32109; // Hensel lifting (default = 1) unsigned long r = 1; return helib::Context(m, p, r); } helib::Context helibTestContext(){ auto context = helibTestMinimalContext(); // Number of bits of the modulus chain unsigned long bits = 300; // Number of columns of Key-Switching matix (default = 2 or 3) unsigned long c = 2; // Modify the context, adding primes to the modulus chain buildModChain(context, bits, c); return context; } BOOST_AUTO_TEST_CASE(serialization_pubkey) { auto context = helibTestContext(); helib::SecKey secret_key(context); secret_key.GenSecKey(); // Compute key-switching matrices that we need helib::addSome1DMatrices(secret_key); // Set the secret key (upcast: SecKey is a subclass of PubKey) const helib::PubKey& original_pubkey = secret_key; std::string const filename = "pubkey.serialized"; { std::ofstream os(filename, std::ios::binary); boost::archive::binary_oarchive oarchive(os); oarchive << context << original_pubkey; } { // just checking reproducible output std::ofstream os(filename + ".2", std::ios::binary); boost::archive::binary_oarchive oarchive(os); oarchive << context << original_pubkey; } // reading back to independent instances of Context/PubKey { // NOTE: if you start from something rogue, it will fail with PAlgebra mismatch. helib::Context surrogate = helibTestMinimalContext(); std::ifstream ifs(filename, std::ios::binary); boost::archive::binary_iarchive iarchive(ifs); iarchive >> surrogate; // we CAN test that the contexts end up matching BOOST_TEST((context == surrogate)); helib::SecKey independent(surrogate); helib::PubKey& indep_pk = independent; iarchive >> indep_pk; // private again, as it should be, but to understand the relation: // BOOST_TEST((&independent.context == &surrogate)); // The library's operator== compares the reference, so it would say "not equal" BOOST_TEST((indep_pk != original_pubkey)); { // just checking reproducible output std::ofstream os(filename + ".3", std::ios::binary); boost::archive::binary_oarchive oarchive(os); oarchive << surrogate << indep_pk; } } // doing it the other way (sharing the context): { helib::PubKey restored_pubkey(context); { std::ifstream ifs(filename, std::ios::binary); boost::archive::binary_iarchive iarchive(ifs); iarchive >> context >> restored_pubkey; } // now `operator==` confirms equality BOOST_TEST((restored_pubkey == original_pubkey)); { // just checking reproducible output std::ofstream os(filename + ".4", std::ios::binary); boost::archive::binary_oarchive oarchive(os); oarchive << context << restored_pubkey; } } }
ТЕСТОВЫЙ ВЫХОД
time ./test-serialization -l all -r detailed
Running 1 test case...
Entering test module "main"
test-serialization.cpp(34): Entering test case "serialization_pubkey"
test-serialization.cpp(61): info: check (context == surrogate) has passed
test-serialization.cpp(70): info: check (indep_pk != original_pubkey) has passed
test-serialization.cpp(82): info: check (restored_pubkey == original_pubkey) has passed
test-serialization.cpp(34): Leaving test case "serialization_pubkey"; testing time: 36385217us
Leaving test module "main"; testing time: 36385273us
Test module "main" has passed with:
1 test case out of 1 passed
3 assertions out of 3 passed
Test case "serialization_pubkey" has passed with:
3 assertions out of 3 passed
real 0m36,698s
user 0m35,558s
sys 0m0,850s
Побитовые воспроизводимые выходы
При повторной сериализации оказывается, что результат действительно побитовый идентичен, что может быть важным свойством:
sha256sum pubkey.serialized*
66b95adbd996b100bff58774e066e7a309e70dff7cbbe08b5c77b9fa0f63c97f pubkey.serialized
66b95adbd996b100bff58774e066e7a309e70dff7cbbe08b5c77b9fa0f63c97f pubkey.serialized.2
66b95adbd996b100bff58774e066e7a309e70dff7cbbe08b5c77b9fa0f63c97f pubkey.serialized.3
66b95adbd996b100bff58774e066e7a309e70dff7cbbe08b5c77b9fa0f63c97f pubkey.serialized.4
Обратите внимание, что он (очевидно) не идентичен для разных прогонов (потому что генерирует разный ключевой материал).
Побочный квест (Погоня за диким гусем)
Один из способов улучшить код сериализации IndexSet вручную - это также использовать vector<bool>
:
template<class Archive>
void save(Archive & archive, const helib::IndexSet & index_set, const unsigned int version){
std::vector<bool> elements;
elements.resize(index_set.last()-index_set.first()+1);
for (auto n : index_set)
elements[n-index_set.first()] = true;
archive << index_set.first() << elements;
}
template<class Archive>
void load(Archive & archive, helib::IndexSet & index_set, const unsigned int version){
long first_ = 0;
std::vector<bool> elements;
archive >> first_ >> elements;
index_set.clear();
for (size_t n = 0; n < elements.size(); ++n) {
if (elements[n])
index_set.insert(n+first_);
}
}
Лучше было бы использовать dynamic_bitset
(для которого я предоставил код сериализации (см. Как сериализовать boost:: dynamic_bitset?)):
template<class Archive>
void save(Archive & archive, const helib::IndexSet & index_set, const unsigned int version){
boost::dynamic_bitset<> elements;
elements.resize(index_set.last()-index_set.first()+1);
for (auto n : index_set)
elements.set(n-index_set.first());
archive << index_set.first() << elements;
}
template<class Archive>
void load(Archive & archive, helib::IndexSet & index_set, const unsigned int version) {
long first_ = 0;
boost::dynamic_bitset<> elements;
archive >> first_ >> elements;
index_set.clear();
for (size_t n = elements.find_first(); n != -1; n = elements.find_next(n))
index_set.insert(n+first_);
}
Конечно, вам, вероятно, придется делать то же самое для
IndexMap
.