Gluten 本地库加载原理

简介

gluten以来底层的向量化计算引擎,向量化计算需要依赖操作系统的底层指令,所以这些引擎都是c++开发的。gluten通过jni方法加载和调用c++程序,这些c++都是封装成library格式的,一般在linux环境中,是以so 文件格式来存储的。

gluten加载so文件,支持两种方式。从系统目录自动加载,或者从jar包加载。选择方式由spark.gluten.loadLibFromJar配置项决定。

对于第一种系统加载方式,只需要将需要的so文件放到jvm system property java.library.path指定的路径里即可,jvm会自动从这些路径里寻找和加载对应的so文件。以ubuntu 22.04为例,java.library.path的路径为/usr/java/packages/lib/amd64\:/usr/lib/x86_64-linux-gnu/jni\:/lib/x86_64-linux-gnu\:/usr/lib/x86_64-linux-gnu\:/usr/lib/jni\:/lib\:/usr/lib

对于第二种方式,它和系统的隔离性会更好。so文件都被打包到一个jar里,gluten会从jar包寻找和动态加载。这样就不会污染java.library.path的系统目录。

动态加载

glutenjar包动态加载so文件的流程如下:

flowchart LR
so[so文件]
so --打包 --> jar[jar包]
jar --getResource --> classloader
classloader --write --> temp_file[临时 so 文件]
temp_file -- System.load --> jvm

so文件都被打包为jar,然后被当作resource file被读取,并且写入到临时目录中. 后面再通过System.load方法,加载临时目录的so文件。

运行错误

当运行spark gluten时,会报错FileNotFoundException,显示相关的so文件找不到,我们就可以检查jar包是否存在。 如果不存在,则需要重新打包,将缺失的so文件添加进去。

1
2
cd gluten-library/  
jar cvf gluten-thirdparty-lib-$LINUX_OS-$VERSION.jar ./

源码实现

velox向量化引擎为例,VeloxSharedlibraryLoader定义了需要加载哪些so文件的接口。它支持多种操作系统,不同操作系统需要的so文件会有差异。VeloxSharedlibraryLoader的每个子类对应着一个操作系统,以VeloxSharedlibraryLoaderUbuntu2204为例,它对应ubuntu 22.04版本的系统。

classDiagram
class VeloxSharedlibraryLoader {
	<<interface>>
	+ loadLib(JniLibLoader loader)
}

class VeloxSharedlibraryLoaderCentos7
class VeloxSharedlibraryLoaderCentos8
class VeloxSharedlibraryLoaderUbuntu2004
class VeloxSharedlibraryLoaderUbuntu2204
VeloxSharedlibraryLoader <|-- VeloxSharedlibraryLoaderCentos7
VeloxSharedlibraryLoader <|-- VeloxSharedlibraryLoaderCentos8
VeloxSharedlibraryLoader <|-- VeloxSharedlibraryLoaderUbuntu2004
VeloxSharedlibraryLoader <|-- VeloxSharedlibraryLoaderUbuntu2204

上述接口只是定义了要加载哪些类,负责加载的逻辑由JniLibLoader负责。使用方法如下

1
2
3
4
jniLibLoader.newTransaction()
  .loadAndCreateLink("libthrift-0.16.0.so", "libthrift.so", false)
  .loadAndCreateLink("libsnappy.so.1", "libsnappy.so", false)
  .commit()

newTransacation创建了JniLoadTransaction事务,它记录了需要加载的so文件集合。LoadRequest表示单个so文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class JniLoadTransaction {
	
	private final Map<String, LoadRequest> toLoad = new LinkedHashMap<>(); // ordered
	
}

private static final class LoadAction {  
	final String libName;  
	final String linkName;  
	final boolean requireUnload;  
	final File file;
}

JniLoadTransaction.commit方法实现很简单,它会遍历toLoad集合,然后调用getResourceAsStream方法读取文件内容,并且复制到指定的临时目录作。最后调用System.load方法加载临时目录的文件。

1
2
3
4
5
6
final Path libPath = Paths.get(workDir + "/" + libraryToLoad)
final File temp = new File(workDir + "/" + libraryToLoad);
InputStream is = JniLibLoader.class.getClassLoader()  
.getResourceAsStream(libraryToLoad)
Files.copy(is, temp.toPath());
System.load(temp)
updatedupdated2023-07-122023-07-12