Python 性能

执行性能 #

GraalPy 使用 GraalVM 最先进的即时 (JIT) 编译器。在 JIT 编译后,GraalPy 在官方的 Python 性能基准套件 上运行 Python 代码的速度比 CPython 快约 4 倍。

可以通过安装 pyperformance 包并在 CPython 和 GraalPy 上分别调用 pyperformance run 来运行这些基准测试。为了获得 Jython 的数据,我们调整了测试程序和基准测试,因为 Jython 缺少对 Python 3 的支持。然后通过获取工作基准测试的对交集并计算几何平均值来计算加速比。

在没有 JIT 编译器的情况下,GraalPy 目前执行纯 Python 代码的速度比 CPython 慢约 4 倍。这意味着预计运行时间非常短的脚本或在 Oracle JDK 或 OpenJDK 上没有 Graal 编译器运行的脚本会更慢。

机器学习或数据科学生态系统中的许多 Python 包包含 C 扩展代码。此代码从 GraalPy 的 JIT 编译中获益甚少,并且必须在 GraalPy 上模拟 CPython 实现细节。当涉及到许多 C 扩展时,性能会根据原生代码和 Python 代码的具体交互而有很大差异。

代码加载性能和占用空间 #

解析 Python 代码需要时间,因此在使用 GraalPy 将其他语言嵌入到 Python 中时,请注意与 代码缓存 相关的嵌入 Graal 语言的一般建议。此外,一些 Python 库在启动前需要加载大量代码才能执行任何操作。由于 Python 语言的设计,无法进行增量解析,对于某些脚本,解析器可能占用了大量运行时和内存。为了缓解这种情况,如果配置了适当的文件系统访问权限,GraalPy 可以将解析过程中生成的字节码缓存到 .pyc 文件中。

创建和管理 .pyc 文件 #

当存在与相应 .py 文件匹配的无效或缺失的 .pyc 文件时,GraalPy 会自动创建一个 .pyc 文件。

当 GraalPy 在执行过程中第一次导入 Python 源文件(模块)时,它会自动创建一个相应的 .pyc 文件。如果 GraalPy 再次导入同一个模块,则它会使用现有的 .pyc 文件。这意味着对于尚未执行(导入)的源文件,没有 .pyc 文件。GraalPy 完全通过 文件系统 API 创建 .pyc 文件,以便具有嵌入式 Python 代码的 Java 应用程序可以管理文件系统访问权限。

注意:GraalPy 从不删除 .pyc 文件。

每次 GraalPy 随后执行脚本时,它都会重用现有的 .pyc 文件,或创建一个新的 .pyc 文件。如果原始源文件的日期戳或哈希码发生更改,GraalPy 会重新创建 .pyc 文件。GraalPy 通过调用 source.hashCode() 仅根据 Python 源文件生成哈希码,该哈希码是使用 java.util.Arrays.hashCode(byte[]) 计算的源文件字节数组上的 JDK 哈希码。

如果 Python 解析器中的魔数发生更改,GraalPy 也会重新创建 .pyc 文件。魔数在 Python 的源代码中是硬编码的,用户无法更改(除非用户当然可以访问 Python 的字节码)。

GraalPy 开发人员在字节码格式发生更改时更改魔数。这是一个实现细节,因此魔数不必与 GraalPy 的版本相对应(如 CPython 中那样)。pyc 的魔数是运行的实际 Python 运行时 Java 代码的函数。魔数的更改将在发行说明中进行说明,以便开发人员或系统管理员可以在升级时删除旧的 .pyc 文件。

请注意,如果您使用 .pyc 文件,则必须至少在切换版本或修改原始源代码文件时允许 GraalPy 进行写访问。否则,源代码文件的重新生成将失败,每次导入都将有访问每个旧的 .pyc 文件、解析代码、序列化代码以及尝试(并失败)写入新 .pyc 文件的开销。

GraalPy 为 .pyc 文件创建以下目录结构

top_directory/
  __pycache__/
    sourceA.graalpy.pyc
    sourceB.graalpy.pyc
  sourceA.py
  sourceB.py
  sub_directory/
    __pycache__/
      sourceX.graalpy.pyc
    sourceX.py

默认情况下,GraalPy 在与源代码文件相同的目录级别上创建 __pycache__ 目录,并在该目录中存储来自同一目录的所有 .pyc 文件。该目录可能存储使用不同 Python 版本(包括例如 CPython)创建的 .pyc 文件,因此用户可能会看到以 .cpython3-6.pyc 结尾的文件,例如。

.pyc 文件在很大程度上由 GraalPy 以与 CPython 兼容的方式自动管理。GraalPy 提供与 CPython 类似的选项来指定 t_.pyc_ 文件的位置以及是否应写入它们,这两个选项都可以由来宾代码更改。

可以通过 与 CPython 相同的方式 控制 .pyc 文件的创建

  • GraalPy 启动器 (graalpy) 读取 PYTHONDONTWRITEBYTECODE 环境变量。如果将其设置为非空字符串,则 Python 不会尝试在导入模块时创建 .pyc 文件。
  • 如果给出启动器命令行选项 -B,则它与上述效果相同。
  • 来宾语言代码可以在运行时更改 sys 内置模块的 dont_write_bytecode 属性以更改后续导入的行为。
  • GraalPy 启动器读取 PYTHONPYCACHEPREFIX 环境变量。如果设置,它将在指定的前缀路径上创建 __pycache__ 目录,并根据需要创建源树目录结构的镜像以存储 .pyc 文件。
  • 来宾语言代码可以在运行时更改 sys 模块的 pycache_prefix 属性以更改后续导入的位置。

由于开发人员无法使用环境变量或 CPython 选项将这些选项传达给 GraalPy,因此这些选项作为语言选项提供

  • python.DontWriteBytecodeFlag - 等效于 -BPYTHONDONTWRITEBYTECODE
  • python.PyCachePrefix - 等效于 PYTHONPYCACHEPREFIX

请注意,Python 上下文默认情况下不会启用写入 .pyc 文件。GraalPy 启动器默认情况下启用它,但如果在嵌入式用例中需要这样做,应注意确保正确管理 __pycache__ 位置,并以与从中派生的源代码文件 (.py) 相同的方式保护该位置中的文件免遭篡改。

还要注意,要将应用程序源代码升级到 Oracle GraalPy,开发人员必须根据需要删除旧的 .pyc 文件。

安全注意事项 #

GraalPy 通过 文件系统 API 执行所有文件操作(获取数据、日期戳和写入 .pyc 文件)。开发人员可以通过自定义(例如只读)FileSystem 实现来修改所有这些操作。开发人员还可以通过禁用 GraalPy 的 I/O 权限来有效地禁用 .pyc 文件的创建。

如果 .pyc 文件不可读,则它们的位置不可写。如果 .pyc 文件的序列化数据或魔数以任何方式损坏,则反序列化将失败,GraalPy 将再次解析 .py 源代码文件。这只会对模块的解析造成轻微的性能影响,对于大多数应用程序来说,这应该不会造成重大影响(前提是应用程序除了加载 Python 代码之外还执行实际工作)。

联系我们