办公问答网

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 66|回复: 1

fastFFI 官宣开源,一款高效的Java跨语言通信框架

[复制链接]

1

主题

5

帖子

8

积分

新手上路

Rank: 1

积分
8
发表于 2022-12-5 12:54:21 | 显示全部楼层 |阅读模式


在近期的云栖大会上,阿里巴巴 JVM 团队开源了其自主研发的 fastFFI 项目,目前,fastFFI 的代码和样例均在 Github (访问链接见文末)上可以访问。
fastFFI 介绍
fastFFI 是一个现代高效的 FFI 框架,其开发初衷是提高不同语言之间相互通信的易用性与性能,目前的实现主要是针对 Java 访问 C++ 代码和数据。不同程序设计语言擅长解决不同的问题,因此跨语言调用的需求在现代软件开发过程中越来越强烈。
JNI (Java Native Interface) 是 JVM 标准的  FFI  接口,开发  JNI  应用耗时且易错,除此之外, 频繁的通过 Java Native 方法对 JNI 函数的调用往往会 带来严重的性能问题 。为了使用一个外部的函数,开发者需要首先在 Java 里声明一个 Native 方法,之后再在 Native 端开发一个 JNI 函数,在这个 JNI 函数里实现对外部函数的调用。由于 Java 和 Native 端的类型不同,JNI 函数还需要做相应的类型转换。




相比对普通的 Java 方法的调用,对 Java Native 方法的调用有额外性能开销,这些开销主要来自两部分:
1、对 Java Native 方法以及 JNI 函数调用的传参、上下文切换的开销 ,这是由于 JIT 编译器无法通过内联优化消除对 JNI 函数的调用。这一类开销主要是由于 Java 方法是由 JIT 编译,而 JNI 函数是由 C++ 编译器编译,不同编译器之间存在屏障。
2、Java Native 方法和 JNI 函数之间参数和返回值类型转换 ,例如所有的 Java String 对象需要通过 JNI 提供的接口函数转成对应的 C-style 字符串。这一类开销主要是由于 Java 和 Native 数据类型的不同造成的。
既有的 Java 的 FFI 框架大多称之为抽象 FFI 框架,是基于 JNI 之上提供一些类型支持,主要尝试解决 FFI 的易用性差的问题。事实上,既有的一些流行工具,例如 JNA (Java Native Access) 以及 JNR (Java Native Runtime) ,实现上基于通用 JNI stub 和 dlsym,以牺牲性能来追求极致的易用性。




fastFFI 同样易用 ,和 JavaCPP 一样,fastFFI 也是能够自动生成 JNI 代码。相比 JNR 和 JNA,fastFFI 额外需要的步骤是调用 C++ 编译器编译其自动生成的 JNI 代码。fastFFI 提供一个工具(正在内部测试中),可以自动的从 C++ 头文件中抽取 C++ 的各种定义,生成对应的 Java Binding。
在 fastFFI 中,一个 binding 是一个用来映射一个 C++ 类型定义的 Java 接口 。由于接口不能被执行,因此 fastFFI 会自动生成一个 binding 类,通过在 binding 类里定义 native 方法来通过 JNI 访问 C++ 的函数。这里,我们采用 Java 接口是为了最大化的保留灵活性,未来 JVM 若提出新的 FFI 接口,fastFFI 可以无缝切换,只要在生成 binding 类的时候做相应的适配即可。 采用 fastFFI 用户可以省去开发 Java Native 方法和对应的 JNI 函数的步骤。




我们认为,在现代的云部署环境下,Java 跨平台优势已经不重要,目前的云基础设施可以自动的完成对 Java 以及生成的 JNI 代码的编译、构建、打包和部署。此外, fastFFI 和 JavaCPP 都能支持 C++ ,而 JNR 和 JNA 只能支持对 C 构建出的动态链接库的支持,C++ 开发的库需要额外提供一套 CAPI。
顾名思义,fastFFI 相比既有的 FFI 工具和框架, 其一大特点是快 。由于是基于生成的 JNI 代码,最坏情况下,fastFFI 的其性能和人工开发的 JNI 一致。然而,fastFFI 青出于蓝而胜于蓝,fastFFI 能提供比 JNI 更快的访问 C++ 数据的效率, 这得益于 fastFFI 内置一个工具 llvm4jni ,可以将 LLVM Bitcode 转成 Java Bytecode,打破 JVM 的 JIT 编译器和 JNI 代码的 C++ 编译器之间的屏障。
我们说 fastFFI 是一个现代的 FFI 框架,这是由于,相比既有的任何 FFI 框架, fastFFI 是唯一可以将 C++ 模板映射到 Java 泛型的框架 。通过 fastFFI 定义一些基本的类型信息,fastFFI 的代码生成器通过类型推导,可以为一个 C++ 模板的每一个实例化生成一个对应的 binding 类。这个 binding 类还可以用于在传参时做类型检查,用于在 Java 端尽早发现一些类型错误。此外,我们还尽可能的利用 Java 的语言结构映射 C++ 的语言结构。例如,fastFFI 采用的是 binding 接口而不是 binding 类,能够通过 Java 接口的多继承支持 C++ 的多继承。一些 Java 和 C++ 共同的高级的语言结构,例如异常处理和 Lambda,也在开发支持中。
应用场景
向 Java 中引入优秀的 C++ 库
fastFFI 的 第一个应用场景 是让 Java 用户享受 C++ 的高质量框架,这里以 JSON 解析为例。下图是一个典型的 JSON 解析的场景,一个用户通过 JSON 的 parser 将输入的 JSON 文件或者字节流转成 DOM 对象,之后将 DOM 对象转换成最终的用户对象。 因此整个流程分为两个部分:解析和转换




我们用 simdjson (链接见文末) 和 Jackson 比较。simdjson 是一个 C++ 开发的现代 JSON 解析器,我们用 fastFFI 为其创建了一个 Java SDK,而 Jackson 则是由 Java 开发的 JSON 解析框架。我们采用 simdjson 中的四个 benchmark 中比较,从中可以看出, simdjson 的解析性能远远大于 Jackson ,虽然通过 FFI 来转换 DOM 对象相比 Java 来转换 DOM 对象有些性能差异。因此,在实际的使用中,尤其针对比较大的 JSON 文件,完全可以采用 simdjson+fastFFI 的方式来处理。这里由于解析是一次复杂 JNI 调用,llvm4jni 无法优化,因此 fastFFI 和 JNI 的性能差异并不明显,仅仅略有优势。




因为不开启 llvm4jni,fastFFI 性能和 JNI 等价 。本文所有表格的表头中,JNI 指的是采用 fastFFI 开发的 Java SDK,且未开启 llvm4jni,而 fastFFI 则指的是采用 fastFFI 开发的 Java SDK 且打开了 llvm4jni。
大数据
fastFFI 一大重要场景就是在大数据领域替代既有的 in-memory 跨平台数据格式 (例如 Apache Arrow 和 Google FlatBuffers)。在大数据领域,一个应用一般分为数据管理平台和数据处理算法两部分。对于数据管理平台,一般用 C++ 开发更合适,可以得到灵活的内存管理,而是对于数据处理算法,用高级程序设计语言开发效率更高。
我们以 Alibaba 的图计算引擎  Grape(链接见文末) 为例,通过使用 fastFFI 创建的 Java SDK 可以显著的减少 JNI 带来的性能问题。
我们选取四种算法, BFS、SSSP、PageRank、以及 WCC ,用 C++ 和 Java 分别基于 Grape C++ 和 Java API 实现。如下表所示,尤其以 SSPS 和 PageRank 为例,虽然 fastFFI 相比 C++ 的实现仍然有不少的性能差距,但是其已经大幅显著的缩小 JNI 的开销,为在 Grape 上利用 Java 开发算法提出了可能。




跨语言数据共享
fastFFI 起初是为了在跨语言调用中取代跨平台内存数据格式(例如,Apache Arrow和Google FlatBuffers)。我们认为,在 Java 和 C++ 互相调用的程序中,最优秀的内存数据格式就是 C++ 对象,C++ 代码可以高效灵活的访问和创建这些对象。然而,C++ 对象在内存中的布局在 Java 中并不能轻易获得,Java 因此无法快速的访问 C++ 对象的成员。
为了解决这些难题,fastFFI 可以根据 C++ 的对象定义生成对应的 Java binding 接口以及相应的 binding 类和 JNI 代码,这样 Java 就可以通过 fastFFI 访问 C++ 的成员。为了消除 JNI 的开销,我们可以通过 llvm4jni 将对成员访问的 JNI 函数转成 Java 字节码方法,这样 JIT 编译器就可以优化这些 JNI 函数。换句话说,fastFFI 借助 C++ 编译器(clang)生成对 C++ 对象的成员访问 LLVM Bitcode 代码,这些 Bitcode 代码蕴含了对 C++ 对象的访问以及布局信息。借助 llvm4jni,我们将这些信息通过 Java Bytecode 编码,最终通过 JIT 实现高效的对 C++ 成员的访问。
为了移除开发 C++ 数据类型的需求,fastFFI 提供一些 Annotation 可以支持从一个 Java 接口自动的生成 C++ 对象的定义,该机制称为 fastFFI mirror。
此外,一些跨平台的数据格式都会同时提供 C++ 和 Java 的 API,例如 Protocol Buffer 以及 FlatBuffers。根据之前的评估知道,一般直觉上而言,C++ 的 parser 要比 Java 的 parser 在大部分场景下要快。因此,我们用 fastFFI 来将 FlatBuffers 的 C++ API 创建了新的 Java SDK,称之为 FlatBuffers FFI。从表中可以,采用 fastFFI mirror(通过 C++ 对象交互)是最高效的方式。值得注意的是,C++ 对象仅适合数据是在本进程的 Java 和 C++ 中交互,如果涉及到跨平台的交互,则可能仍然需要将数据转成 FlatBuffers 的格式。不过即使如此,采用 fastFFI 包裹 C++ API 得到的 Java SDK 仍然是最为高效的。




结语
fastFFI 项目仍在不断演进中,期待更多开发者的使用和讨论。
回复

使用道具 举报

2

主题

11

帖子

19

积分

新手上路

Rank: 1

积分
19
发表于 昨天 20:47 | 显示全部楼层
发发呆,回回帖,工作结束~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|办公问答网

GMT+8, 2025-3-16 07:28 , Processed in 0.082541 second(s), 23 queries .

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc. Templated By 【未来科技 www.veikei.com】设计

快速回复 返回顶部 返回列表