使用 Python 创建原生可执行文件

GraalPy 支持 GraalVM Native Image,用于生成使用 GraalPy 的 Java 应用程序的原生二进制文件。

快速入门 #

如果您从 Maven 原型 开始,生成的 pom.xml 使得使用 Maven 插件进行 Native Image 构建 生成原生可执行文件变得容易。

要构建应用程序,请运行

mvn -Pnative package

该命令将打包项目并创建一个原生可执行文件。

查看生成的 pom.xmlMain.java 文件。它们有文档说明如何将 Python 资源包含在生成的二进制文件中。生成的项目应被视为起点。它包含了整个 Python 标准库,因此 Python 代码可以调用所有标准库代码。可以手动修剪资源,将包含的 Python 库缩减到必要数量,从而减小包大小并缩短启动时间。此 Java 示例演示了 Python 上下文的一些有用的默认选项,但其他设置可能需要进一步控制 Python 代码允许执行的操作。

减小二进制文件大小 #

Python 是一种大型语言。“自带电池”长期以来一直是 CPython 的核心原则。作为一种兼容的替代方案,GraalPy 也包含了大多数这些“电池”。这会导致在 Java 应用程序中包含 GraalPy 时二进制文件大小显著增加。

只有您作为开发人员知道您特定的嵌入场景。默认值可能包含比任何特定应用程序都多得多的内容。嵌入式 Python-in-Java 应用程序通常对 Python 解释器的用例比完整的 GraalPy 分发版更有限,而且您通常可以提前知道是否需要某些功能。某些功能(例如,加密算法或套接字访问)在某些情况下甚至可能是不希望包含的。因此,在将 GraalPy 嵌入到 Java 应用程序时,可以通过排除 Python 语言的组件来减小二进制文件大小并提高安全性。

排除 Python 组件 #

GraalPy 定义了一些系统属性,这些属性可以在 native-image 命令行上传递,以排除语言的某些方面。这些选项,当一起使用时,可以将可执行文件的大小减少约 20%。这些是

  • python.WithoutSSL=true - 此选项将删除 ssl 模块。如果不需要安全网络访问或证书检查,这将删除 Java 的 SSL 类和 BouncyCastle 库。
  • python.WithoutDigest=true - 此选项将删除 _md5_sha1_sha256_sha512_sha3_hashlib 模块。这将删除 GraalPy 中对 java.security.MessageDigestjavax.crypto.Mac 的直接使用。
  • python.WithoutPlatformAccess=true - 这将删除 signalsubprocess 模块,删除对进程属性(如 Unix UID 和 GID)的访问,或设置 Java 默认时区。这不会对二进制文件大小产生重大影响,但如果这些是不希望有的功能,并且无论如何都使用上下文选项动态禁用它们,那么也可以提前删除它们。
  • python.WithoutCompressionLibraries=true - 此选项将删除 zliblzmabzip2zipimporter 模块以及相关的类。这些模块既有原生实现,也有纯 Java 实现(前者用于性能,后者用于更好的沙盒);但是,如果不需要这些模块,可以完全删除它们。
  • python.WithoutNativePosix=true - 当 GraalPy 嵌入而不是通过其启动器运行时,默认的 os 模块后端是纯 Java 实现。GraalPy 的原生 POSIX 后端仅推荐用于与 CPython 的 POSIX 接口完全兼容,如果未使用,可以通过此选项从构建中删除。
  • python.WithoutJavaInet=true - Python 的 socket 模块的 Java 实现基于 Java 的网络类。如果嵌入场景禁止网络访问,此选项可以进一步减小二进制文件大小。
  • python.AutomaticAsyncActions=false - 信号处理、Python 弱引用回调以及清理原生资源通常是通过生成 GraalPy 守护线程来自动实现的,这些守护线程向 Python 主线程提交安全点操作。这使用一个带有线程池的 ExecutorService。如果您想不允许使用这些额外的线程或避免引入 ExecutorService 及其相关类,则将此属性设置为 false 并从上下文的 polyglot 绑定中检索 PollPythonAsyncActions 对象。该对象是可执行的,可以用于在您希望的位置触发 Python 异步操作。
  • python.WithoutJNI=true - 此选项将删除任何使用 JNI 的代码。因此,您不能使用 HPy JNI 后端,也可能不能使用其他依赖 JNI 的部分。

移除预初始化的 Python 堆 #

另一个减少原生可执行文件大小的有用选项是省略可执行文件中的预初始化 Python 上下文。默认情况下,默认 Python 上下文已经预初始化并准备立即执行。这会导致启动速度略有提高,但代价是在二进制文件中包含几千个 Python 对象。在使用自定义 polyglot 引擎允许上下文共享的嵌入式应用程序中,预初始化的上下文根本无法使用,并且包含这些对象是浪费的。可以通过将以下内容传递到 native-image 命令来省略预初始化的堆

-Dimage-build-time.PreinitializeContexts=

禁用 Python 代码的运行时编译 #

如果二进制文件大小比执行速度重要得多(如果预计所有 Python 脚本都运行时间短,执行大量 I/O,或脚本很少执行一次以上,则可能如此),则可能需要完全禁用 JIT 编译。请注意,这可能会严重影响您的 Python 性能,因此在选择使用此选项时,请务必测试实际用例的运行时行为。可以通过传递以下选项来实现这一点

-Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime \
-Dpolyglot.engine.WarnInterpreterOnly=false

摘要 #

结合所有这些方法可以将 GraalPy 二进制文件的大小减半。每个嵌入式应用程序都是不同的,并且 Java 代码的其他部分所引入的代码也很重要,因此应尝试这些选项的组合,以确定它们在特定情况下会产生什么影响。

发布 Python 包 #

我们的 Maven 原型默认设置为将所有必需的 Python 文件包含在原生二进制文件中,因此映像是自包含的。

在自定义嵌入中,Python 标准库会复制到原生映像旁边。移动原生映像时,需要将标准库文件夹保留在它旁边。

联系我们