Теперь жить стало ещё страшнее. Думаю как бы вкорячить возможность просить из васм-кода делать запросы, само апи у меня сейчас JSON'овое, хочу приделать ещё flatbuffers, во-первых судя по flamegraph сэкономлю процентов эдак 10 на парсинге json'ов, во-вторых ещё сколько-то на base64, в-третьих оно покомпактнее будет, в-четвёртых схема всех методов более наглядно будет описана, в общем, выглядит как хорошая идея.
А затем по коже пошёл холодок, когда осознал, что до этого мне просто очень сильно везло, когда я использовал метод, который создавал Vec и копировал туда байты результата сериализации.
Смотрите, flatbuffers работает так: даём ему (либо он сам создаёт) пустой Vec, используем кодогенерированные структурки чтобы записать какие-то данные, передаём в сериализатор WIP-объект (выдаются после записей структур) финального узла и финализируем сериализацию. После этого у нас есть три вещи: Vec держащий байты, head: usize говорящий с какого байта идут сериализированные данные, len: usize говорящий сколько там байт этих данных.
Иногда мне приходится копировать эти данные (например, из памяти васма, к которой относительно сложно обращаться безопасно, но думаю позже наворочу ансейфа и местами перестану копировать, а всё-таки возьму указатель и напишу себе каких-нибудь обёрток, которые будут гарантировать, что не натворю делов; но пока время этих оптимизаций не пришло, по тому же flamegraph видны другие проблемы, да и я пока не распараллелился даже). Ну так вот, я и делал Vec::from(&buffer[head..(head+len)]), который брал эти сериализованные данные и помещал в новый Vec, красота.
Но у нас опять контры с выравниваниями получаются. При валидации/чтениях этих сериализованных данных flatbuffers может запаниковать, сказав что я дурачок и дал ему невыровненные байты. Т.е. был там какой-нибудь u64, требующий выравнивания в 8 байт, был head = 5 чтобы гарантировать, что адрес где находится этот head правильно выровнен, чтобы все остальные адреса были хорошими, а я взял и всё сломал, сдвинув на пару байт в сторону.
И принимая эти сериализированные данные в виде байт по сети, если я запишу их в обычный Vec — нет гарантий, что всё сойдётся. u8 требует выравнивания в 1, переопредилить его нельзя. Чисто в теории можно создать Vec со структурой в восемь байт, но работать с этим потом будет ну совсем неудобно.
Хотел по коду std найти, почему оно не сломалось у меня, но за пару минут не вышло, бросил. Вероятнее всего потому что текущий аллокатор да ещё и на 64-битной системе старается всё с 8-байтовым выравниваем создавать, не знаю.
Что делать? Не знаю, как это другие делают, но я пожалуй добавлю себе в утилитки структуру аля AlignedBytesData(Vec) (где ALIGN < 255, но на практике пока только 8 нужно, ибо во flatbuffers нет 128-битных чисел), которая будет делать следующее:
◾️ Если нужно скопировать сериализированные данные, то аллоцирует вектор с размером данных + ALIGN байт. Узнаёт адрес первого байта, записывает в первый байт ALIGN минус остаток от деления на ALIGN, с этого смещения и записывает байты.
◾️ Если нужно прочитать — читает первый байт, узнаёт смещение, отдаёт слайс начиная с него и до ALIGN минус смещение с конца.
Завтра ещё в чатике спрошу по этому поводу и возможно в 64-битной системе это лишний оверхед, но я ведь живу и в 32-битном васме, который вполне может взять и аллоцировать вектор на полуслове, в который я затем запишу данные и всё развалится.
UPD: эта схема не прокатит из-за реаллокаций памяти вектора, после которого смещение может оказаться сломано.
#rust
А затем по коже пошёл холодок, когда осознал, что до этого мне просто очень сильно везло, когда я использовал метод, который создавал Vec и копировал туда байты результата сериализации.
Смотрите, flatbuffers работает так: даём ему (либо он сам создаёт) пустой Vec, используем кодогенерированные структурки чтобы записать какие-то данные, передаём в сериализатор WIP-объект (выдаются после записей структур) финального узла и финализируем сериализацию. После этого у нас есть три вещи: Vec держащий байты, head: usize говорящий с какого байта идут сериализированные данные, len: usize говорящий сколько там байт этих данных.
Иногда мне приходится копировать эти данные (например, из памяти васма, к которой относительно сложно обращаться безопасно, но думаю позже наворочу ансейфа и местами перестану копировать, а всё-таки возьму указатель и напишу себе каких-нибудь обёрток, которые будут гарантировать, что не натворю делов; но пока время этих оптимизаций не пришло, по тому же flamegraph видны другие проблемы, да и я пока не распараллелился даже). Ну так вот, я и делал Vec::from(&buffer[head..(head+len)]), который брал эти сериализованные данные и помещал в новый Vec, красота.
Но у нас опять контры с выравниваниями получаются. При валидации/чтениях этих сериализованных данных flatbuffers может запаниковать, сказав что я дурачок и дал ему невыровненные байты. Т.е. был там какой-нибудь u64, требующий выравнивания в 8 байт, был head = 5 чтобы гарантировать, что адрес где находится этот head правильно выровнен, чтобы все остальные адреса были хорошими, а я взял и всё сломал, сдвинув на пару байт в сторону.
И принимая эти сериализированные данные в виде байт по сети, если я запишу их в обычный Vec — нет гарантий, что всё сойдётся. u8 требует выравнивания в 1, переопредилить его нельзя. Чисто в теории можно создать Vec со структурой в восемь байт, но работать с этим потом будет ну совсем неудобно.
Хотел по коду std найти, почему оно не сломалось у меня, но за пару минут не вышло, бросил. Вероятнее всего потому что текущий аллокатор да ещё и на 64-битной системе старается всё с 8-байтовым выравниваем создавать, не знаю.
Что делать? Не знаю, как это другие делают, но я пожалуй добавлю себе в утилитки структуру аля AlignedBytesData(Vec) (где ALIGN < 255, но на практике пока только 8 нужно, ибо во flatbuffers нет 128-битных чисел), которая будет делать следующее:
◾️ Если нужно скопировать сериализированные данные, то аллоцирует вектор с размером данных + ALIGN байт. Узнаёт адрес первого байта, записывает в первый байт ALIGN минус остаток от деления на ALIGN, с этого смещения и записывает байты.
◾️ Если нужно прочитать — читает первый байт, узнаёт смещение, отдаёт слайс начиная с него и до ALIGN минус смещение с конца.
Завтра ещё в чатике спрошу по этому поводу и возможно в 64-битной системе это лишний оверхед, но я ведь живу и в 32-битном васме, который вполне может взять и аллоцировать вектор на полуслове, в который я затем запишу данные и всё развалится.
UPD: эта схема не прокатит из-за реаллокаций памяти вектора, после которого смещение может оказаться сломано.
#rust