Karmem은 빠른 이진 직렬화 형식입니다. Karmem의 우선 순위는 가능한 빨리 사용하기 쉽습니다. Golang과 Tinygo의 최대 성능을 취하는 데 최적화되어 있으며 동일한 유형의 다른 콘텐츠를 읽고 반복 가능한 읽기에 효율적입니다. Karmem은 Google Flatbuffers보다 10 배 빠른 것으로 나타 났으며, 바운드 체크의 추가 오버 헤드가 포함되어 있습니다.
켈 Karmem은 여전히 개발 중이며 API는 안정적이지 않습니다. 그러나 직렬화 형식 자체는 변경과는 다르며 이전 버전과 뒤로 호환됩니다.
Karmem은 하나의 단일 문제를 해결하기 위해 만들어졌습니다. WebAssembly 호스트와 게스트간에 데이터를 쉽게 전송할 수 있습니다. 비 방사 언어는 여전히 휴대 할 수 있습니다. 우리는 하나의 프로젝트에서 WASM-Host와 WASM-GUEST 사이의 "이벤트-명령 패턴"을 실험하고 있지만 데이터 공유는 매우 비싸고 FFI 통화도 저렴하지 않습니다. Karmem은 언어에 관계없이 한 번 인코딩하고 동일한 컨텐츠를 여러 손님과 공유하므로 매우 효율적입니다. 또한 Object-API를 사용하여 디코딩하더라도 충분히 빠르며 Karmem은 해당 패턴을 이용하고 할당을 피하고 여러 데이터에 대해 동일한 구조물을 재사용하도록 설계되었습니다.
Witx를 사용하지 않는 이유는 무엇입니까? 그것은 좋은 프로젝트이며 WASM을 목표로하지만, 더 복잡해 보이고 데이터 구조뿐만 아니라 피하려고하는 기능을 정의합니다. 또한 비 웨이스트에 휴대 할 수있는 것은 아닙니다. 플랫 버퍼를 사용하지 않는 이유는 무엇입니까? 우리는 시도했지만 충분히 빠르지 않으며 바운드 체크가 부족하여 공황을 일으 킵니다. Cap'n'Proto를 사용하지 않는 이유는 무엇입니까? 좋은 대안이지만 Zig 및 AssemblyScript에 대한 구현이 부족합니다.
그것은 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은 Google Flatbuffers보다 거의 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의 WebAsSembly (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)
성능은 기본 구조물에서 비 시리얼 데이터를 읽고 Karmem-Serialized 데이터에서 읽는 것을 비교할 때 거의 동일합니다.
네이티브 (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의 WebAsSembly (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가 생성되었으며 라이브러리는 안정적인 것을 고려해서는 안됩니다.
| 특징 | 가/작은 고고 | 급격한 변경 | 어셈블리 스크립트 | 스위프트 | 기음 | 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, Empty String 등)을 갖습니다.
열거는 uint8 과 같은 정수 유형의 별칭으로 사용할 수 있습니다.
enum Team uint8 {
Unknown ;
Humans ;
Orcs ;
Zombies = 255 ;
}열거는 모든 경우의 기본값 인 0 값으로 시작해야합니다. 열거의 값이 생략되면 열거 순서를 값으로 사용합니다.
스키마가 정의 된 후에는 코드를 생성 할 수 있습니다. 먼저 karmem 설치하거나 릴리스 페이지에서 가져 오거나 이동하여 실행해야합니다.
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 : 어셈블리 스크립트 생성을 활성화합니다-o <dir> : 출력 폴더를 정의합니다<input-file> : 입력 스키마를 정의합니다Karmem은 빠르며 일반적인 사용을 위해 안전하고 안정적인 것을 목표로합니다.
한계 밖
Karmem에는 외형 읽기를 방지하고 충돌 및 공황을 피하기 위해 한계 검사가 포함되어 있습니다. 그것은 Google Flatbuffers가 가지고 있지 않은 것이며, 기형 콘텐츠는 공황을 유발할 것입니다. 그러나 가능한 모든 취약점을 해결하는 것은 아닙니다.
자원 소진
Karmem은 동일한 메시지에서 하나의 포인터/오프셋을 여러 번 다시 사용할 수 있습니다. 불행히도, 그 동작은 짧은 메시지가 메시지 크기보다 더 광범위한 배열을 생성 할 수있게합니다. 현재이 문제에 대한 유일한 완화는 배열 대신 제한 계산을 사용하고 객체 -API 디코드를 피하는 것입니다.
데이터 유출
Karmem은 인코딩하기 전에 메모리를 지우지 않으며 이전 메시지 나 시스템 메모리 자체에서 정보를 누출 할 수 있습니다. 앞에서 설명한대로 @packed(true) 태그를 사용하여 해결할 수 있습니다. packed 태그는 메시지에서 패딩을 제거하여 누출을 방지합니다. 또는 인코딩하기 전에 메모리를 지우실 수 있습니다.
Karmem은 다음과 같은 다른 직렬화 라이브러리와 비교하여 몇 가지 제한 사항이 있습니다.
최대 크기
Google Protobuf 및 Google Flatbuffers와 마찬가지로 Karmem의 크기는 2GB입니다. 이것이 각 배열의 최대 크기가 아니라 전체 메시지의 최대 크기입니다. 이 제한은 WASM이 32 비트로 설계되었으며 최대 2GB의 크기는 현재 요구에 적합한 것으로 보입니다. 현재 작가는이 제한을 시행하지 않지만 2GB보다 큰 메시지를 읽으면 정의되지 않은 동작이 발생합니다.
배열/테이블의 배열
Karmem은 어레이 배열 또는 테이블 배열을 지원하지 않습니다. 그러나 위에서 언급 한 것처럼 이러한 유형을 하나의 인라인 스트럭 내에 랩핑 할 수 있습니다. 이 제한은 언어에서 기본 배열/슬라이스를 활용하기 위해 부과되었습니다. 대부분의 언어는 구조물과 같은 구조체 내부의 배열의 포인터와 크기를 캡슐화하여 각 요소의 크기를 알고 있어야하며, 결과적으로 가변 크기/스트라이드의 항목 배열을 방지합니다.
UTF-8
Karmem은 UTF-8 만 지원하며 다른 인코딩을 지원하지 않습니다.