Karmem هو تنسيق تسلسلي ثنائي سريع. أولوية Karmem هي أن تكون سهلة الاستخدام بينما كانت سريعة قدر الإمكان. تم تحسين أداء Golang و Tinygo القصوى وهو فعال للقراءات القابلة للتكرار ، وقراءة محتوى مختلف من نفس النوع. يوضح Karmem أنه أسرع بعشر مرات من Google Flatbuffers ، مع تضمين النفقات العامة الإضافية لتكوين الحدود.
️ Karmem لا يزال قيد التطوير ، واجهة برمجة التطبيقات ليست مستقرة. ومع ذلك ، فإن التنسيق التسلسلي نفسه يختلف عن التغيير ويجب أن يظل متوافقًا مع الإصدارات القديمة.
تم إنشاء Karmem لحل مشكلة واحدة: اجعل البيانات سهلة نقل البيانات بين مضيف Webassembly و Guest. بينما لا تزال محمولة للغات غير ويب. نحن نجرب "نمط قائد الحدث" بين WASM-Host و WASM-GUEST في مشروع واحد ، ولكن مشاركة البيانات مكلفة للغاية ، ومكالمات FFI ليست رخيصة أيضًا. يشفر Karmem مرة واحدة وتبادل نفس المحتوى مع العديد من الضيوف ، بغض النظر عن اللغة ، مما يجعلها فعالة للغاية. أيضًا ، حتى باستخدام Object-API لفك التشفير ، فهو سريع بما فيه الكفاية ، وتم تصميم Karmem للاستفادة من هذا النمط ، وتجنب التخصيصات ، وإعادة استخدام الهيكل نفسه لبيانات متعددة.
لماذا لا تستخدم Witx؟ إنه مشروع جيد ويهدف إلى WASM ، ومع ذلك يبدو أكثر تعقيدًا ولا يحدد بنية البيانات فحسب ، بل وظائف ، والتي أحاول تجنبه. أيضا ، ليس المقصود أن تكون محمولة لغير الاشتراك. لماذا لا تستخدم Flatbuffers؟ لقد حاولنا ، لكنه ليس سريعًا بما فيه الكفاية ويؤدي أيضًا إلى ذعر بسبب الافتقار إلى التحقق من الحدود. لماذا لا تستخدم cap'n'proto؟ إنه بديل جيد ولكنه يفتقر إلى التنفيذ للملعبين والتجميع ، وهو أولوية عالية ، ويحتوي أيضًا على مخصصات أكبر ، ويصعب استخدام واجهة برمجة التطبيقات التي تم إنشاؤها ، مقارنةً بـ 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 )
}باستخدام مخطط مماثل مع Flatbuffers و Karmem. Karmem أسرع حوالي 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 on 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)
يكون الأداء هو نفسه تقريبًا عند مقارنة قراءة البيانات غير المخصصة من بنية أصلية وقراءتها من بيانات Karmemmized.
الأصلي (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 on 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%
حاليًا ، نركز على التجميع على الويب ، وبسبب أن هذه هي اللغات المدعومة:
بعض اللغات لا تزال قيد التطوير ، وليس لديها أي وعد توافق متخلف. سنحاول مواكبة أحدث إصدار. حاليًا ، لا ينبغي أن تنظر API المولد وينظر المكتبات في الاستقرار.
| سمات | الذهاب/tinygo | متعرج | 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 نوعان من الهياكل: مضمّن وجدول.
INLINE: هياكل مضمنة ، كما يوحي الاسم ، يتم مضمونها عند استخدامها. وهذا يقلل من الحجم وقد يحسن الأداء. ومع ذلك ، لا يمكن تغيير تعريفهم. من أجل الكلمات: لا يمكنك تحرير حقل بنية مضمنة دون كسر التوافق.
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 ، سلسلة خاطئة ، فارغة ، إلخ).
يمكن استخدام التعدادات كمستعار لنوع الأعداد الصحيحة ، مثل 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 : تمكين توليد التعرج--swift : تمكين توليد سويفت/سويفتوما--odin : تمكين توليد أودين--golang : تمكين توليد Golang/Tinygo--dotnet : تمكين توليد .NET--c : تمكين توليد C--assemblyscript : تمكين توليد التجميع-o <dir> : يحدد مجلد الإخراج<input-file> : يحدد مخطط الإدخالKarmem سريع ويهدف أيضًا إلى أن يكون آمنًا ومستقرًا للاستخدام العام.
خارج الحدود
يتضمن Karmem حدوث حدود لمنع القراءة خارج الحدود وتجنب الحوادث والذعر. هذا شيء لا يتمتع به Google Flatbuffers ، وسيؤدي المحتوى المشوه إلى الذعر. ومع ذلك ، فإنه لا يصلح جميع نقاط الضعف الممكنة.
استنفاد الموارد
يسمح Karmem بمؤشر/إزاحة يمكن إعادة استخدامه عدة مرات في نفس الرسالة. لسوء الحظ ، هذا السلوك يجعل من الممكن لرسالة قصيرة إنشاء صفائف أكثر شمولاً من حجم الرسالة. في الوقت الحالي ، فإن التخفيف الوحيد لهذه المشكلة هو استخدام المصفوفات المحدودة بدلاً من المصفوفات وتجنب فك تشفير الكائن API.
تسرب البيانات
لا يقوم Karmem بمسح الذاكرة قبل الترميز ، والتي قد تسرب المعلومات من الرسالة السابقة أو من ذاكرة النظام نفسها. يمكن حلها باستخدام علامة @packed(true) ، كما هو موضح سابقًا. ستقوم العلامة packed بإزالة الحشوة من الرسالة ، والتي ستمنع التسرب. بدلاً من ذلك ، يمكنك مسح الذاكرة قبل الترميز ، يدويًا.
لدى Karmem بعض القيود مقارنة بمكتبات التسلسل الأخرى ، مثل:
الحد الأقصى للحجم
على غرار Google Protobuf و Google Flatbuffers ، لدى Karmem حجم أقصى قدره 2 جيجابايت. هذا هو الحد الأقصى لحجم الرسالة بأكملها ، وليس الحد الأقصى لحجم كل صفيف. يرجع هذا القيد إلى حقيقة أن WASM مصمم ليكون 32 بت ، ويبدو أن الحد الأقصى لحجم 2GB كافية للاحتياجات الحالية. الكاتب الحالي لا يفرض هذا القيد ، لكن قراءة رسالة أكبر من 2 جيجابايت ستؤدي إلى سلوك غير محدد.
صفائف المصفوفات/الجداول
Karmem لا يدعم صفائف المصفوفات أو صفائف الجداول. ومع ذلك ، من الممكن لف هذه الأنواع داخل الهيكل المضمن ، كما ذكر أعلاه. تم فرض هذا القيد للاستفادة من المصفوفات/الشريحة الأصلية من اللغة. تغلف معظم اللغات المؤشر وحجم الصفيف داخل البنية ، مما يتطلب أن يكون حجم كل عنصر معروفًا ، وبالتالي يمنع صفائف العناصر ذات الحجم/الخطوات المتغيرة.
UTF-8
يدعم Karmem UTF-8 فقط ولا يدعم الترميزات الأخرى.