fury:由jit和零拷贝支持的超快序列化框架


阿里alipay的Fury是一个极快的多语言序列化框架,由jit(即时编译)和零拷贝提供支持,提供高达 170 倍的性能和终极易用性。

仅用于序列化
通过使用fury将Java对象转换为字节流,您可以获得高达170倍的速度提升。并非所有情况都能获得 170 倍的加速。

170x 仅适用于这个结构:所有字段都是数字字段。


字符串、原始数组序列化将需要一个副本,并且会稍微摊销由 Fury jit 带来的加速。

序列化
常见的序列化操作包括:

  • 位图操作
  • 数字编码/解码
  • int/long 压缩
  • 字符串创建/复制
  • 字符串编码:ASCII/UTF8/UTF16
  • 内存复制
  • 数组复制&压缩
  • 元编码&压缩&缓存

Fury 使用 SIMD 和其他高级语言功能使每种语言的基本操作都变得非常快。

JIT动态编译加速
自定义类型对象通常包含大量类型信息,Fury利用这些信息在运行时生成高效的序列化代码,从而可以将大量运行时操作推入动态编译阶段。通过内联更多的方法、更好的代码缓存、减少虚拟方法调用、条件分支、哈希查找、元数据写入和内存读/写,序列化性能大大加快。

对于 Java,Fury 实现了运行时代码生成框架并定义了运算符表达式 IR。然后Fury可以在运行时根据对象的泛型类型信息进行类型推断,构建描述序列化代码逻辑的表达式树。codegen框架将从表达式树生成高效的Java代码,然后传递给Janino将其编译为字节码,并加载到用户的ClassLoader或Fury创建的ClassLoader中,最后通过Java JIT将其编译为高效的汇编代码。

由于JVM JIT跳过了大方法的编译和内联,Fury还实现了一个优化器,将大方法递归地拆分为小方法,从而确保所有代码都可以编译和内联。

小结:

  1. 通过生成代码中的内联变量减少内存访问。
  2. 通过生成代码中的内联调用减少虚拟方法调用。
  3. 减少条件分支。
  4. 减少哈希查找。所有内存访问、检查、虚拟方法

通过这种技术,序列化会快得多。唯一成本消耗只是内存复制。

静态代码生成
虽然JIT编译可以极大地提高序列化效率,并根据运行时数据的统计分布生成更好的序列化代码,但像这样的语言C++不支持反射,没有虚拟机,也没有内存模型的低级API。我们无法通过 JIT 为此类语言动态生成序列化代码。

在这种场景下,Fury正在实现一个AOT codegen框架,它根据对象模式静态生成序列化代码,并且可以使用生成的序列化器自动序列化对象。对于 Rust,Rust 宏用于静态生成代码。

缓存优化
在序列化自定义类型时,fury 会重新排序字段,以确保相同类型的字段按顺序序列化。这样可以命中更多的数据缓存和CPU指令缓存。

基本类型字段按字节大小降序写入。这样,如果初始地址对齐,后续的读写操作就会发生在内存地址对齐的位置,使得CPU执行效率更高。

Java序列化
Java 广泛应用于大数据、云原生、微服务和企业应用程序。因此,Fury对Java序列化做了很多优化,大大降低了系统延迟和服务器成本,并显着提高了吞吐量。我们的实施有以下亮点:

  • 极快的性能:基于 Java 类型、JIT 编译和 Unsafe 低级操作,Fury 比 JDK 快 170 倍,最多比 Kryo/Hessian 快 50~110 倍。
  • 100% JDK序列化API兼容性:原生支持所有JDK自定义序列化方法writeObject/readObject/writeReplace/readResolve/readObjectNoData,保证任何场景下序列化的正确性。Kryo/Hessian 在这些场景中存在一些正确性问题。
  • 直接替换JDK/Kryo/Hessian/Fst,无需修改用户代码。
  • 类型向前/向后兼容性:当反序列化和序列化类schema不一致时,仍然可以正确反序列化。支持应用程序升级和部署,独立添加/删除字段。与类型一致模式相比,Fury 类型兼容模式的实现没有性能损失。
  • 元数据共享:在上下文(TCP连接)下跨多个序列化共享元数据(类名,字段名和类型等),元数据仅在第一次序列化时发送给对等方,对等方可以根据此信息重建相同的反序列化器。后续序列化将跳过传输元数据,这样可以减少网络流量,并自动支持类型兼容性。
  • 零拷贝支持:支持带外零拷贝和堆外内存读/写。

Fury 支持所有通过 writeObject/readObject/writeReplace/readResolve/readObjectNoData/Externalizable 定制的序列化。用户不必更改这些代码。

项目点击标题