Кармм - это быстрое бинарное формат сериализации. Приоритет Karmem должен быть простым в использовании, в то же время был быстрым. Оптимизируется, чтобы взять максимальную производительность Голанга и Tinygo и эффективно для повторяемых чтений, чтение различного содержания одного и того же типа. Кармам демонстрирует в десять раз быстрее, чем Google Flatbuffers, причем дополнительные накладные расходы включены проверки границ.
️ Кармем все еще находится в стадии разработки, API не стабилен. Тем не менее, сама формат сериализации не похож на изменение и должен оставаться отсталым совместимым с более старыми версиями.
Karmem был создан для решения одной проблемы: облегчить передачу данных между хостом Webassembly и гостем. Несмотря на то, что он еще портативен для языков, не связанных с паузах. Мы экспериментируем с «схемой соревнований» между Wasm-Host и Wasm-Guest в одном проекте, но обмен данными очень дорого, а вызовы FFI также не дешевы. Кармем кодирует один раз и делится одним и тем же контентом с несколькими гостями, независимо от языка, что делает его очень эффективным. Кроме того, даже используя API-API для декодирования, он достаточно быстро, и Karmem был разработан, чтобы воспользоваться этим шаблоном, избежать распределения и повторного использования одной и той же структуры для нескольких данных.
Почему бы не использовать WITX? Это хороший проект, и он направлен на WASM, однако он кажется более сложным и определяет не только структуру данных, но и функции, которых я пытаюсь избежать. Кроме того, он не предназначен для переносного к не-волне. Почему бы не использовать плоские буферы? Мы пытались, но это не достаточно быстро, а также вызывает панику из-за отсутствия проверки связанных. Почему бы не использовать cap'n'proto? Это хорошая альтернатива, но ей не хватает реализации для Zig и Assemblyscript, которая является высшим приоритетом, она также имеет больше ассигнований, а генерируемый API сложнее использовать, чем Karmem.
Это небольшой пример того, как использовать Karmem.
karmem app @ packed ( true ) @ golang . package ( `app` );
enum SocialNetwork uint8 { Unknown ; Facebook ; Instagram ; Twitter ; TikTok ; }
struct ProfileData table {
Network SocialNetwork ;
Username [] char ;
ID uint64 ;
}
struct Profile inline {
Data ProfileData ;
}
struct AccountData table {
ID uint64 ;
Email [] char ;
Profiles [] Profile ;
} Сгенерировать код с помощью go run karmem.org/cmd/karmem build --golang -o "km" app.km
Чтобы кодировать, использование должно создать собственную структуру, а затем кодировать ее.
var writerPool = sync. Pool { New : func () any { return karmem . NewWriter ( 1024 ) }}
func main () {
writer := writerPool . Get ().( * karmem. Writer )
content := app. AccountData {
ID : 42 ,
Email : "[email protected]" ,
Profiles : []app. Profile {
{ Data : app. ProfileData {
Network : app . SocialNetworkFacebook ,
Username : "inkeliz" ,
ID : 123 ,
}},
{ Data : app. ProfileData {
Network : app . SocialNetworkFacebook ,
Username : "karmem" ,
ID : 231 ,
}},
{ Data : app. ProfileData {
Network : app . SocialNetworkInstagram ,
Username : "inkeliz" ,
ID : 312 ,
}},
},
}
if _ , err := content . WriteAsRoot ( writer ); err != nil {
panic ( err )
}
encoded := writer . Bytes ()
_ = encoded // Do something with encoded data
writer . Reset ()
writerPool . Put ( writer )
}Вместо того, чтобы декодировать его в другую структуру, вы можете прочитать некоторые поля напрямую, без какого -либо дополнительного декодирования. В этом примере нам нужно только имя пользователя каждого профиля.
func decodes ( encoded [] byte ) {
reader := karmem . NewReader ( encoded )
account := app . NewAccountDataViewer ( reader , 0 )
profiles := account . Profiles ( reader )
for i := range profiles {
fmt . Println ( profiles [ i ]. Data ( reader ). Username ( reader ))
}
} Примечание: мы используем NewAccountDataViewer , любой Viewer - просто зритель и не копирует данные бэкэнд. Некоторые языки (C#, Assemblyscript) используют UTF-16, в то время как Karmem использует UTF-8, в этих случаях у вас есть некоторый штраф за производительность.
Вы также можете расшифровать его на существующую структуру. В некоторых случаях лучше, если вы повторно используете ту же структуру для множества чтения.
var accountPool = sync. Pool { New : func () any { return new (app. AccountData ) }}
func decodes ( encoded [] byte ) {
account := accountPool . Get ().( * app. AccountData )
account . ReadAsRoot ( karmem . NewReader ( encoded ))
profiles := account . Profiles
for i := range profiles {
fmt . Println ( profiles [ i ]. Data . Username )
}
accountPool . Put ( account )
}Используя аналогичную схему с плоскими буферами и карманом. Кармм почти в 10 раз быстрее, чем Google Flatbuffers.
Натив (macOS/ARM64 - M1):
name old time/op new time/op delta
EncodeObjectAPI-8 2.54ms ± 0% 0.51ms ± 0% -79.85% (p=0.008 n=5+5)
DecodeObjectAPI-8 3.57ms ± 0% 0.20ms ± 0% -94.30% (p=0.008 n=5+5)
DecodeSumVec3-8 1.44ms ± 0% 0.16ms ± 0% -88.86% (p=0.008 n=5+5)
name old alloc/op new alloc/op delta
EncodeObjectAPI-8 12.1kB ± 0% 0.0kB -100.00% (p=0.008 n=5+5)
DecodeObjectAPI-8 2.87MB ± 0% 0.00MB -100.00% (p=0.008 n=5+5)
DecodeSumVec3-8 0.00B 0.00B ~ (all equal)
name old allocs/op new allocs/op delta
EncodeObjectAPI-8 1.00k ± 0% 0.00k -100.00% (p=0.008 n=5+5)
DecodeObjectAPI-8 110k ± 0% 0k -100.00% (p=0.008 n=5+5)
DecodeSumVec3-8 0.00 0.00 ~ (all equal)
Webassembly на Wazero (macOS/ARM64 - M1):
name old time/op new time/op delta
EncodeObjectAPI-8 17.2ms ± 0% 4.0ms ± 0% -76.51% (p=0.008 n=5+5)
DecodeObjectAPI-8 50.7ms ± 2% 1.9ms ± 0% -96.18% (p=0.008 n=5+5)
DecodeSumVec3-8 5.74ms ± 0% 0.75ms ± 0% -86.87% (p=0.008 n=5+5)
name old alloc/op new alloc/op delta
EncodeObjectAPI-8 3.28kB ± 0% 3.02kB ± 0% -7.80% (p=0.008 n=5+5)
DecodeObjectAPI-8 3.47MB ± 2% 0.02MB ± 0% -99.56% (p=0.008 n=5+5)
DecodeSumVec3-8 1.25kB ± 0% 1.25kB ± 0% ~ (all equal)
name old allocs/op new allocs/op delta
EncodeObjectAPI-8 4.00 ± 0% 4.00 ± 0% ~ (all equal)
DecodeObjectAPI-8 5.00 ± 0% 4.00 ± 0% -20.00% (p=0.008 n=5+5)
DecodeSumVec3-8 5.00 ± 0% 5.00 ± 0% ~ (all equal)
Производительность почти одинакова при сравнении чтения не сепаратизированных данных из нативной структуры и чтения их из данных, сериализованных в кармане.
Натив (macOS/ARM64 - M1):
name old time/op new time/op delta
DecodeSumVec3-8 154µs ± 0% 160µs ± 0% +4.36% (p=0.008 n=5+5)
name old alloc/op new alloc/op delta
DecodeSumVec3-8 0.00B 0.00B ~ (all equal)
name old allocs/op new allocs/op delta
DecodeSumVec3-8 0.00 0.00 ~ (all equal)
Это сравнение со всеми поддерживаемыми языками.
Webassembly на Wazero (macOS/ARM64 - M1):
name time/op result/wasi-go-km.out result/wasi-as-km.out result/wasi-zig-km.out result/wasi-swift-km.out result/wasi-c-km.out result/wasi-odin-km.out result/wasi-dotnet-km.out
DecodeSumVec3-8 757µs ± 0% 1651µs ± 0% 369µs ± 0% 9145µs ± 6% 368µs ± 0% 1330µs ± 0% 75671µs ± 0%
DecodeObjectAPI-8 1.59ms ± 0% 6.13ms ± 0% 1.04ms ± 0% 30.59ms ±34% 0.90ms ± 1% 4.06ms ± 0% 231.72ms ± 0%
EncodeObjectAPI-8 3.96ms ± 0% 4.51ms ± 1% 1.20ms ± 0% 8.26ms ± 0% 1.03ms ± 0% 5.19ms ± 0% 237.99ms ± 0%
name alloc/op result/wasi-go-km.out result/wasi-as-km.out result/wasi-zig-km.out result/wasi-swift-km.out result/wasi-c-km.out result/wasi-odin-km.out result/wasi-dotnet-km.out
DecodeSumVec3-8 1.25kB ± 0% 21.75kB ± 0% 1.25kB ± 0% 1.82kB ± 0% 1.25kB ± 0% 5.34kB ± 0% 321.65kB ± 0%
DecodeObjectAPI-8 15.0kB ± 0% 122.3kB ± 1% 280.8kB ± 1% 108.6kB ± 3% 1.2kB ± 0% 23.8kB ± 0% 386.5kB ± 0%
EncodeObjectAPI-8 3.02kB ± 0% 58.00kB ± 1% 1.23kB ± 0% 1.82kB ± 0% 1.23kB ± 0% 8.91kB ± 0% 375.82kB ± 0%
name allocs/op result/wasi-go-km.out result/wasi-as-km.out result/wasi-zig-km.out result/wasi-swift-km.out result/wasi-c-km.out result/wasi-odin-km.out result/wasi-dotnet-km.out
DecodeSumVec3-8 5.00 ± 0% 5.00 ± 0% 5.00 ± 0% 32.00 ± 0% 5.00 ± 0% 6.00 ± 0% 11.00 ± 0%
DecodeObjectAPI-8 5.00 ± 0% 4.00 ± 0% 4.00 ± 0% 32.00 ± 0% 4.00 ± 0% 6.00 ± 0% 340.00 ± 0%
EncodeObjectAPI-8 4.00 ± 0% 3.00 ± 0% 3.00 ± 0% 30.00 ± 0% 3.00 ± 0% 5.00 ± 0% 40.00 ± 0%
В настоящее время мы сосредоточимся на Webassembly, и из -за этого это поддерживаемые языки:
Некоторые языки все еще находятся в стадии разработки и не имеют никаких обещаний обратной совместимости. Мы постараемся не отставать от последней версии. В настоящее время сгенерированный API, и библиотеки не должны рассматривать стабильные.
| Функции | Go/tinygo | Zig | Assemblyscript | Быстрый | В | C#/. Net | Один |
|---|---|---|---|---|---|---|---|
| Производительность | Хороший | Отличный | Хороший | Бедный | Отличный | Ужасный | Хороший |
| Приоритет | Высокий | Высокий | Высокий | Низкий | Высокий | Середина | Низкий |
| Кодирование | |||||||
| Кодирование объекта | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| Сырое кодирование | |||||||
| Нулевая копия | |||||||
| Декодирование | |||||||
| Декодирование объекта | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| Объект повторный использование | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | |
| Случайный доступ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| Нулевая копия | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | |
| Нулевая строка | ✔ | ✔ | ✔ | ✔ | ✔ | ||
| Местный массив | ✔ | ✔ | ✔ | ️ | ✔ |
Karmem использует пользовательский язык схемы, который определяет структуры, перечисления и типы.
Схема очень проста для понимания и определения:
karmem game @ packed ( true ) @golang. package ( `km` ) @ assemblyscript . import ( `../../assemblyscript/karmem` );
enum Team uint8 { Humans ; Orcs ; Zombies ; Robots ; Aliens ;}
struct Vec3 inline {
X float32 ;
Y float32 ;
Z float32 ;
}
struct MonsterData table {
Pos Vec3 ;
Mana int16 ;
Health int16 ;
Name [] char ;
Team Team ;
Inventory [ < 128 ] byte ;
Hitbox [ 4 ] float64 ;
Status [] int32 ;
Path [ < 128 ] Vec3 ;
}
struct Monster inline {
Data MonsterData ;
}
struct State table {
Monsters [ < 2000 ] Monster ;
} Каждый файл должен начинаться с: karmem {name} [@tag()]; Полем Другие дополнительные теги могут быть определены, как показано выше, рекомендуется использовать опцию @packed(true) .
Примитивы :
uint8 , uint16 , uint32 , uint64int8 , int16 , int32, int64float32 , float64boolbyte , charНевозможно определить необязательные или нулевые типы.
Массивы :
[{Length}]{Type} (пример: [123]uint16 , [3]float32 )[]{Type} (пример: []char , []uint64 )[<{Length}]{Type} (пример: [<512]float64 , [<42]byte )Невозможно иметь кусочки таблиц или кусочек перечисления или ломтиков. Тем не менее, можно обернуть эти типы внутри одного встроенного структуры.
В настоящее время Karmem имеет два типа структуры: встроенные и таблицы.
Встроенные: встроенные структуры, как следует из названия, вставлены при использовании. Это уменьшает размер и может улучшить производительность. Тем не менее, это не может изменить их определение. В порядке слова: вы не можете редактировать поле одной встроенной структуры, не нарушая совместимость.
struct Vec3 inline {
X float32 ;
Y float32 ;
Z float32 ;
} Эта структура точно такая же от [3]float32 и будет иметь такой же результат сериализации. Из -за этого любое изменение этой структуры (например, изменение ее на float64 или добавление новых полей) сломает совместимость.
Таблицы: Таблицы можно использовать, когда имеет значение обратной совместимости. Например, таблицы могут иметь новые поля, добавляющие внизу, не нарушая совместимость.
struct User table {
Name [] char ;
Email [] char ;
Password [] char ;
}Давайте рассмотрим, что вам нужно еще одно поле ... для таблиц, это не проблема:
struct User table {
Name [] char ;
Email [] char ;
Password [] char ;
Telephone [] char ;
}Поскольку это таблица, вы можете добавить новые поля в нижней части структуры, и обе версии совместимы между ними. Если сообщение отправлено клиенту, который не понимает новое поле, оно будет проигнорировано. Если один устаревший клиент отправляет сообщение новому клиенту, новое поле будет иметь значение по умолчанию (0, false, пустая строка и т. Д.).
Enums можно использовать в качестве псевдонима для целых чисел, например, uint8 .
enum Team uint8 {
Unknown ;
Humans ;
Orcs ;
Zombies = 255 ;
}Перечисление должно начинаться с нулевого значения, значения по умолчанию во всех случаях. Если значение любого перечисления опущено, оно будет использовать порядок перечисления в качестве значения.
После определения схемы вы можете генерировать код. Во -первых, вам нужно установить karmem , получить его со страницы выпусков или запустить с помощью Go.
karmem build --assemblyscript -o "output-folder" your-schema.km
Если у вас уже установлен Golang, вы можете использовать go karmem.org/cmd/karmem build --zig -o "output-folder" your-schema.km .
Команды:
build
--zig : включить поколение для Zig--swift : включить генерацию для Swift/Swiftwasm--odin : Включить генерацию для ODIN--golang : включить генерацию для Golang/tinygo--dotnet : включить генерацию для .net--c : включить генерацию для c--assemblyscript : включить генерацию для Assemblyscript-o <dir> : определяет выходную папку<input-file> : определяет схему вводаКармм быстрый и также направлен на то, чтобы быть безопасным и стабильным для общего использования.
Вне границ
Karmem включает в себя проверку границ, чтобы предотвратить непрерывное чтение и избежать сбоев и паники. Это то, чего нет в Google Flatbuffers, и сохраненный контент вызовет панику. Тем не менее, это не исправляет все возможные уязвимости.
Истощение ресурсов
Karmem позволяет одному указателю/смещению можно повторно использовать несколько раз в одном и том же сообщении. К сожалению, это поведение позволяет короткому сообщению генерировать более обширные массивы, чем размер сообщения. В настоящее время единственное смягчение этой проблемы-это использование ограниченных массивов вместо массивов и избегание декодирования объекта-API.
Утечка данных
Кармм не очищает память перед кодированием, которая может утечь информацию из предыдущего сообщения или из самой системной памяти. Это может быть решено с использованием тега @packed(true) , как описано ранее. packed тег удалит заполнение из сообщения, что предотвратит утечку. В качестве альтернативы вы можете очистить память перед кодировкой вручную.
Карма имеет некоторые ограничения по сравнению с другими библиотеками сериализации, такими как:
Максимальный размер
Подобно Google Protobuf и Google Flatbuffers, Karmem имеет максимальный размер 2 ГБ. Это максимальный размер всего сообщения, а не максимальный размер каждого массива. Это ограничение связано с тем, что WASM предназначен для 32-битного, а максимальный размер 2 ГБ кажется достаточным для текущих потребностей. Нынешний писатель не применяет это ограничение, но чтение сообщения, которое больше, чем 2 ГБ, вызовет неопределенное поведение.
Массивы массивов/таблиц
Karmem не поддерживает массивы массивов или массивов таблиц. Тем не менее, можно обернуть эти типы внутри одного встроенного структуры, как упомянуто выше. Это ограничение было наложено, чтобы воспользоваться местными массивами/ломтиками из языка. Большинство языков инкапсулируют указатель и размер массива внутри структуры, подобного структуру, который требует, чтобы размер каждого элемента был известен, следовательно, предотвращая массивы элементов с переменным размером/шагами.
UTF-8
Кармем поддерживает только UTF-8 и не поддерживает другие кодировки.