网站的图片大小,淘宝自己网站怎么建设,网站建设力洋,网站后台logo简介#xff1a; 跨语言编程时现代程序语言中非常重要的一个方向#xff0c;也被广泛应用于复杂的设计与实现中。
跨语言编程是现代程序语言中非常重要的一个方向#xff0c;也被广泛应用于复杂系统的设计与实现中。本文是 GIAC 2021(全球互联网架构大会) 中关于 Alibaba …简介 跨语言编程时现代程序语言中非常重要的一个方向也被广泛应用于复杂的设计与实现中。
跨语言编程是现代程序语言中非常重要的一个方向也被广泛应用于复杂系统的设计与实现中。本文是 GIAC 2021(全球互联网架构大会) 中关于 Alibaba FFI -- 跨语言编程的探索 主题分享的内容整理。作者董登辉、顾天晓来自阿里云智能基础软件部 JVM 团队 背景
前言
无疑Java 是目前工业界最流行的应用编程语言之一。除了主流实现上OpenJDK Hotspot不俗的性能表现和成熟的研发生态Spring 等外其成功的背后离不开语言本身较低相比于 C/C的学习门槛。一个初学者可以利用现有的项目脚手架快速地搭建出一个初具规模的应用程序但也因此许多 Java 程序员对程序底层的执行原理并不熟悉。本文将探讨一个在大部分 Java 相关的研发工作中不太涉及到的技术 -- 跨语言编程。
回想起多年前第一次用 Java 在控制台上打印出 “Hello World” 时出于好奇便翻阅 JDK 源码想一探实现的究竟 在 C 语言中我们可以使用 printf 函数而 printf 在具体实现上又依赖操作系统的接口再一次次跳转后最终停留在了一个“看不到实现”的 native 方法上额。。。然后就没有然后了。
我想有不少 Java 初级程序员对 native 方法的调用机制仍一知半解毕竟在大部分研发工作中我们直接实现一个自定义 native 方法的需求并不多简单来说 native 方法是 Java 进行跨语言调用的接口这也是 Java Native Interface 规范的一部分。 Java 跨语言编程技术的应用场景
常见场景
在介绍 Java 跨语言编程技术之前首先简单地分析下实际编程过程中需要使用跨语言编程技术的场景在这里我罗列了以下四个场景
1、依赖字节码不支持的能力
换个角度看目前标准的字节码提供了哪些能力呢根据 Spec 规范已有的字节码可以实现创建 Java 对象、访问对象字段和方法、常规计算加减乘除与或非等、比较、跳转以及异常、锁操作等等但是像前言中提到的打印字符串到控制台这样的高阶功能字节码并不直接支持此外像获取当前时间分配堆外内存以及图形渲染等等字节码也都不支持我们很难写出一个纯粹的 Java 方法组合这些字节码来实现这类能力因为这些功能往往需要和系统资源产生交互。在这些场景下我们就需要使用跨语言编程技术通过其他语言的实现来集成这些功能。
2、使用系统级语言C、C、Assembly实现系统的关键路径
不需要显示释放对象是 Java 语言学习门槛低的原因之一但因此也引入了 GC 的机制来清理程序中不再需要的对象。在主流 JVM 实现中GC 会使得应用整体暂停影响系统整体性能包括响应与吞吐。
所以相对于 C/C Java 虽然减轻了程序员的研发负担提高了产品研发效率但也引入了运行时的开销。Software engineering is largely the art of balancing competing trade-offs. 当系统关键路径上的核心部分比如一些复杂算法使用 Java 实现会出现性能不稳定的情况下可以尝试使用相对底层的编程语言来实现这部分逻辑以达到性能稳定以及较低的资源消耗目的。
3、其他语言实现调用 Java
这个场景可能给大部分 Java 程序员的第一感觉是几乎没有遇到过但事实上我们几乎每天都在经历这样的场景。
举个例子通过 java Main-Class 跑一个 Java 程序就会经过 C 语言调用 Java 语言的过程后文还会对此做提及。
4、历史遗留库
公司内部或者开源实现中存在一些 C/C 写的高性能库用 Java 重写以及后期维护的成本非常大。当 Java 应用需要使用这些库提供的能力时我们需要借助跨语言编程技术来复用。
Alibaba Grape
再简单谈谈阿里内部的一个场景Alibaba Grape 项目也是在跨语言编程技术方向上与我们团队合作的第一个业务方。
Grape 本身是一个并行图计算框架的开源项目相关论文获得了 ACM SIGMOD Best Paper Award主要使用 C 编写工程实现上应用了大量的模板特性。对 Grape 项目感兴趣的同学可以参考 Github 上的相关文档这里不再详细介绍。
该项目在内部应用中存在很多使用 Java 作为主要编程语言的业务方因此需要开发人员把 Grape 库封装成 Java SDK 供上层的 Java 应用调用。在实践过程中遇到的两个显著问题
封装 SDK 的工作非常繁琐尤其对于像 Grape 这样依赖模板的库在初期基于手动的封装操作经常出错运行时性能远低于 C 应用
为了解决这些问题两个团队展开了合作Alibaba FFI 项目正式开始演进该项目的实现目前也主要针对 Java 调用 C 场景。 Java 跨语言编程技术
下面介绍一些在工业界中相对成熟、应用较多的 Java 跨语言调用技术。
Java Native Interface
谈到 Java 跨语言编程首先不得不提的就是 Java Native Interface简称 JNI。后面提到的 JNA/ JNR、JavaCPP 技术都会依赖 JNI。首先通过两个例子来简单回顾一下。
控制台输出的例子
Plain Text
自动换行
xxxxxxxxx1
System.out.println(hello ffi);通过 System.out 我们可以快速地实现控制台输出功能我相信会有不少好奇的同学会关心这个调用到底是如何实现输出功能的翻阅源码后我们最终会看见这样一个 native 方法 private native void writeBytes(byte b[], int off, int len, boolean append) throws
IOException;
该方法由 JDK 实现具体实现可以参考这里。
那么我们是否可以自己实现这样的功能呢答案是肯定的大致步骤如下省略了一些细节a. 首先我们定义一个 Java native 方法需要使用 native 关键字同时不提供具体的实现native 方法可以被重载
static native void myHelloFFI();b. 通过 javah 或者 javac -h JDK 10命令生成后续步骤依赖的头文件该头文件可以被 C 或者 C 程序使用
/* DO NOT EDIT THIS FILE - it is machine generated */
#include jni.h
/* Header for class HelloFFI */#ifndef _Included_HelloFFI
#define _Included_HelloFFI
#ifdef __cplusplus
extern C {
#endif
/** Class: HelloFFI* Method: myHelloFFI* Signature: ()V*/
JNIEXPORT void JNICALL Java_HelloFFI_myHelloFFI(JNIEnv *, jclass);#ifdef __cplusplus
}
#endif
#endifc. 实现头文件中的函数在这里我们直接使用 printf 函数在控制台输出“ hello ffi”
JNIEXPORT void JNICALL Java_HelloFFI_myHelloFFI(JNIEnv * env, jclass c) {printf(hello ffi);
}d. 通过 C/C 编译器gcc/llvm 等编译生成库文件
e. 使用 -Djava.library.path... 参数指定库文件路径并在运行时调用 System.loadLibrary 加载上个步骤中生成的库之后 Java 程序就可以正常调用我们自己实现的 myHelloFFI 方法了。
C 程序调用 Java 方法
上面是 Java 方法调用 C 函数的例子通过 JNI 技术我们还可以实现 C 程序中调用 Java 方法这里面会涉及到 2 个概念Invocation API 与 JNI function在下面代码示例中省略了初始化虚拟机的步骤仅给出最终实现调用的两个步骤。
// Init jvm ...// Get method idjmethodID mainID (*env)-GetStaticMethodID(env, mainClass, main, ([Ljava/lang/String;)V);/* Invoke */(*env)-CallStaticVoidMethod(env, mainClass, mainID, mainArgs);示例中首先通过 GetStaticMethodID 获取方法的 id之后通过 CallStaticVoidMethod 实现方法的调用这两个函数都是 JNI function。
前面我们提到过当我们 java Main Class 运行 Java 程序时是其他语言调用 Java 语言的场景事实上 Java 命令在实现上就是应用类似上述代码的流程完成主类 main 方法的调用。顺带提一点我们日常研发过程中常用的一些诊断命令比如 jcmd、jmap、jstack和 java 命令是同一份源码实现可以从图中看出这几个二进制文件的大小差不多只是在构建过程中使用了不同的构建参数。 那么 JNI 到底是什么呢以下是我的理解。
首先JNI 是 Java 跨语言访问的接口规范主要面向 C、C、Assembly为什么没有其他语言我个人认为是由于这几种语言在当时规范设计之初足以覆盖绝大部分场景规范本身考虑了主流虚拟机的实现hotspot但本身不和任何具体的实现绑定换句话说Java 程序中跨语言编程的部分理论上可以跑在任何实现这个规范的虚拟机上规范定义了其他语言如何访问 Java 对象、类、方法、异常如何启动虚拟机也定义了Java 程序如何调用其他语言C、C、Assembly在具体使用和实际运行效果的表现用一句话总结Powerful, but slow, hard to use, and error-proneJava Native Access Java Native Runtime
通过前面对 Java Native Interface 的介绍我们可以认识到使用 JNI 技术实现 Java 方法调用 C 语言的步骤是非常麻烦的因此为了降低 Java 跨语言编程指 Java 调用 C/C 程序的难度开源社区诞生了 Java Native AccessJNA 和 Java Native RuntimeJNR这两个项目。本质上这两个技术底层仍然是基于 JNI因此在运行时性能上不会优于 JNI。
通过 JNA/JNR 进行 C/C 程序的封装开发者就不需要主动生成或者编写底层的胶水代码快速地实现跨语言的调用。此外两者还提供了其他优化比如 Crash Protection后文会有介绍等。在实现上JNR 会动态生成一些 Stub 优化运行时的性能。
JNA/JNR 和 JNI 的关系如下入 下面是 JNR 官方给出的示例。首先创建 LibC 接口封装目标 C 函数然后调用 LibraryLoader 的 API 创建 LibC 的具体实例最后通过接口完成调用
public class HelloWorld {public interface LibC { // A representation of libC in Javaint puts(String s); // mapping of the puts function, in C int puts(const char *s);}public static void main(String[] args) {LibC libc LibraryLoader.create(LibC.class).load(c); // load the c library into the libc variablelibc.puts(Hello World!); // prints Hello World! to console}
}遗憾的是JNA 和 JNR 对 C 的支持并不友好因此在调用 C 库的场景中使用受限。
JavaCPP
The missing bridge between Java and native C
如果说 JNA/JNR 优化了 Java 调用 C 的编程体验那么 JavaCPP 的目标则是优化 Java 调用 C 的编程体验目前该项目也是工业界用得较多的SDK。
JavaCPP 已经支持大部分 C 特性比如 Overloaded operators、Class Function templates、Callback through function pointers 等。和 JNA/JNR 类似JavaCPP 底层也是基于 JNI实现上通过注解处理等机制自动生成类似的胶水代码以及一些构建脚本。
此外该项目也提供了利用 JavaCPP 实现的一些常用 C 库的 Preset如 LLVM、Caffe 等。
下面是使用 JavaCPP 封装 std::vector 的的示例
Platform(includevector)
public class VectorTest {Name(std::vectorstd::vectorvoid* )public static class PointerVectorVector extends Pointer {static { Loader.load(); }public PointerVectorVector() { allocate(); }public PointerVectorVector(long n) { allocate(n); }public PointerVectorVector(Pointer p) { super(p); } // this (vectorvectorvoid* *)p/**other methods ....*/public native Index void resize(long i, long n); // (*this)[i].resize(n)public native Index Pointer get(long i, long j); // return (*this)[i][j]public native void put(long i, long j, Pointer p); // (*this)[i][j] p}public static void main(String[] args) {PointerVectorVector v new PointerVectorVector(13);v.resize(0, 42); // v[0].resize(42)Pointer p new Pointer() { { address 0xDEADBEEFL; } };v.put(0, 0, p); // v[0][0] pPointerVectorVector v2 new PointerVectorVector().put(v);Pointer p2 v2.get(0).get(); // p2 *(v[0][0])System.out.println(v2.size() v2.size(0) p2);v2.at(42);}
}Graal Panama
Graal 和 Panama 是目前两个相对活跃的社区项目与跨语言编程有直接的联系。但这两项技术还未在生产环境中大规模使用验证在这里不做具体的描述有机会的话会单独介绍这两个项目。
FBJNI
FBJNIhttps://github.com/facebookincubator/fbjni是 Facebook 开源的一套辅助 C 开发人员使用 JNI 的框架。前面提到的大多是如何让Java用户快速的访问Native方法实际在跨语言调用场景下也存在 C 用户需要安全便捷的访问 Java 代码的场景。Alibaba FFI 目前关注的是如何让 Java 快速的访问 C例如假设一个需求是让 C 用户访问 Java 的 List 接口那么 Alibaba FFI的做法是与其通过 JNI 接口函数来操作 Java 的 List 对象不如将 C的 std::vector 通过 FFI 包转成 Java 接口。
JNI 的开销
内联
JVM 高性能的最核心原因是内置了强大的及时编译器Just in time简称 JIT。JIT 会将运行过程中的热点方法编译成可执行代码使得这些方法可以直接运行避免了解释字节码执行。在编译过程中应用了许多优化技术内联便是其中最重要的优化之一。简单来说内联是把被调用方法的执行逻辑嵌入到调用者的逻辑中这样不仅可以消除方法调用带来的开销同时能够进行更多的程序优化。
但是在目前 hotspot 的实现中JIT 仅支持Java 方法的内联所以如果一个 Java 方法调用了 native 方法则无法对这个 native 方法应用内联优化。 说到这里肯定有人疑惑难道我们经常使用的一些 native 方法比如 System.currentTimeMillis没有办法被内联吗实际上针对这些在应用中会被经常使用的 native 方法hotspot 会使用 Intrinsic 技术来提高调用性能非 native 方法也可以被 Intrinsic。个人认为 Intrinsic 有点类似 build-in 的概念当 JIT 遇到这类方法调用时能够在最终生成的代码中直接嵌入方法的实现不过方法的 Intrinsic 支持通常需要直接修改 JVM。
参数传递
JNI 的另一个开销是参数传递包括返回值。由于不同语言的方法/函数的调用规约Calling Convention不同因此在 Java 方法在调用 native 方法 的时候需要涉及到参数传递的过程如下图针对 x64 平台 根据 JNI 规范JVM 首先需要把 JNIEnv* 放入第一个参数寄存器rdi中然后把剩下的几个参数包括 thisreceiver分别放入相应的寄存器中。为了让这一过程尽可能地快 hotspot 内部会根据方法签名动态生成转换过程的高效 stub。
状态切换
从 Java 方法进入 native 方法以及 native 方法执行完成并返回到 Java 方法的过程中会涉及到状态切换。如下图 在实现上状态切换需要引入 memory barrier 以及 safepoint check。
对象访问
JNI 的另一个开销存在于 native 方法中访问 Java 对象。
设想一下我们需要在一个 C 函数中访问一个 Java 对象最暴力的方式是直接获取对象的指针然后访问。但是由于 GC 的存在Java 对象可能会被移动因此需要一个机制让 native 方法中访问对象的逻辑与地址无关。All problems in CS can be solved by another level of indirection在具体的实现上通过增加一个简介层 JNI Handle同时使用 JNI Functions 进行对象的访问来解决这个问题当然这个方案也势必引入了开销。 通过前面的介绍我们知道现在主流的 Java 跨语言编程技术主要存在两个问题
1、编程难度
2、跨语言通信的开销
针对问题 1我们可以利用 JNA/JNR 、JavaCPP 这样技术来解决。那么针对问题 2我们有相应的优化方案么
下面正式介绍 Alibaba FFI 项目
Alibaba FFI
概览
Alibaba FFI 项目致力于解决 Java 跨语言编程中遇到的问题从整体上看项目分为以下两个模块
a. FFI 解决编程难度问题
一套 Java 注解和类型包含一个注解处理器用于生成胶水代码运行时支持
b. LLVM4JNI解决运行时开销问题
实现 bitcode 到 bytecode 的翻译打破 Java 方法与 Native 函数的边界基于FFI的纯 Java 接口定义底层依赖 LLVM通过 FFI 访问 LLVM 的 C API
目前 Alibaba FFI 主要针对 C 下文也主要以 C 作为目标通信语言。
通过 Alibaba FFI 进行跨语言编程的 workflow
1、包含用户需要使用的 C API 声明的头文件
2、用 Java 语言封装 C API目前这个步骤仍需要手动进行在未来我们会提供 SDK用户仅需手动编写配置文件即可生成这部分代码
3、通过 FFI 中的注解处理器生成的胶水代码包括 Java 层和 native 层的代码
4、库的具体实现
5、Clinet 应用在运行阶段会 load 上述过程的产物
注实线表示运行前阶段源码与产物之间的关系虚线表示运行阶段应用与库和产物之间的关系 FFI
FFI 模块提供了一套注解和类型用于封装其他语言的接口可以在下图中看到最顶层是一个 FFITypeFFI - Foreign function interface接口。在面向 C 的具体实现中一个底层的 C 对象会映射到一个 Java 对象因此需要在Java 对象中包含 C 对象的地址。由于 C 对象不会被移动所以我们可以在 Java 对象中直接保存裸指针。 类层次图不完整本质上 FFI 模块是通过注解处理器生成跨语言调用中需要的相关代码用户仅需要依赖 FFI 的相关库插件并用 FFI 提供的 api 封装需要调用的目标函数即可。
示例
下面是一个封装 std::vector 的过程。
a. 通过注解和类型封装需要调用的底层函数
FFIGen(library stdcxx-demo)
CXXHead(system {vector, string})
FFITypeAlias(std::vector)
CXXTemplate(cxxjint, javaInteger)
CXXTemplate(cxxjbyte, javaByte)
public interface StdVectorE extends CXXPointer {FFIFactoryinterface FactoryE {StdVectorE create();}long size();CXXOperator([]) CXXReference E get(long index);CXXOperator([]) void set(long index, CXXReference E value);void push_back(CXXValue E e);long capacity();void reserve(long size);void resize(long size);
}
FFIGen指定最终生成库的名称
CXXHead胶水代码中依赖的头文件
FFITypeAliasC 的类名
CXXTemplate实现 C 模板参数具体类型到 Java 类型的映射相对于 JavaCPPAlibaba FFI 提供了更灵活的配置 b. 编译过程中注解处理器会生成最终调用过程中依赖的组件接口的真实实现 public class StdVector_cxx_0x6b0caae2 extends FFIPointerImpl implements StdVectorByte {static {FFITypeFactory.loadNativeLibrary(StdVector_cxx_0x6b0caae2.class, stdcxx-demo);}public StdVector_cxx_0x6b0caae2(final long address) {super(address);}public long capacity() {return nativeCapacity(address);}public static native long nativeCapacity(long ptr);...public long size() {return nativeSize(address);}public static native long nativeSize(long ptr);}
JNI 的胶水代码
#include jni.h
#include new
#include vector
#include string
#include stdcxx_demo.h#ifdef __cplusplus
extern C {
#endifJNIEXPORT
jbyte JNICALL Java_com_alibaba_ffi_samples_StdVector_1cxx_10x6b0caae2_nativeGet(JNIEnv* env, jclass cls, jlong ptr, jlong arg0 /* index0 */) {return (jbyte)((*reinterpret_caststd::vectorjbyte*(ptr))[arg0]);
}JNIEXPORT
jlong JNICALL Java_com_alibaba_ffi_samples_StdVector_1cxx_10x6b0caae2_nativeSize(JNIEnv* env, jclass cls, jlong ptr) {return (jlong)(reinterpret_caststd::vectorjbyte*(ptr)-size());
}......#ifdef __cplusplus
}
#endif
Crash Protection
在演进过程中我们引入了一些优化机制比如针对 C 函数返回临时对象的处理、异常的转换等。在这里介绍一下 Crash Protection也是针对客户在实际场景遇到的问题的解决方案在 JNA 和 JNR 中也有相应的处理。有时候Java 应用依赖的 C 库需要进行版本升级为了防止 C 库中的 Bug 导致整个应用 Crash对于 Java 中的 Bug 通常会表现为异常多数情况下不会导致应用整体出现问题我们需要引入保护机制。如下
JNIEXPORT void JNICALL Java_Demo_crash(JNIEnv* env, jclass) {void* addr 0;*(int*)addr 0; // (Crash)
}
在第 3 行会出现内存访问越界的问题如果不做特殊处理应用会 Crash。为了”隔离“这个问题我们引入在保护机制以下是 Linux 上的实现
PROTECTION_START // 生成胶水代码中插入宏
void* addr 0;
*(int*)addr 0;
PROTECTION_END // 宏// 宏展开后的实现如下// Pseudo code
// register signal handlers
signal(sig, signal_handler);int sigsetjmp_rv;
sigsetjmp_rv sigsetjmp(acquire_sigjmp_buf(), 1);
if (!sigsetjmp_rv) {void* addr 0;*(int*)addr 0;
}
release_sigjmp_buf();
// restore handler ...
if (sigsetjmp_rv) {handle_crash(env, sigsetjmp_rv);
}
通过实现自己的信号处理函数和 sigsetjmp/siglongjmp 机制来实现 Crash 的保护需要注意的是由于 Hotspot 有自定义的信号处理器safepoint checkimplicit null check 等为了防止冲突需要在启动是 preload libjsig.soLinux 上这个库。最后在 handle_crash 中我们可以抛出 Java 异常供后续排查分析。
相关项目的对比 LLVM4JNI LLVM4JNI 实现了 bitcode 到 bytecode 的翻译这样一个 Native 函数就是被转换成一个 Java 方法从而消除前面提到的一系列开销问题。翻译过程是在应用运行前完成的其核心就是将 bitcode 的语义用 bytecode 来实现本文不会介绍具体的实现细节待项目开源后做详细介绍。下面演示几例简单过程的翻译结果。1、简单的四则运算
sourceint v1 i j;int v2 i - j;int v3 i * j;int v4 i / j;return v1 v2 v3 v4;
bitcode
%5 sdiv i32 %2, %3%6 add i32 %3, 2%7 mul i32 %6, %2%8 add nsw i32 %5, %7ret i32 %8
bytecode
Code:stack2, locals6, args_size30: iload_11: iload_22: idiv3: istore_34: iload_25: ldc #193 // int 27: iadd8: iload_19: imul10: istore 512: iload_313: iload 515: iadd16: ireturn
2、JNI Functions 的转换目前已经支持 90 个。未来该功能会和fbjni等类似框架集成打破Java和Native的代码边界消除方法调用的额外开销。
source
jclass objectClass env-FindClass(“java/util/List);return env-IsInstanceOf(arg, objectClass);
bytecode
Code:stack1, locals3, args_size20: ldc #205 // class java/util/List2: astore_23: aload_14: instanceof #205 // class java/util/List7: i2b8: ireturn
3、C 对象访问。Alibaba FFI的另外一个好处是可以以面向对象的方式C是面向对象语言来开发 Java off-heap 应用。当前基于Java的大数据平台大多需要支持 off-heap 的数据模块来减轻垃圾回收的压力然而人工开发的 off-heap 模块需要小心仔细处理不同平台和架构的底层偏移和对齐容易出错且耗时。通过 Aliabba FFI我们可以采用 C开发对象模型再通过 Alibaba FFI 暴露给 Java 用户使用。
source
class Pointer {public:int _x;int _y;Pointer(): _x(0), _y(0) {}const int x() { return _x; }const int y() { return _y; }
};JNIEXPORT
jint JNICALL Java_Pointer_1cxx_10x4b57d61d_nativeX(JNIEnv*, jclass, jlong ptr) {return (jint)(reinterpret_castPointer*(ptr)-x());
}JNIEXPORT
jint JNICALL Java_Pointer_1cxx_10x4b57d61d_nativeY(JNIEnv*, jclass, jlong ptr) {return (jint)(reinterpret_castPointer*(ptr)-y());
}
bitcode
define i32 Java_Pointer_1cxx_10x4b57d61d_nativeX%4 inttoptr i64 %2 to %class.Pointer*%5 getelementptr inbounds %class.Pointer, %class.Pointer* %4, i64 0, i32 0%6 load i32, i32* %5, align 4, !tbaa !3ret i32 %6define i32 Java_Pointer_1cxx_10x4b57d61d_nativeY%4 inttoptr i64 %2 to %class.Pointer*%5 getelementptr inbounds %class.Pointer, %class.Pointer* %4, i64 0, i32 1%6 load i32, i32* %5, align 4, !tbaa !8ret i32 %6
bytecodepublic int y();descriptor: ()Iflags: ACC_PUBLICCode:stack2, locals1, args_size10: aload_01: getfield #36 // Field address:J4: invokestatic #84 // Method nativeY:(J)I7: ireturnLineNumberTable:line 70: 0public static int nativeY(long);descriptor: (J)Iflags: ACC_PUBLIC, ACC_STATICCode:stack4, locals2, args_size10: lload_01: ldc2_w #85 // long 4l4: ladd5: invokestatic #80 // Method com/alibaba/llvm4jni/runtime/JavaRuntime.getInt:(J)I8: ireturn
JavaRuntime
public class JavaRuntime {public static final Unsafe UNSAFE;...public static int getInt(long address) {return UNSAFE.getInt(address);}...
}
在访问 C 对象的字段实现中我们使用 Unsafe API 完成堆外内存的直接访问从而避免了 Native 方法的调用。
性能数据
Grape 在应用 Alibaba FFI 实现的 SSSP单源最短路径算法的性能数据如下 这里比较三种模式
纯粹的 C 实现基于 Aibaba FFI 的 Java 实现但是关闭 LLVM4JNIJNI 的额外开销没有任何消除基于 Alibaba FFI 的 Java 实现同时开启 LLVM4JNI一些 native 方法的额外开销被消除
这里我们以算法完成的时间Job Time为指标将最终结果以 C 的计算时间为单位一做归一化处理。
结语
跨语言编程是现代编程语言的一个重要方向在社区中存在许多方案来实现针对不同语言的通信过程。Alibaba FFI 目前主要针对 C在未来我们会尝试 Java 与其他语言通信过程的实现与优化项目也会正式开源欢迎大家持续关注。
原文链接 本文为阿里云原创内容未经允许不得转载。