Karmem ist ein schnelles binäres Serialisierungsformat. Die Priorität von Karmem besteht darin, leicht wie möglich zu bedienen. Es ist optimiert, dass die maximale Leistung von Golang und Tinygo für wiederholbare Lesevorgänge effizient ist und verschiedene Inhalte desselben Typs gelesen werden. Karmem zeigt, dass es zehnmal schneller ist als Google Flatbuffers, wobei der zusätzliche Aufwand der Grenzenprüfung enthalten ist.
Euen Karmem noch in der Entwicklung ist die API nicht stabil. Die Serialisation-Format selbst ist jedoch nicht zu verändern und sollte mit älteren Versionen rückwärtsvergleichbar bleiben.
Karmem wurde erstellt, um ein einziges Problem zu lösen: Machen Sie es einfach zu übertragen, Daten zwischen WebAssembly -Host und Gast zu übertragen. Während immer noch tragbar für Sprachen ohne Abschnitte. Wir experimentieren mit einem "Event-Command-Muster" zwischen WASM-Host und WASM-Guest in einem Projekt, aber die Teilen von Daten sind sehr teuer, und FFI-Anrufe sind auch nicht billig. Karmem codiert einmal und teilt den gleichen Inhalt mit mehreren Gästen, unabhängig von der Sprache, was es sehr effizient macht. Auch auch die Verwendung von Objekt-API zum Dekodieren ist es schnell genug, und Karmem wurde so konzipiert, dass sie dieses Muster nutzen, Zuordnungen vermeiden und die gleiche Struktur für mehrere Daten wiederverwenden.
Warum nicht Witx verwenden? Es ist ein gutes Projekt und zielt auf WASM ab, es erscheint jedoch komplexer und definiert nicht nur Datenstruktur, sondern auch Funktionen, die ich vermeiden möchte. Außerdem ist es nicht beabsichtigt, auf Nicht-WASMS tragbar zu sein. Warum nicht Flatbuffers verwenden? Wir haben es versucht, aber es ist nicht schnell genug und verursacht auch Panik aufgrund des Mangels an Grenzüberprüfung. Warum nicht Cap'n'proto verwenden? Es ist eine gute Alternative, aber es fehlt keine Implementierung für Zig und Assemblyskript, was die höchste Priorität ist. Es hat auch mehr Zuweisungen und die generierte API ist schwieriger zu verwenden als Karmem.
Das ist ein kleines Beispiel dafür, wie Karmem verwendet wird.
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 ;
} Generieren Sie den Code mit go run karmem.org/cmd/karmem build --golang -o "km" app.km
Um zu codieren, sollte die Verwendung eine native Struktur erstellen und dann codieren.
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 )
}Anstatt es zu einer anderen Struktur zu dekodieren, können Sie einige Felder direkt ohne zusätzliche Dekodierung lesen. In diesem Beispiel brauchen wir nur den Benutzernamen jedes Profils.
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 ))
}
} Hinweis: Wir verwenden NewAccountDataViewer , jeder Viewer ist nur ein Zuschauer und kopiert die Backend -Daten nicht. Einige Sprachen (C#, AssemblyScript) verwenden UTF-16, während Karmem UTF-8 verwendet, haben Sie in diesen Fällen eine gewisse Leistungsstrafe.
Sie können es auch zu einer existierenden Struktur dekodieren. In einigen Fällen ist es besser, wenn Sie die gleiche Struktur für Multiply-Lesevorgänge wiederverwenden.
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 )
}Verwenden von ähnlichem Schema mit Flatbuffers und Karmem. Karmem ist fast 10 -mal schneller als Google FlatBuffers.
Native (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 auf 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)
Die Leistung ist nahezu gleich, wenn Sie nicht serialisierte Daten aus einer nativen Struktur lesen und sie aus karmem-serialisierten Daten lesen.
Native (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)
Das ist ein Vergleich mit allen unterstützten Sprachen.
WebAssembly auf 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%
Derzeit konzentrieren wir uns auf WebAssembly, und deshalb sind dies die unterstützten Sprachen:
Einige Sprachen, die sich noch in der Entwicklung befinden, haben kein Rückwärtskompatibilitätsversprechen. Wir werden versuchen, mit der neuesten Version Schritt zu halten. Derzeit ist die generierte API und die Bibliotheken nicht als stabil berücksichtigt.
| Merkmale | Go/Tinygo | Zick | Assemblyskript | Schnell | C | C#/. Netz | Odin |
|---|---|---|---|---|---|---|---|
| Leistung | Gut | Exzellent | Gut | Arm | Exzellent | Schrecklich | Gut |
| Priorität | Hoch | Hoch | Hoch | Niedrig | Hoch | Medium | Niedrig |
| Codierung | |||||||
| Objektkodierung | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Rohe Codierung | |||||||
| Nullkopie | |||||||
| Dekodierung | |||||||
| Objektdecodierung | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Wiederverwendung von Objekten | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |
| Zufallszugriff | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Nullkopie | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |
| Null-Kopie-String | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ||
| Native Array | ✔️ | ✔️ | ✔️ | Euen | ✔️ |
Karmem verwendet eine benutzerdefinierte Schemasprache, die Strukturen, Aufzüge und Typen definiert.
Das Schema ist sehr einfach zu verstehen und zu definieren:
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 ;
} Jede Datei muss beginnen mit: karmem {name} [@tag()]; . Andere optionale Tags können definiert werden, wie oben gezeigt, wird empfohlen, die Option @packed(true) zu verwenden.
Primitive :
uint8 , uint16 , uint32 , uint64int8 , int16 , int32, int64float32 , float64boolbyte , charEs ist nicht möglich, optionale oder nullbare Typen zu definieren.
Arrays :
[{Length}]{Type} (Beispiel: [123]uint16 , [3]float32 )[]{Type} (Beispiel: []char , []uint64 )[<{Length}]{Type} (Beispiel: [<512]float64 , [<42]byte )Es ist nicht möglich, Tische oder Scheiben von Scheiben Scheiben Scheiben Scheiben zu haben. Es ist jedoch möglich, diese Typen in einen Inline-Struktur einzuwickeln.
Derzeit verfügt Karmem über zwei Strukturarten: Inline und Tabelle.
Inline: Inline -Strukturen werden, wie der Name schon sagt, bei Verwendung eingefügt. Das verringert die Größe und kann die Leistung verbessern. Die Definition kann jedoch nicht geändert werden. In Ordnung Wörter: Sie können das Feld einer Inline -Struktur nicht bearbeiten, ohne die Kompatibilität zu brechen.
struct Vec3 inline {
X float32 ;
Y float32 ;
Z float32 ;
} Diese Struktur ist genau das gleiche von [3]float32 und hat das gleiche Serialisierungsergebnis. Aus diesem Grund wird jede Änderung dieser Struktur (zum Beispiel in float64 oder Hinzufügen neuer Felder) die Kompatibilität durchbrechen.
Tabellen: Tabellen können verwendet werden, wenn Rückwärtskompatibilität wichtig ist. Beispielsweise können Tabellen neue Felder unten anhängen, ohne die Kompatibilität zu brechen.
struct User table {
Name [] char ;
Email [] char ;
Password [] char ;
}Betrachten wir, dass Sie ein anderes Feld benötigen ... für Tabellen ist dies kein Problem:
struct User table {
Name [] char ;
Email [] char ;
Password [] char ;
Telephone [] char ;
}Da es sich um eine Tabelle handelt, können Sie am Ende der Struktur neue Felder hinzufügen, und beide Versionen sind zwischen ihnen kompatibel. Wenn die Nachricht an einen Kunden gesendet wird, der das neue Feld nicht versteht, wird sie ignoriert. Wenn ein veralteter Client eine Nachricht an einen neueren Client sendet, hat das neue Feld den Standardwert (0, falsche, leere Zeichenfolge usw.).
Enums können als Alias für Ganzzahlen verwendet werden, wie z. B. uint8 .
enum Team uint8 {
Unknown ;
Humans ;
Orcs ;
Zombies = 255 ;
}Enums müssen mit einem Nullwert beginnen, den Standardwert in allen Fällen. Wenn der Wert einer Enum weggelassen wird, wird die Reihenfolge von Enum als Wert verwendet.
Sobald Sie ein Schema definiert haben, können Sie den Code generieren. Zunächst müssen Sie karmem installieren, es von der Releases -Seite erhalten oder mit GO ausführen.
karmem build --assemblyscript -o "output-folder" your-schema.km
Wenn Sie bereits Golang installiert haben, können Sie stattdessen go karmem.org/cmd/karmem build --zig -o "output-folder" your-schema.km verwenden.
Befehle:
build
--zig : Ermöglichen Sie die Erzeugung für Zig--swift : Ermöglichen Sie die Generation für Swift/SwiftWasmus--odin : Ermöglichen Sie die Erzeugung für Odin--golang : Ermöglichen Sie die Generation für Golang/Tinygo--dotnet : Ermöglichen Sie die Generation für .NET--c : Generierung für C aktivieren--assemblyscript : Aktivieren Sie die Generierung für Assemblyskript-o <dir> : Definiert den Ausgangsordner<input-file> : Definiert das EingangsschemaKarmem ist schnell und zielt auch darauf ab, sicher und stabil für den allgemeinen Gebrauch zu sein.
Aus den Grenzen
Karmem umfasst Grenzenprüfung, um das Lesen außerhalb des Gebrauchs zu verhindern und Abstürze und Panik zu vermeiden. Das ist etwas, das Google Flatbuffers nicht hat, und missgebildete Inhalte verursachen Panik. Es repariert jedoch nicht alle möglichen Schwachstellen.
Ressourcenerschöpfung
Karmem lässt einen Zeiger/Offset in derselben Nachricht mehrfach wiederverwendet werden. Leider ermöglicht dieses Verhalten eine kurze Nachricht, umfangreichere Arrays als die Nachrichtengröße zu generieren. Derzeit ist die einzige Minderung für dieses Problem darin bestehen, nur Arrays mit begrenzten Arrays zu verwenden und Objekt-API-Decodien zu vermeiden.
Datenleck
Karmem löscht den Speicher vor der Codierung nicht, die Informationen aus der vorherigen Nachricht oder aus dem Systemspeicher selbst auslaufen kann. Dies kann mit @packed(true) Tag gelöst werden, wie zuvor beschrieben. Das packed Tag wird die Polsterung aus der Nachricht entfernt, wodurch das Leck verhindert wird. Alternativ können Sie das Gedächtnis vor der Codierung manuell löschen.
Karmem hat einige Einschränkungen im Vergleich zu anderen Serialisierungsbibliotheken, wie z. B.:
Maximale Größe
Ähnlich wie bei Google Protobuf und Google FlatBuffers hat Karmem eine maximale Größe von 2 GB. Das ist die maximale Größe der gesamten Nachricht, nicht die maximale Größe jedes Arrays. Diese Einschränkung ist auf die Tatsache zurückzuführen, dass WASM 32-Bit beträgt, und die maximale Größe von 2 GB scheint für die aktuellen Bedürfnisse ausreichend zu sein. Der aktuelle Schriftsteller erzwingt diese Einschränkung nicht, aber das Lesen einer Botschaft, die größer als 2 GB ist, verursacht ein undefiniertes Verhalten.
Arrays von Arrays/Tabellen
Karmem unterstützt keine Arrays von Arrays oder Tabellenarrays. Es ist jedoch möglich, diese Typen in einen Inline-Struktur einzuwickeln, wie oben erwähnt. Diese Einschränkung wurde vorgeschrieben, um native Arrays/Slice aus der Sprache auszunutzen. Die meisten Sprachen verkapeln den Zeiger und die Größe des Arrays in einem strukturähnlichen, für die die Größe jedes Elements bekannt werden muss, wobei folglich Arrays von Elementen mit variabler Größe/-stritt verhindert werden.
UTF-8
Karmem unterstützt nur UTF-8 und unterstützt andere Codierungen nicht.