41个版本 (9个破坏性更新)
0.9.60 | 2021年3月26日 |
---|---|
0.9.6 | 2021年3月10日 |
0.7.3 | 2020年12月30日 |
0.4.2 | 2020年11月20日 |
0.0.0-beta.3 | 2020年3月21日 |
#839 in 编码
每月下载量 93次
795KB
13K SLoC
NoProto:带RPC的灵活、快速、紧凑序列化
功能
轻量级
- 无依赖
no_std
支持,WASM ready- 最紧凑的非编译存储格式
稳定
- 安全接受不受信任的缓冲区
- 通过 Miri 编译器安全检查
- 无 panic 和 unwrap
简单易用
- 详尽的文档和测试
- 与JSON完全兼容,导入和导出JSON值
- 详尽文档 & 简单的数据存储格式
快速
- 零拷贝反序列化
- 大多数更新都是追加的
- 反序列化是增量式的
强大
- 原生字节排序
- 支持递归数据类型
- 支持大多数常见原生数据类型
- 支持集合(列表、映射、结构体和元组)
- 支持任意嵌套的集合类型
- 模式支持默认值和非破坏性更新
- 无传输协议依赖的 RPC框架.
为什么还需要另一种序列化格式?
- NoProto结合了编译格式的高性能和动态格式的灵活性
编译 格式如Flatbuffers、CapN Proto和bincode具有惊人的性能和非常紧凑的缓冲区,但您必须将数据类型编译到您的应用程序中。这意味着如果数据的模式发生变化,应用程序必须重新编译以适应新的模式。
动态格式如JSON、MessagePack和BSON,在运行时可以存储任何模式的数据,但缓冲区庞大,性能介于糟糕和希望可接受之间。
NoProto借鉴了编译格式的高性能优势,并实现了灵活的格式。
- NoProto是一种以键值数据库为核心的格式
字节排序曾经尝试在数据库中存储作为可排序键的有符号整数吗?NoProto可以做到。几乎所有数据类型都以字节排序的方式存储在缓冲区中,这意味着可以在字节级别比较缓冲区以进行排序,而不需要反序列化。
主键管理NoProto使复合可排序键的生成、维护和更新变得极其简单。在键值存储中,你不需要自定义排序函数,只需要这个库。
UUID & ULID支持NoProto是少数几个原生支持这些流行的主键数据类型的格式之一。它可以轻松编码、解码和生成这些数据类型。
最快更新NoProto是唯一一个支持所有突变而不需要反序列化的格式。它在其他动态格式之间,数据库的读 -> 更新 -> 写操作可以快50x - 300x。 基准测试
与其他格式比较
与Apache Avro相比
- 空间效率更高- 序列化和反序列化速度显著加快
- 所有值都是可选的(没有void或null类型)
- 支持更多原生类型(如无符号整数)
- 更新不需要反序列化/序列化
- 与`no_std`兼容。
- 安全处理不受信任的数据。
与Protocol Buffers相比
- 序列化和反序列化性能相当- 更新缓冲区速度快一个数量级
- 模式在运行时是动态的,无需编译步骤
- 所有值都是可选的
- 支持更多类型,并且嵌套类型支持更好
- 字节排序是第一类操作
- 更新不需要反序列化/序列化
- 安全处理不受信任的数据。
- 所有值都是可选的,可以以任何顺序插入。
与JSON / BSON相比
- 空间效率更高- 序列化和反序列化速度显著加快
- 反序列化是零拷贝
- 有模式/类型安全
- 支持字节排序
- 支持原始字节和其他原生类型
- 更新不需要反序列化/序列化
- 与`no_std`兼容。
- 安全处理不受信任的数据。
与Flatbuffers / Bincode相比
- 数据类型可以在运行时更改或创建- 更新缓冲区速度快一个数量级
- 支持字节排序
- 更新不需要反序列化/序列化
- 与`no_std`兼容。
- 安全处理不受信任的数据。
- 所有值都是可选的,可以以任何顺序插入。
格式 | 零拷贝 | 大小限制 | 可变 | 模式 | 字节排序 |
---|---|---|---|---|---|
运行时库 | |||||
NoProto | ✓ | ~4GB | ✓ | ✓ | ✓ |
Apache Avro | ✗ | 2^63字节 | ✗ | ✓ | ✓ |
JSON | ✗ | 无限 | ✓ | ✗ | ✗ |
BSON | ✗ | ~16MB | ✓ | ✗ | ✗ |
MessagePack | ✗ | 无限 | ✓ | ✗ | ✗ |
编译库 | |||||
FlatBuffers | ✓ | ~2GB | ✗ | ✓ | ✗ |
Bincode | ✓ | ? | ✓ | ✓ | ✗ |
Protocol Buffers | ✗ | ~2GB | ✗ | ✓ | ✗ |
Cap'N Proto | ✓ | 2^64字节 | ✗ | ✓ | ✗ |
Veriform | ✗ | ? | ✗ | ✗ | ✗ |
快速示例
use no_proto::error::NP_Error;
use no_proto::NP_Factory;
// An ES6 like IDL is used to describe schema for the factory
// Each factory represents a single schema
// One factory can be used to serialize/deserialize any number of buffers
let user_factory = NP_Factory::new(r#"
struct({ fields: {
name: string(),
age: u16({ default: 0 }),
tags: list({ of: string() })
}})
"#)?;
// create a new empty buffer
let mut user_buffer = user_factory.new_buffer(None); // optional capacity
// set the "name" field
user_buffer.set(&["name"], "Billy Joel")?;
// read the "name" field
let name = user_buffer.get::<&str>(&["name"])?;
assert_eq!(name, Some("Billy Joel"));
// set a nested value, the first tag in the tag list
user_buffer.set(&["tags", "0"], "first tag")?;
// read the first tag from the tag list
let tag = user_buffer.get::<&str>(&["tags", "0"])?;
assert_eq!(tag, Some("first tag"));
// close buffer and get internal bytes
let user_bytes: Vec<u8> = user_buffer.finish().bytes();
// open the buffer again
let user_buffer = user_factory.open_buffer(user_bytes);
// read the "name" field again
let name = user_buffer.get::<&str>(&["name"])?;
assert_eq!(name, Some("Billy Joel"));
// get the age field
let age = user_buffer.get::<u16>(&["age"])?;
// returns default value from schema
assert_eq!(age, Some(0u16));
// close again
let user_bytes: Vec<u8> = user_buffer.finish().bytes();
// we can now save user_bytes to disk,
// send it over the network, or whatever else is needed with the data
# Ok::<(), NP_Error>(())
指导学习/下一步
Schemas
- 学习如何构建和操作模式。Factories
- 将模式解析为可以操作的内容。Buffers
- 如何创建、更新和压缩缓冲区/数据。RPC Framework
- 如何使用RPC框架API。数据 & 模式格式
- 学习数据如何保存到缓冲区和模式。
基准测试
在公平的方式下对这类库进行基准测试非常困难,但我已在下面的图表中尝试进行了。这些基准测试可在bench
文件夹中找到,您可以使用以下命令轻松运行它们:cargo run --release
。
基准测试所使用的格式和数据来自flatbuffers
的GitHub仓库。在做出任何选择之前,您应该为每个库进行基准测试/测试自己的用例。
图例:操作/毫秒,数值越高越好
格式/库 | 编码 | 解码全部 | 解码1 | 更新1 | 大小(字节) | 大小(Zlib) |
---|---|---|---|---|---|---|
运行时库 | ||||||
NoProto | ||||||
no_proto | 1393 | 1883 | 55556 | 9524 | 308 | 198 |
Apache Avro | ||||||
avro-rs | 156 | 57 | 56 | 40 | 702 | 337 |
FlexBuffers | ||||||
flexbuffers | 444 | 962 | 24390 | 294 | 490 | 309 |
JSON | ||||||
json | 609 | 481 | 607 | 439 | 439 | 184 |
serde_json | 938 | 646 | 644 | 403 | 446 | 198 |
BSON | ||||||
bson | 129 | 116 | 123 | 90 | 414 | 216 |
rawbson | 130 | 1117 | 17857 | 89 | 414 | 216 |
MessagePack | ||||||
rmp | 661 | 623 | 832 | 202 | 311 | 193 |
messagepack-rs | 152 | 266 | 284 | 138 | 296 | 187 |
编译库 | ||||||
Flatbuffers | ||||||
flatbuffers | 3165 | 16393 | 250000 | 2532 | 264 | 181 |
Bincode | ||||||
bincode | 6757 | 9259 | 10000 | 4115 | 163 | 129 |
Postcard | ||||||
postcard | 3067 | 7519 | 7937 | 2469 | 128 | 119 |
Protocol Buffers | ||||||
protobuf | 953 | 1305 | 1312 | 529 | 154 | 141 |
prost | 1464 | 2020 | 2232 | 1040 | 154 | 142 |
Abomonation | ||||||
abomonation | 2342 | 125000 | 500000 | 2183 | 261 | 160 |
Rkyv | ||||||
rkyv | 1605 | 37037 | 200000 | 1531 | 180 | 154 |
- 编码:将测试数据的字段集合传输到序列化的
Vec<u8>
。 - 解码全部:将测试对象从
Vec<u8>
反序列化到所有字段。 - 解码1:将测试对象从
Vec<u8>
反序列化到一个字段。 - 更新1:反序列化、更新单个字段,然后将序列化数据重新写入
Vec<u8>
。
运行时与编译库:某些格式需要将数据类型编译到应用程序中,这提高了性能,但意味着数据类型在运行时无法更改。如果数据类型需要在运行时更改或无法在应用程序编译前知道(如数据库),则必须使用不将数据类型编译到应用程序中的格式,如JSON或NoProto。
完整的基准测试源代码可在此处找到。欢迎提出改进这些基准测试质量的建议。
NoProto 优势
如果您的用例符合以下任何一点,NoProto可能适合您的应用程序。
-
运行时灵活
如果您需要处理将在运行时更改或创建的数据类型,通常您必须选择类似JSON这样的东西,因为高度优化的格式(如Flatbuffers和Bincode)依赖于将数据类型编译到您的应用程序中(使一切在运行时固定)。当涉及到可以在运行时更改/实现数据类型的格式时,NoProto是我们所知的最快的格式(如果您知道可能更快的一种,请告诉我们!)。 -
安全接受不受信任的数据
NoProto 缓冲区的最坏情况失败模式是垃圾数据。虽然其他格式可能导致拒绝服务攻击或允许不安全的内存访问,但NoProto没有这样的失败案例。无法构造一个会导致主机应用程序性能下降或导致不安全内存访问的NoProto缓冲区。此外,库中没有引起恐慌的代码,这意味着它永远不会使您的应用程序崩溃。 -
极快更新
如果你的应用中有读取->修改->写入缓冲区的流程,NoProto通常会比其他任何格式(包括Bincode和Flatbuffers)表现更好。这是因为NoProto实际上从不反序列化,它不需要这样做。这包括像在嵌套列表中推入值或替换整个结构体这样复杂的变更。 -
所有字段可选,任意顺序插入/更新
许多格式要求所有值都存在才能关闭缓冲区,此外它们可能还需要按照特定的顺序插入数据以适应编码/解码方案。在NoProto中,所有字段都是可选的,任何更新/插入都可以在任何顺序发生。 -
增量反序列化
你只需为读取的字段付费,不再需要支付更多。NoProto中没有反序列化步骤,打开缓冲区不会执行任何操作。一旦你开始请求字段,库将使用格式规则导航缓冲区,只获取你请求的内容,而不获取其他内容。如果你在应用中有读取缓冲区并只获取其中几个字段的流程,NoProto将优于大多数其他库。 -
按字节排序
几乎所有的NoProto数据类型都设计成可以序列化为按字节排序的值,包括有符号整数。当与元组一起使用时,创建复合排序的数据库键变得极其容易。当你结合对UUID和ULID的第一级支持时,NoProto成为解析和创建RocksDB、LevelDB和TiKV等数据库主键的出色工具。 -
no_std
支持
如果你需要一个在no_std
环境中使用的内存占用低的序列化格式,NoProto是少数几个不错的选择之一。 -
稳定
NoProto永远不会导致你的应用崩溃。它有零崩溃或解包,这意味着没有可能导致崩溃的代码路径。默认行为是提供合理的默认路径或向上传递错误。 -
与CPU无关
NoProto缓冲区中的所有数字和指针始终以大端存储,因此你可以在任何CPU架构上安全地创建缓冲区,并知道它们可以与任何其他CPU架构一起工作。
何时使用Flatbuffers / Bincode / CapN Proto
如果你可以安全地将所有数据类型编译到你的应用中,所有缓冲区/数据都是可信的,并且你不想在创建缓冲区后修改它们,Bincode/Flatbuffers/CapNProto是你更好的选择。
何时使用JSON / BSON / MessagePack
如果你的数据变化如此频繁,以至于模式没有实际意义,或者你使用的格式必须是自描述的,JSON/BSON/MessagePack是更好的选择。尽管我争论说,如果你可以让模式工作,你应该这样做。一旦你可以使用具有模式的格式,你就可以在结果缓冲区中节省大量空间,并且性能更好。
限制
- 结构体和元组不能超过255项。
- 列表和映射不能超过2^16 (~64k)项。
- 你不能嵌套超过255层。
- 结构体字段名不能超过255个UTF8字节。
- 枚举/选项类型限于255个选项,每个选项不能超过255个UTF8字节。
- 映射键不能超过255个UTF8字节。
- 缓冲区不能超过2^32字节或~4GB。
不安全
这个库利用了unsafe
来实现更好的性能。一般来说,没有unsafe
,就不可能有一个高性能的序列化库。它只在性能提升显著且执行额外检查的地方使用,以确保任何unsafe
块最坏的情况只是导致缓冲区中的数据变得无效。
MIT许可证
版权所有 (c) 2021 Scott Lott
特此授予任何人无限制地使用此软件及其相关文档文件(以下简称“软件”)的权利,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售软件副本,并允许向软件提供者提供软件的人这样做,前提是遵守以下条件
上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。
软件按“原样”提供,不提供任何形式的保证,无论是明示的、暗示的还是其他方式的,包括但不限于适销性、特定用途适用性和非侵权性保证。在任何情况下,作者或版权所有者不对任何索赔、损害或其他责任承担责任,无论这些责任是基于合同、侵权或其他原因,是否因软件或其使用或其他方式引起。