Karmemは、高速バイナリシリアル化形式です。 Karmemの優先事項は、できるだけ早く使いやすいことです。 GolangとTinygoの最大パフォーマンスを採用するように最適化されており、同じタイプの異なるコンテンツを読み取る繰り返し可能な読み取りに効率的です。 Karmemは、Googleフラットバッファの10倍速いことを実証しており、Bound-Checkingの追加のオーバーヘッドが含まれています。
ショ和 Karmemはまだ開発中ですが、APIは安定していません。ただし、Serialization-Format自体は変更とは異なり、古いバージョンとの逆方向に互換性がある必要があります。
Karmemは、1つの問題を解決するために作成されました。WebAssemblyホストとゲスト間でデータを簡単に転送できます。非ワバセンブル言語のためにまだポータブルですが。 Wasm-HostとWasm-Guestの間の「イベントコマンドパターン」を1つのプロジェクトで実験していますが、データの共有は非常に高価であり、FFIコールも安くはありません。 Karmemは一度エンコードし、言語に関係なく複数のゲストと同じコンテンツを共有し、非常に効率的にします。また、Object-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 )
}フラットバッファとカルメムで同様のスキーマを使用します。 Karmemは、Googleフラットバッファーのほぼ10倍速いです。
ネイティブ(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)
Wazero(MacOS/ARM64 -M1)のWebAssembly:
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)
これは、サポートされているすべての言語との比較です。
Wazero(MacOS/ARM64 -M1)のWebAssembly:
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 | ジグ | assemblyscript | 迅速 | c | C#/。ネット | オーディン |
|---|---|---|---|---|---|---|---|
| パフォーマンス | 良い | 素晴らしい | 良い | 貧しい | 素晴らしい | 最悪 | 良い |
| 優先度 | 高い | 高い | 高い | 低い | 高い | 中くらい | 低い |
| エンコーディング | |||||||
| オブジェクトエンコーディング | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ |
| 生のエンコーディング | |||||||
| ゼロコピー | |||||||
| デコード | |||||||
| オブジェクトデコード | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ |
| オブジェクトの再利用 | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | |
| ランダムアクセス | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ |
| ゼロコピー | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | |
| ゼロコピーストリング | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ✔✔️ | ||
| ネイティブ配列 | ✔✔️ | ✔✔️ | ✔✔️ | ショ和 | ✔✔️ |
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 )テーブルのスライスや酵素のスライス、またはスライスのスライスを用意することはできません。ただし、これらのタイプを1つのインライン構造内に包むことは可能です。
現在、Karmemにはインラインとテーブルの2つの構造体があります。
インライン:名前が示すように、インライン構造体は使用するとインラリングされます。これにより、サイズが削減され、パフォーマンスが向上する可能性があります。ただし、定義を変更することはできません。順番に:互換性を破ることなく、1つのインライン構造体のフィールドを編集することはできません。
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などのIntegersタイプのエイリアスとして使用できます。
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は高速であり、一般的な使用のために安全で安定することも目的としています。
範囲外
Karmemには、バウンド外の読み取りを防ぎ、クラッシュやパニックを避けるための限界チェックが含まれています。これは、Google Flatbuffersにはないものであり、奇形のコンテンツはパニックを引き起こします。ただし、すべての可能性のある脆弱性を修正するわけではありません。
リソースの疲労
Karmemを使用すると、1つのポインター/オフセットを同じメッセージで複数回再利用できます。残念ながら、その動作により、短いメッセージがメッセージサイズよりも広範な配列を生成することが可能になります。現在、その問題の唯一の緩和は、配列の代わりに限定アレイを使用し、オブジェクトAPIデコードを回避することです。
データリーク
Karmemは、エンコードする前にメモリをクリアしません。これは、前のメッセージまたはシステムメモリ自体から情報を漏らします。前述のように、 @packed(true)タグを使用して解決できます。 packedタグは、メッセージからパディングを削除し、漏れを防ぎます。または、エンコードする前に手動でメモリをクリアすることができます。
Karmemには、次のような他のシリアル化ライブラリと比較していくつかの制限があります。
最大サイズ
Google ProtobufとGoogle Flatbuffersと同様に、Karmemの最大サイズは2GBです。これは、メッセージ全体の最大サイズであり、各配列の最大サイズではありません。この制限は、WASMが32ビットになるように設計されており、2GBの最大サイズが現在のニーズに適していると思われるという事実によるものです。現在のライターはこの制限を強制しませんが、2GBよりも大きいメッセージを読むと、未定義の動作が生じます。
配列/テーブルの配列
Karmemは、アレイの配列またはテーブルの配列をサポートしていません。ただし、上記のように、これらのタイプを1つのインライン構造内に包むことができます。その制限は、言語からネイティブ配列/スライスを利用するために課されました。ほとんどの言語は、各要素のサイズを既知にする必要がある構造体のようなポインターと配列のサイズをカプセル化し、その結果、さまざまなサイズ/ストライドを持つアイテムの配列を防ぎます。
UTF-8
KarmemはUTF-8のみをサポートし、他のエンコーディングをサポートしていません。