Karmem เป็นรูปแบบการทำให้เป็นอนุกรมแบบไบนารีที่รวดเร็ว ลำดับความสำคัญของ Karmem นั้นใช้งานง่ายในขณะที่เร็วที่สุดเท่าที่จะทำได้ มันได้รับการปรับให้ดีที่สุดที่จะใช้ประสิทธิภาพสูงสุดของ Golang และ Tinygo และมีประสิทธิภาพสำหรับการอ่านซ้ำ ๆ อ่านเนื้อหาที่แตกต่างกันในประเภทเดียวกัน Karmem แสดงให้เห็นว่าเร็วกว่า Google Flatbuffers สิบเท่าโดยมีค่าใช้จ่ายเพิ่มเติมจากการตรวจสอบขอบเขตเพิ่มเติม
Karmem ยังอยู่ระหว่างการพัฒนา API ไม่มั่นคง อย่างไรก็ตามรูปแบบการทำให้เป็นอนุกรมนั้นแตกต่างจากการเปลี่ยนแปลงและควรอยู่ย้อนหลังเข้ากันได้กับรุ่นเก่า
Karmem ถูกสร้างขึ้นเพื่อแก้ปัญหาเดียว: ทำให้ง่ายต่อการถ่ายโอนข้อมูลระหว่างโฮสต์ WebAssembly และแขก ในขณะที่ยังคงพกพาสำหรับภาษาที่ไม่ใช่ Webassembly เรากำลังทดลองกับ "รูปแบบการควบคุมเหตุการณ์" ระหว่าง WASM-HOST และ WASM-GUEST ในโครงการเดียว แต่การแบ่งปันข้อมูลมีราคาแพงมากและการโทร FFI ก็ไม่ถูกเช่นกัน Karmem เข้ารหัสหนึ่งครั้งและแบ่งปันเนื้อหาเดียวกันกับแขกหลายคนโดยไม่คำนึงถึงภาษาทำให้มีประสิทธิภาพมาก นอกจากนี้แม้จะใช้ Object-API เพื่อถอดรหัสก็เร็วพอและ Karmem ได้รับการออกแบบมาเพื่อใช้ประโยชน์จากรูปแบบนั้นหลีกเลี่ยงการจัดสรรและใช้โครงสร้างเดียวกันอีกครั้งสำหรับข้อมูลหลายข้อมูล
ทำไมไม่ใช้ Witx? มันเป็นโครงการที่ดีและมุ่งเป้าไปที่ WASM แต่ดูเหมือนว่าซับซ้อนกว่าและกำหนดไม่เพียงแค่โครงสร้างข้อมูล แต่เป็นฟังก์ชั่นที่ฉันพยายามหลีกเลี่ยง นอกจากนี้ยังไม่ได้มีวัตถุประสงค์เพื่อพกพาไปที่ไม่ใช่ความวิเศษ ทำไมไม่ใช้ Flatbuffers? เราพยายาม แต่มันไม่เร็วพอและยังทำให้เกิดความตื่นตระหนกเนื่องจากขาดการตรวจสอบ ทำไมไม่ใช้ 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 Flatbuffers เกือบ 10 เท่า
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 บน 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)
ประสิทธิภาพเกือบจะเหมือนกันเมื่อเปรียบเทียบการอ่านข้อมูลที่ไม่ได้รับการรักษาจากโครงสร้างดั้งเดิมและอ่านจากข้อมูลที่เป็นกรรม
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)
นั่นคือการเปรียบเทียบกับภาษาที่รองรับทั้งหมด
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 | ซิก | แอสเซมบลีสคริปต์ | ฉับพลัน | C | c#/. net | โอดิน |
|---|---|---|---|---|---|---|---|
| ผลงาน | ดี | ยอดเยี่ยม | ดี | ยากจน | ยอดเยี่ยม | น่ากลัว | ดี |
| ลำดับความสำคัญ | สูง | สูง | สูง | ต่ำ | สูง | ปานกลาง | ต่ำ |
| การเข้ารหัส | |||||||
| การเข้ารหัสวัตถุ | |||||||
| การเข้ารหัสดิบ | |||||||
| สำเนาเป็นศูนย์ | |||||||
| การถอดรหัส | |||||||
| การถอดรหัสวัตถุ | |||||||
| การใช้วัตถุซ้ำ | |||||||
| การเข้าถึงแบบสุ่ม | |||||||
| สำเนาเป็นศูนย์ | |||||||
| ไม่มีการคัดลอก | |||||||
| อาร์เรย์พื้นเมือง |
Karmem ใช้ภาษาสคีมาแบบกำหนดเองซึ่งกำหนดโครงสร้าง enums และประเภท
สคีมานั้นง่ายมากที่จะเข้าใจและกำหนด:
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 )เป็นไปไม่ได้ที่จะมีตารางหรือชิ้นส่วนของ enums หรือชิ้นของชิ้น อย่างไรก็ตามมันเป็นไปได้ที่จะห่อประเภทเหล่านั้นภายในหนึ่งเส้นแบบอินไลน์
ปัจจุบัน Karmem มีสองโครงสร้าง: อินไลน์และตาราง
อินไลน์: โครงสร้างแบบอินไลน์ตามชื่อที่แนะนำจะถูก inlined เมื่อใช้ ที่ลดขนาดและอาจปรับปรุงประสิทธิภาพ อย่างไรก็ตามมันไม่สามารถเปลี่ยนคำจำกัดความได้ ตามคำสั่ง: คุณไม่สามารถแก้ไขฟิลด์ของโครงสร้างแบบอินไลน์หนึ่งแบบโดยไม่ทำลายความเข้ากันได้
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, เท็จ, สตริงว่างเปล่า ฯลฯ )
enums สามารถใช้เป็นนามแฝงกับประเภทจำนวนเต็มเช่น uint8
enum Team uint8 {
Unknown ;
Humans ;
Orcs ;
Zombies = 255 ;
}enums จะต้องเริ่มต้นด้วยค่าศูนย์ค่าเริ่มต้นในทุกกรณี หากค่าของ enum ใด ๆ ถูกละเว้นมันจะใช้ลำดับของ enum เป็นค่า
เมื่อคุณมีสคีมาที่กำหนดไว้คุณสามารถสร้างรหัส ก่อนอื่นคุณต้องติดตั้ง 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 : เปิดใช้งานการสร้างสำหรับ Swift/Swiftwasm--odin : เปิดใช้งานการสร้างโอดิน--golang : เปิดใช้งานการสร้างสำหรับ Golang/TinyGo--dotnet : เปิดใช้งานการสร้างสำหรับ. NET--c : เปิดใช้งานการสร้างสำหรับ C--assemblyscript : เปิดใช้งานการสร้างสำหรับแอสเซมบลีสคริปต์-o <dir> : กำหนดโฟลเดอร์เอาต์พุต<input-file> : กำหนดสคีมาอินพุตKarmem นั้นรวดเร็วและมีวัตถุประสงค์เพื่อรักษาความปลอดภัยและมั่นคงสำหรับการใช้งานทั่วไป
นอกขอบเขต
Karmem รวมถึงการตรวจสอบขอบเขตเพื่อป้องกันการอ่านออกนอกขอบเขตและหลีกเลี่ยงการล่มและตื่นตระหนก นั่นคือสิ่งที่ Google Flatbuffers ไม่มีและเนื้อหาที่ผิดรูปจะทำให้เกิดความตื่นตระหนก อย่างไรก็ตามมันไม่ได้แก้ไขช่องโหว่ที่เป็นไปได้ทั้งหมด
ความอ่อนเพลียของทรัพยากร
Karmem อนุญาตให้ใช้ตัวชี้/ออฟเซ็ตหนึ่งตัวสามารถนำกลับมาใช้ใหม่ได้หลายครั้งในข้อความเดียวกัน น่าเสียดายที่พฤติกรรมนั้นทำให้ข้อความสั้น ๆ สามารถสร้างอาร์เรย์ที่กว้างขวางกว่าขนาดข้อความ ขณะนี้การบรรเทาผลกระทบเพียงอย่างเดียวสำหรับปัญหานั้นคือการใช้อาร์เรย์ จำกัด แทนอาร์เรย์และหลีกเลี่ยงการถอดรหัส Object-API
การรั่วไหลของข้อมูล
Karmem ไม่ได้ล้างหน่วยความจำก่อนที่จะเข้ารหัสซึ่งอาจรั่วไหลข้อมูลจากข้อความก่อนหน้าหรือจากหน่วยความจำระบบเอง ที่สามารถแก้ไขได้โดยใช้แท็ก @packed(true) ตามที่อธิบายไว้ก่อนหน้า แท็ก packed จะลบช่องว่างภายในออกจากข้อความซึ่งจะป้องกันการรั่วไหล หรือคุณสามารถล้างหน่วยความจำก่อนเข้ารหัสด้วยตนเอง
Karmem มีข้อ จำกัด บางประการเมื่อเทียบกับห้องสมุดการทำให้เป็นอนุกรมอื่น ๆ เช่น:
ขนาดสูงสุด
เช่นเดียวกับ Google Protobuf และ Google Flatbuffers, Karmem มีขนาดสูงสุด 2GB นั่นคือขนาดสูงสุดของข้อความทั้งหมดไม่ใช่ขนาดสูงสุดของแต่ละอาร์เรย์ ข้อ จำกัด นี้เกิดจากความจริงที่ว่า WASM ได้รับการออกแบบให้เป็น 32 บิตและขนาดสูงสุดของ 2GB ดูเหมือนจะเพียงพอสำหรับความต้องการในปัจจุบัน นักเขียนปัจจุบันไม่ได้บังคับใช้ข้อ จำกัด นี้ แต่การอ่านข้อความที่ใหญ่กว่า 2GB จะทำให้เกิดพฤติกรรมที่ไม่ได้กำหนด
อาร์เรย์ของอาร์เรย์/ตาราง
Karmem ไม่สนับสนุนอาร์เรย์ของอาร์เรย์หรืออาร์เรย์ของตาราง อย่างไรก็ตามมีความเป็นไปได้ที่จะห่อประเภทเหล่านั้นภายในหนึ่งเส้นแบบอินไลน์ดังที่กล่าวไว้ข้างต้น ข้อ จำกัด นั้นถูกกำหนดให้ใช้ประโยชน์จากอาร์เรย์พื้นเมือง/ชิ้นจากภาษา ภาษาส่วนใหญ่ห่อหุ้มตัวชี้และขนาดของอาร์เรย์ภายในโครงสร้างที่ต้องใช้ขนาดของแต่ละองค์ประกอบที่จะรู้ดังนั้นจึงป้องกันอาร์เรย์ของรายการที่มีขนาด/ก้าวตัวแปร
UTF-8
Karmem รองรับ UTF-8 เท่านั้นและไม่รองรับการเข้ารหัสอื่น ๆ