vlambda博客
学习文章列表

读书笔记《supercharge-your-applications-with-graalvm》第 7 章 GraalVM Polyglot – JavaScript 和 Node.js

Chapter 8: GraalVM Polyglot – Java on Truffle, Python, and R

在上一章中,我们介绍了 JavaScript 和 Node.js 解释器以及语言之间的互操作性。在本章中,我们将介绍其他语言实现,例如:

  • Truffle 上的 Java(也称为 Espresso):Truffle 上的 Java 实现
  • GraalPython:Python 语言解释器实现
  • FastR:R语言解释器实现

所有这些语言实现仍处于实验阶段,因此在本书编写时尚未发布用于生产。但是,我们将探索这些特性并构建一些代码来理解各种概念。

在本章中,我们将介绍以下主题:

  • 了解 Python、R 和 Java/Truffle 解释器
  • 学习和探索语言互操作性
  • 了解这些不同语言解释器的兼容性和局限性

在本章结束时,您将获得使用 Python、R 和 Java/Truffle 解释器构建多语言应用程序的实践经验。

Technical requirements

本章需要以下内容以及各种编码/动手部分:

Understanding Espresso (Java on Truffle)

GraalVM 21.0 是一个主要版本,它引入了一种名为 Java on Truffle 的新客户语言运行时。在此之前,我们可以选择使用 HotSpot 运行 Java(我们第 2 章JIT、Hotspot 和 GraalJIT) ,关于 Graal JIT(我们在 第 4 章Graal Just -In-Time Compiler),或作为带有 Graal AOT 的原生镜像(我们在 Chapter 5 Graal Ahead-of-Time 编译器和原生图像)。在 GraalVM 21.0 中,Truffle 上的 Java 是新的运行时,可以运行 Java。它的代号为 Espresso。这仍处于实验阶段,在编写本书时还没有准备好生产。在本节中,我们将了解如何使用这个新的运行时运行 Java 应用程序,以及它如何帮助多语言编程。

Espresso 是 JVM 的精简版,但实现了 JVM 的所有核心组件,例如字节码解释器、字节码验证器、Java 原生接口、Java 调试线协议等 开。 Espresso 重用了 GraalVM 的所有类和本地库。 Espresso 实现了 JRE (Java Runtime Environment) 库 libjvm。所以 API。下图显示了 Espresso 堆栈架构:

读书笔记《supercharge-your-applications-with-graalvm》第 7 章 GraalVM Polyglot – JavaScript 和 Node.js

图 8.1 – Espresso 堆栈架构

该图显示了 Espresso 如何在 Truffle 之上实现。

Why do we need Java on Java?

在 Truffle (Espresso) 上运行 Java 是违反直觉的,您可能想知道在 Truffle 上运行 Java 的优势,这在 Graal 之上添加了一个附加层。以下是运行 Espresso 的一些优势:

  • 运行时/调试时热交换方法、lambda 和访问修饰符:Espresso 提供了一种在运行时调试期间热交换方法、lambda 和访问修饰符的方法。这对开发人员来说是一个很棒的功能,因为它允许他们在调试时完全更改代码,并且无需停止运行时和重新编译,更改会在运行时生效。这加快了开发人员的工作流程并提高了生产力。它还可以帮助开发人员在提交代码之前进行试验和尝试。
  • 运行不受信任的 Java 代码的沙箱:Espresso 就像 Truffle 之上的沙箱一样运行,可以在访问限制下运行。通过提供特定的访问权限,这是运行不受信任的 Java 代码的好方法。请参考 Java 中的 JavaScript 嵌入代码 部分"ch10lvl1sec68">第 7 章GraalVM Polyglot - JavaScript 和 Node.js, 了解更多关于如何配置访问限制。
  • 使用相同内存空间的 JVM 和非 JVM 之间的互操作性:在 Espresso 之前,Java 应用程序和非 JVM 动态客户语言之间的数据传递不是在相同的内存空间中完成的。这可能是因为性能影响。使用 Espresso,我们可以在同一内存空间中的 Java 和 非 JVM 客户语言之间传递数据。这提高了应用程序的性能。
  • 利用 Truffle 工具和工具:在 Truffle 上运行 Java 将有助于使用使用 Truffle 工具开发的所有分析、诊断和调试工具。 (请参阅 了解 Truffle 检测部分">第 6 章Truffle 支持多语言(多语言)。)
  • 提前编译:Espresso 完全内置在 Java 中,Truffle 和 Graal 也是如此。这使得 Espresso 能够嵌入到原生图像中,就像其他 Truffle 来宾语言解释器一样。这有助于运行动态代码(例如 Reflection、JNI 等),这是构建原生镜像的限制之一(请参阅 原生镜像配置 部分a href="B16878_05_Final_SK_ePub.xhtml#_idTextAnchor099">第 5 章Graal Ahead-of-Time 编译器和原生图像 em>,了解更多详情)。我们现在可以看看分离需要这些动态特性的代码,并在 Truffle 上运行它,而代码的其他部分可以构建为 native-image
  • 运行 Java 的混合版本:Espresso 提供了运行用 Java 8 编写的 Java 代码以在 Java 11 上运行所需的隔离层。Java 8 代码可以在 Espresso 上运行,它可能在 GraalVM Java 11 上运行。这有助于在不更改旧代码的情况下运行旧代码,并且可能是对代码进行仔细现代化的一步,而不是我们从旧版本迁移时采用的大爆炸式现代化方法Java 到较新版本的 Java。

现在让我们在 Espresso 上安装和运行简单的 Java 代码。

Installing and running Espresso

Espresso 是一个 可选运行时;它必须使用 Graal Updater 工具单独下载和安装。以下是安装 Espresso 的命令:

gu install espresso

为了测试是否安装了 Espresso,让我们执行一个简单的 HelloEspresso.java 应用程序。这是一个非常简单的 Hello World 程序,它打印一条消息。查看 HelloEspresso.java 的以下代码:

public class HelloEspresso {
    public static void main(String[] args) {
        System.out.println("Hello Welcome to Espresso!!!");
    }
}

让我们使用 javac 编译这个应用程序并使用以下命令运行它:

javac HelloEspresso.java

要在 Truffle 上运行 Java,我们只需将 -truffle 作为命令行参数传递给 java。运行后,我们应该看到以下输出:

java -truffle HelloEspresso
Hello Welcome to Espresso!!!

这验证了安装。我们还可以使用 -jar 参数和 -truffle 来运行 JAR 文件。现在让我们探索 Espresso 的多语言功能。

Exploring polyglot interoperability with Espresso

Espresso 基于 Truffle 构建,并实现了 Truffle 多语言和互操作性 API。在本节中,我们将探讨这些功能。

在我们开始使用多语言功能之前,我们必须安装 Espresso 多语言功能。要安装 Espresso 多语言功能,我们可能需要下载 Espresso JAR 文件。您可以在 https://www.oracle.com/downloads/graalvm- 找到最新版本下载.html

以下屏幕截图显示了在撰写本书时我们必须下载的 JAR 文件:

读书笔记《supercharge-your-applications-with-graalvm》第 7 章 GraalVM Polyglot – JavaScript 和 Node.js

图 8.2 – Java on Truffe JAR 文件下载

下载此文件后,我们可以通过运行以下命令来安装它:

sudo gu install -L espresso-installable-svm-svmee-java11-darwin-amd64-21.0.0.2.jar

安装成功后,我们必须重建 libpolyglot 原生镜像,以包含 Espresso 库。运行多语言支持需要此库:

sudo gu rebuild-images libpolyglot -cp ${GRAALVM_HOME}/lib/graalvm/lib-espresso.jar

这将重建 libpolyglot 本机图像。我们现在准备好使用 Espresso 的多语言功能。让我们在下一节中探讨这些功能。

Exploring Espresso interoperability with other Truffle languages

正如您现在所知,Espresso 实现了 Truffle 实现框架和 com.oracle.truffle.espresso.polyglot.Polyglot类在 Espresso 中实现多语言。像任何其他来宾语言一样,我们在命令行参数中使用 -polyglot 让 Truffle 知道如何创建多语言上下文。 Espresso 将一个 Polyglot 对象注入到代码中,该对象可用于与其他语言进行互操作。让我们通过运行以下代码来探索使用 Espresso 进行多语言编程:

import com.oracle.truffle.espresso.polyglot.Polyglot;
public class EspressoPolyglot {
    public static void main(String[] args) {
        try {
            Object hello = Polyglot.eval("js",                "print('Hello from JS on Espresso');");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

让我们理解前面的代码。 Polyglot 对象为运行动态语言提供上下文。 Polyglot.eval() 方法运行外语代码。第一个参数表明它是 JavaScript 代码,第二个参数是我们想要执行的实际 JavaScript 代码。让我们使用以下命令编译此代码:

javac -cp ${GRAALVM_HOME}/languages/java/lib/polyglot.jar  EspressoPolyglot.java

在这个命令中,我们在 -cp 参数(CLASSPATH)。 polyglot.jar 包含 Espresso 的所有 polyglot 实现,包括 com.oracle.truffle.espresso.polyglot.Polyglot 导入。

现在让我们在 Espresso 上运行 Java 应用程序。我们应该传递 -truffle 参数来在 Espresso 上运行它,如果我们不这样做,它会在 Host JVM 上运行。我们可以看到以下输出:

java -truffle --polyglot EspressoPolyglot
[To redirect Truffle log output to a file use one of the following options:
* '--log.file=<path>' if the option is passed using a guest language launcher.
* '-Dpolyglot.log.file=<path>' if the option is passed using the host Java launcher.
* Configure logging using the polyglot embedding API.]
Hello from JS on Espresso

同样,我们可以调用其他语言代码。 Java 是一种类型化语言,与 Truffle 上的其他动态类型化语言不同。当我们在 Espresso(Truffle 上的 Java)和其他动态类型语言(如 JavaScript、Python 等)之间交换数据时,我们需要一种转换数据类型的方法。 polyglot 对象提供了一种使用 Polyglot.cast() 方法转换数据的方法。让我们使用一个简单的应用程序来了解如何转换数据,代码如下:

import com.oracle.truffle.espresso.polyglot.Polyglot;
import com.oracle.truffle.espresso.polyglot.Interop;

导入 PolyglotInterop 类。 Polyglot 类帮助我们运行客户语言,Interop 类实现了 Truffle 互操作性 API,它抽象了客户之间的数据类型语言。 Truffle 定义了一个互操作性协议,它提供了关于数据和消息(方法调用)如何在 Truffle 语言、工具和嵌入器之间发生交换的明确规范:

public class EspressoPolyglotCast {
    public static void main(String[] args) {
        try {
            Object stringObject = Polyglot.eval("js",                "'This is a JavaScript String'");
            Object integerObject = Polyglot.eval("js",                "1000");
            Object doubleObject = Polyglot.eval("js",                "10.12345");
            Object arrayObject = Polyglot.eval("js",                "[1234, 10.2233, 'String element',400,500,                    'Another Sttring element']");
            Object booleanObject = Polyglot.eval("js",                 "10 > 5");

在前面的代码片段中,我们正在评估 各种返回字符串、整数、双精度、整数的数组的JavaScript代码片段和一个 boolean 值。这些值被分配给一个通用的 Object,然后再转换为相应的 Java 类型 StringIntegerDoubleInteger[]布尔使用 Polyglot.cast() 的 code> 对象,如以下代码片段所示:

            String localStringObject =                Polyglot.cast(String.class, stringObject);
            Integer localIntegerObject =                Polyglot.cast(Integer.class, integerObject);
            Double localDoubleObject =                Polyglot.cast(Double.class, doubleObject);
            Boolean localBooleanObject =                Polyglot.cast(Boolean.class, booleanObject);
            System.out.println("\nString Object : "                + localStringObject                     + ", \nInteger : " + localIntegerObject                     + ", \nDouble : " + localDoubleObject                     + ", \nBoolean : " + localBooleanObject); 

接下来,我们将打印这些值。要处理数组,让我们使用 Interop 类来获取有关数组对象的信息,例如使用 Interop.getArraySize() 的数组大小,并使用 Interop.readArrayElement() 遍历数组。 Interop 还提供了一种方法来检查对象的类型并提取特定数据类型中的值。在我们的示例中,我们评估了一个包含整数、双精度和字符串对象序列的 JavaScript 数组。我们将使用 Interop.fitsInInt()Interop.fitsInDouble()Interop。 isString() 方法检查类型,并使用 Interop.asInt(), Interop.asDouble() 提取值Interop.asString() 方法。以下是代码片段:

            long sizeOfArray =                Interop.getArraySize(arrayObject);
            System.out.println(            "\n Array of objects with Size : " + sizeOfArray );
            for (int i=0; i<sizeOfArray; i++) {
                Object currentElement =                    Interop.readArrayElement
                    (arrayObject, i);
                if (Interop.fitsInInt(currentElement)) {
                    System.out.println("Integer Element: "                       +Interop.asInt(currentElement));
                }
                if (Interop.fitsInDouble(currentElement)) {
                    System.out.println("Double Element: "                        + Interop.asDouble(currentElement));
                }
                if (Interop.isString(currentElement)) {
                    System.out.println("String Element: "                        + Interop.asString(currentElement));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然后打印这些值。让我们编译 并运行这个应用程序。以下是输出:

javac -cp ${GRAALVM_HOME}/languages/java/lib/polyglot.jar  EspressoPolyglotCast.java 
espresso git:(main) java -truffle --polyglot EspressoPolyglotCast
String Object : This is a JavaScript String, 
Integer : 1000, 
Double : 10.12345, 
Boolean : true
Array of objects with Size : 6
Integer Element: 1234
Double Element: 1234.0
Double Element: 10.2233
String Element: String element
Integer Element: 400
Double Element: 400.0
Integer Element: 500
Double Element: 500.0
String Element: Another String element

在输出中,我们可以 看到动态转换语言 (JavaScript) 如何在通用 Object 中捕获,然后转换为特定的类型。我们也可以使用 Polyglot.isForeignObject(<object>) 来检查传入的对象是本地对象还是外来对象。

我们看到了如何从 Espresso 调用其他 Truffle 语言,就像使用 Context polyglot = Context.newBuilder().allowAllAccess(true).build() 调用其他语言一样使用绑定(参考 Bindings 部分">第 7 章GraalVM Polyglot - JavaScript 和 Node.js)来交换数据和调用方法。

Truffle Espresso 上的 Java 处于非常早期的版本中,在编写本书时处于实验阶段。目前存在很多限制,比如缺乏对 JVM Tool Interface 和 Java Management Extensions 的支持。此时甚至存在很多性能问题。请参考 https://www.graalvm.org/reference-manual/java -on-truffle/ 获取最新更新。

现在让我们看一下机器学习中最重要的两种语言——Python 和 R。

Understanding GraalPython – the Python Truffle interpreter

GraalVM 提供 Python 运行时。 Python 运行时兼容 3.8 版本,在编写本书时仍处于实验阶段。在本节中,我们将安装并了解 Python 如何在 Truffle 和 Graal 上运行。我们还将构建一些示例代码,以了解 Graal Python 的互操作性特性。

Installing Graal Python

Graal Python 是一个 可选运行时,默认情况下不与 GraalVM 一起安装。要下载它,您必须使用 Graal Updater 工具。以下命令下载并安装 Graal Python:

gu install python

为了验证安装,让我们运行简单的 Python 代码。以下是HelloGraalPython.py的源码:

print("Hello Graal Python")

这是一个非常简单的 Hello World 应用程序,我们在其中打印消息。让我们使用 graalpython 运行这个应用程序:

graalpython HelloGraalPython.py

当我们执行前面的命令时,我们应该会看到如下所示的输出:

graalpython HelloGraalPython.py 
Hello Graal Python

上面的输出表明应用程序正在运行,并且 graalpython 正在运行。

graalpython 也支持虚拟环境。以下命令将创建一个虚拟环境:

graalpython -m venv <name-of-virtual-env>

此命令将创建一个虚拟环境目录,该目录将是一个隔离环境。 GraalPython 还附带 ginstall,这是一个安装支持库的工具。以下命令将为 graalpython 安装 numpypip 也可用于安装库:

graalpython -m ginstall install numpy

现在让我们了解 GraalPython 编译和解释器管道的工作原理。

Understanding the graalpython compilation and interpreter pipeline

Graalpython 是一个稍微不同的编译/解释器管道。为了 提高解析性能,Graalpython 使用了一个中间的 表示,称为简单语法树 SST)和作用域树(ST)。 SST 是 源文件的更简单表示,镜像源。通常,当 SST 转换为 AST 时,SST 中的一个节点可能会转换为 AST 中的多个节点。 ST 捕获变量和函数的范围信息。在解析之后,SST 和 ST 一起被序列化为 .pyc 文件。这样做是为了加快解析速度。下次我们运行 Python 程序时,Graalpython 会查找 .pyc 文件并验证该文件是否存在,是否存在匹配 Python 源代码,然后将其反序列化以构建 SST 和 ST。否则,它将使用 ANTLR 进行完整解析。下图显示了完整的流程。该图并未捕获所有细节。请参阅 探索 Truffle 解释器/编译器管道 部分="ch09lvl1sec56">第 6 章Truffle 支持多语言(多语言),关于 Truffle 解释器和 Graal JIT 如何执行代码的解释:

读书笔记《supercharge-your-applications-with-graalvm》第 7 章 GraalVM Polyglot – JavaScript 和 Node.js

图 8.3 – Graalpython 编译/解释器管道

一旦创建了 SST 和 ST,它们就会被转换为 AST 中间表示并进行优化。最终的专用 AST 在部分评估后提交给 GraalJIT 以供进一步执行,并继续通常的流程,如 探索 Truffle 解释器/编译器管道 部分所述https://subscription.packtpub.com/book/web-development/9781800564909/9" linkend="ch09lvl1sec56">第 6 章Truffle 支持多语言(多语言)

到目前为止,我们已经学习了如何使用 GraalPython 运行 Python 程序,以及 GraalPython 如何使用 Truffle 和 GraalJIT 优化解析和优化代码。现在让我们探索 GraalPython 的多语言互操作性特性。

Exploring interoperability between Java and Python

在本节中,我们将通过示例 Java 代码探讨 Java 和 Python 之间的 互操作性。以下代码计算斐波那契数的总和。这个类有一个 findFibonacci() 方法,它接收我们需要的斐波那契数并返回这些斐波那契数的数组:

public class FibonacciCalculator{
    public int[] findFibonacci(int count) {
        int fib1 = 0;
        int fib2 = 1;
        int currentFib, index;
        int [] fibNumbersArray = new int[count];
        for(index=2; index < count; ++index ) {
            currentFib = fib1 + fib2;
            fib1 = fib2;
            fib2 = currentFib;
            fibNumbersArray[index - 1] = currentFib;
        }
        return fibNumbersArray;
    }
    public static void main(String args[]) {    
        FibonacciCalculator fibCal =             new FibonacciCalculator();
        int[] fibs = fibCal.findFibonacci(10);
    }
}

现在让我们从 Python 代码中调用 findFibonacci() 方法。以下是调用该方法并通过 Java 类返回的数组迭代 的 Python 代码:

import java
import time
fib = java.type("FibonacciCalculator")()
result = fib.findFibonacci(10)
print("Fibonacci number ")
for num in result:
    print(num)

在前面的代码中,我们使用 java.type() 来加载 Java 类,我们直接使用返回值作为 Python 对象来调用 findFibonacci() 方法,通过传递一个参数。然后,我们能够解析该方法返回的结果。让我们编译 Java 代码并运行 Python 代码。下面显示了终端输出:

javac FibonacciCalculator.java
graalpython --jvm --vm.cp=. FibCal.py
Fibonacci number 
0
1
2
3
5
8
13
21
34

我们可以看到我们能够调用 Java 方法并获取一个整数数组并对其进行迭代,而无需任何额外的转换代码。

现在让我们创建一个简单的 Python 函数,它使用 NumPy 对数据集进行一些快速分析。 NumPy 是一个用于数组/矩阵操作的高性能 Python 库,广泛用于机器学习。要了解 Graal 多语言的价值,请想象一个用例,其中 我们有一个数据集,其中包含有关各种心脏病发作病例的信息,按年龄、性别、胆固醇水平、胸痛水平组织,等等,我们想了解在 3 级(高)胸痛后心脏病发作的人的平均年龄是多少。这就是我们将在本节中构建的内容,以了解 Java 和 Python 之间的多语言互操作性,以及我们如何使用 NumPy Python 库。

我们将使用 Kaggle 上提供的心脏病发作分析数据集 (https: //www.kaggle.com/rashikrahmanpritom/heart-attack-analysis-prediction-dataset)。该数据集包含有关各种心脏病发作病例的信息,包括年龄、胆固醇水平、性别、胸痛程度等。这是执行分析的 Python 代码:

import site
import numpy as np
import polyglot as poly
def heartAnalysis():
    heartData = np.genfromtxt('heart.csv', delimiter=',')
    dataOfPeopleWith3ChestPain =         heartData[np.where(heartData[:,2]>2)]
    averageAgeofPeopleWith3ChestPain =        np.average(dataOfPeopleWith3ChestPain[:,0])
    # Average age of people who are getting level 3 and     greater chest pain
    return averageAgeofPeopleWith3ChestPain
poly.export_value("hearAnalysis", heartAnalysis)

在前面的 代码中,我们将 CSV 文件加载到矩阵中。在这里,我们特别感兴趣的是第三列(索引为 2)。我们正在加载第三列值大于 2 的所有行,并将其存储在另一个变量中。然后我们对该矩阵进行平均并返回它。如果我们必须在 Java 中做同样的事情,这将需要很多代码。现在,让我们从 Java 调用这段代码。

在下面的 Java 代码中,我们将通过 Binding 对象使用键导入函数定义。这是完整的Java代码:

public class NumPyJavaExample {
    public void callPythonMethods() {
        Context ctx =
        Context.newBuilder().allowAllAccess(true).build();
        try {
            File fibCal = new File("./numpy-example.py");
            ctx.eval(Source.newBuilder("python",                fibCal).build());
            Value hearAnalysisFn =                ctx.getBindings("python")                    .getMember("heartAnalysis");
            Value heartAnalysisReport =                hearAnalysisFn.execute();
            System.out.println(                "Average age of people who are getting level 3                     and greater chest pain :" +                        heartAnalysisReport.toString());
        }   catch (Exception e) {
            System.out.println("Exception : " );
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        NumPyJavaExample obj = new NumPyJavaExample();
        obj.callPythonMethods();
    }
}

在前面的 Java 代码中,我们是在 创建一个 Context 对象并评估 中的 Python 代码numpy-example.py。然后,我们通过绑定和调用 Python 函数来访问函数定义,并能够获取值。我们正在打印返回的值。以下是运行此 Java 代码的输出:

$ java NumPyJavaExample
Average age of people who are getting level 3 and greater chest pain :55.869565217391305

在前面的输出中,我们可以看到第一次调用需要时间,但是随后的调用几乎没有时间来执行。这不仅展示了我们如何与 Java 代码中的 Python 代码进行互操作,还展示了 Truffle 和 Graal 如何优化执行。

在本节中,我们探索了 Java 和 Python 的互操作性。在下一节中,我们将探讨动态语言与 Python 之间的互操作性。

Exploring interoperability between Python and other dynamic languages

为了探索 Python 和其他动态语言之间的 互操作性,让我们使用上一节中使用的相同 numpy-example.py .让我们从 JavaScript 调用这个方法。

下面是调用Python代码的JavaScipt:

function callNumPyExmple() {
    Polyglot.evalFile('python', './numpy-example.py');
    heartAnalysis = Polyglot.import('heartAnalysis');
    result = heartAnalysis();
    return result;
}
result = callNumPyExmple();
print ('Average age of people who are getting level 3 and     greater chest pain : '+  String(result));

在前面的代码中,我们可以看到我们是如何使用 Polyglot.import()heartAnalysis() 函数的> 功能。这将返回我们正在打印的平均值。让我们运行这段代码,我们可以看到以下结果:

$ js --polyglot numpy-caller.js
Average age of people who are getting level 3 and greater chest pain : 55.869565217391305

现在让我们创建 JavaScript 代码,该代码将具有计算平方的函数。为了演示如何从 Python 调用 JavaScript 代码,下面是 JavaScript 代码:

var helloMathMessage = " Hello Math.js";
function square(a) {
    return a*a;
}
Polyglot.export('square', square);
Polyglot.export('message', helloMathMessage)

这是一个非常简单的 JavaScript 函数,它返回所传递值的平方。我们还导出了 square() 函数和一个变量 message,它带有 helloMathMessage 变量的值。

现在让我们从 Python 代码中调用这个方法。以下是将导入和调用上述 JavaScript 方法的 Python 代码:

import polyglot
polyglot.eval(path="./math.js", language="js")
message = polyglot.import_value('message')
square = polyglot.import_value('square')
print ("Square numbers by calling JS->Python: " +     str(square(10, 20)))
print ("Hello message from JS: " + message)

在这段代码中,我们使用 Python polyglot 对象来评估 JavaScript 文件。然后,我们通过调用 polyglot.import_value() 函数导入所有导出的函数/变量,方法是使用 JavaScript 用于导出函数或变量的相同键。然后我们可以调用这些函数并访问 message 变量并打印值。以下输出是您运行上述代码后得到的结果:

$ graalpython --jvm --polyglot mathUser.py
Square numbers by calling JS->Python: 100
Hello messagr from JS:  Hello Math.js  

我们可以看到 Python 代码是如何导入和调用 JavaScript 代码的。这展示了双向互操作性。该代码与其他语言非常相似,例如 R 和 Ruby。

在本节中,我们探索并深入了解了 Python 解释器如何与 Truffle 一起工作以在 GraalVM 上以最佳方式运行。现在让我们探索和理解 GraalVM 上的 R 语言解释器。

Understanding FastR – the R Truffle interpreter

GraalVM 为 GNU 兼容的 R 运行时提供 一个 R Truffle 解释器。此运行时支持 R 程序和 REPL (read-eval-print-loop) 模式,我们可以在其中快速测试代码,同时我们以交互方式编写代码。 FastR 是开发此 R 运行时的项目。

Installing and running R

就像 Graal Python 一样,R 运行时默认情况下不会随 GraalVM 提供。我们必须使用 Graal Updater 下载并安装它。使用以下命令下载并安装 R 和 Rscript:

gu install r

要运行 R,我们需要 OpenMP 运行时库。这可以在 Ubuntu 上使用 apt-get install libcomp1 和在 Oracle Linux 上使用 yum install libcomp 安装。该库默认安装在 macOS 中。除此之外,如果 R 代码有 C/C++/Fortran 代码,您将需要 C/C++/Fortran。在编写本书时,R 还处于实验阶段,因此还没有支持所有内容。请参考 GraalVM 文档(https://docs .oracle.com/en/graalvm/enterprise/20/docs/reference-manual/r/)了解最新信息。

现在让我们测试 R。 探索 R 解释器,让我们以交互模式运行它。以下终端输出 显示了测试 R 安装的交互模式:

R
R version 3.6.1 (FastR)
Copyright (c) 2013-19, Oracle and/or its affiliates
Copyright (c) 1995-2018, The R Core Team
Copyright (c) 2018 The R Foundation for Statistical Computing
Copyright (c) 2012-4 Purdue University
Copyright (c) 1997-2002, Makoto Matsumoto and Takuji Nishimura
All rights reserved.
FastR is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.
R is a collaborative project with many contributors.
Type 'contributors()' for more information.
Type 'q()' to quit R.
[Previously saved workspace restored]

我们看到我们正在使用前面输出中列出的版本号中的FastR GraalVM版本。现在让我们通过运行一些 Python 命令来测试我们的 FastR 解释器是否正常工作,如下所示:

> 1+1
[1] 2
> abs(-200)
[1] 200

我们可以看到它以交互方式提供结果。现在让我们绘制一个简单的例子。最好的方法是调用 example(),它将显示绘图,如下所示:

> example (plot)
plot> require(stats) # for lowess, rpois, rnorm
plot> plot(cars)
plot> lines(lowess(cars))
NULL
plot> plot(sin, -pi, 2*pi) # see ?plot.function
NULL
plot> ## Discrete Distribution Plot:
plot> plot(table(rpois(100, 5)), type = "h", col = "red", lwd = 10,
plot+      main = "rpois(100, lambda = 5)")
NULL
plot> ## Simple quantiles/ECDF, see ecdf() {library(stats)} for a better one:
plot> plot(x <- sort(rnorm(47)), type = "s", main = "plot(x, type = \"s\")")
plot> points(x, cex = .5, col = "dark red")

这将导致 弹出窗口 带有绘制的图形。下图是弹出的图形截图:

读书笔记《supercharge-your-applications-with-graalvm》第 7 章 GraalVM Polyglot – JavaScript 和 Node.js

图 8.4 – R Plot 输出截图

编写本书时,在运行前面的plot 命令时出现了一些警告。这些警告列出了FastR 的一些限制。但是,这可能会在即将发布的版本中发生变化。以下是弹出的警告:

NULL
Warning messages:
1: In lines.default(lowess(cars)) :
  lines.default not supported. Note: FastR does not support graphics package and most of its functions. Please use grid package or grid based packages like lattice instead.
2: In plot.function(sin, -pi, 2 * pi) :
  plot.function not supported. Note: FastR does not support graphics package and most of its functions. Please use grid package or grid based packages like lattice instead.
3: In axis(...) :
  axis not supported. Note: FastR does not support graphics package and most of its functions. Please use grid package or grid based packages like lattice instead.
4: In points.default(x, cex = 0.5, col = "dark red") :
  points.default not supported. Note: FastR does not support graphics package and most of its functions. Please use grid package or grid based packages like lattice instead.
>

现在我们可以 看到 R 工作正常,现在让我们探索一下 FastR 的互操作性 特性。

Exploring the interoperability of R

在本节中,为了探索 多语言和与 R 的互操作性,我们将运行一些内联​​ JavaScript 并加载示例 JavaScript 代码并导入导出的函数和变量。我们将使用 R 交互模式来执行此操作,以便于理解。要在多语言模式下运行 R,我们必须传递 --polyglot 参数。以下是命令:

R --polyglot

这将以交互模式启动 R 运行时,输出如下:

R version 3.6.1 (FastR)
Copyright (c) 2013-19, Oracle and/or its affiliates
Copyright (c) 1995-2018, The R Core Team
Copyright (c) 2018 The R Foundation for Statistical Computing
Copyright (c) 2012-4 Purdue University
Copyright (c) 1997-2002, Makoto Matsumoto and Takuji Nishimura
All rights reserved.
FastR is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.
R is a collaborative project with many contributors.
Type 'contributors()' for more information.
Type 'q()' to quit R.
[Previously saved workspace restored]
> 

现在,让我们从 简单的内联 JavaScript 开始:

> x <- eval.polyglot('js','[100,200,300,400]')
> print(x)
[polyglot value]
[1] 100 200 300 400
> print(x[3])
[1] 300

在前面的交互式会话中,我们使用语言 ID 和表达式调用 eval.polyglot() 函数。在这种情况下,我们将其指定为 JavaScript,语言 ID 为 js,然后传递一个元素数组。然后我们打印数组和数组中的第三个元素。 eval.polyglot() 函数提供 polyglot 上下文并运行其他语言代码。现在让我们加载一个简单的 JavaScript 代码文件。以下是 math.js 的代码:

var helloMathMessage = " Hello Math.js";
function add(a, b) {
    print("message from js: add() called");
    return a+b;
}
function subtract(a, b) {
    print("message from js: subtract() called");
    return a-b;
}
function multiply(a, b) {
    print("message from js: multiply() called");
    return a*b;
}
Polyglot.export('add', add);
Polyglot.export('subtract', subtract);
Polyglot.export('multiply', multiply);
Polyglot.export('message', helloMathMessage)

前面的代码非常简单。我们已经定义了 add()subtract()multiply() 函数和一个简单的变量 message,它有一个字符串值 Hello Math.js。然后,我们使用 Polyglot.export() 将其导出以供其他语言访问这些函数和变量。

现在让我们加载这个 JavaScript 文件并执行导出的代码; 我们将以交互模式运行指令。您将在此处找到交互式会话,并解释我们正在做什么:

> mathjs <- eval.polyglot('js', path='/chapter8/r/math.js')

该指令加载 JavaScript 文件。确保使用您拥有 JavaScript 文件的确切路径更新路径。现在让我们将导出的函数和变量导入 R:

> message <- import('message')
> add <- import('add')
> subtract <- import('subtract')
> multiply <- import('multiply')

在前面的说明中,我们使用 import() 函数来导入导出的函数和变量。使用我们在 JavaScript 文件中导出时使用的相同字符串非常重要。这些导入被分配给一个变量。现在让我们调用这些函数并打印变量:

> add(10,20)
message from js: add() called
[1] 30
> subtract(30,20)
message from js: subtract() called
[1] 10
> multiply(10,40)
message from js: multiply() called
[1] 400
> print(message)
[1] " Hello Math.js"
>

如您所见,我们可以调用JavaScript函数并打印变量。这演示了我们如何使用 JavaScript,但我们也可以类似地使用所有其他 Truffle 语言。现在让我们探讨如何从 R 中访问 Java 类。下面是 HelloRPolyglot 类的代码,我们将从 R 中调用它:

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
public class HelloRPolyglot {
    public String hello(String name) {
        System.out.println("Hello Welcome from hello");
        return "Hello Welcome from hello " + name;
    }
    public static void helloStatic() {
        System.out.println("Hello from Static hello()");
        try {
            Context polyglot = Context.create();
            Value array = polyglot.eval("js",                 "print('Hello from JS inline in HelloRPolyglot                     class')");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        HelloRPolyglot.helloStatic();
    }
}

让我们理解 前面的代码。我们有一个静态方法,helloStatic(),它调用内联 JavaScript,它打印一条消息,我们还有另一个方法,hello()< /code>,它接受一个参数并打印一个 hello 消息。

让我们编译并运行 Java 类来测试它是否工作正常。下面显示了控制台输出:

javac HelloRPolyglot.java
java HelloRPolyglot
Hello Welcome to R Polyglot!!!
Hello from JS inline in HelloRPolyglot class

现在类工作正常,让我们启动 R 交互模式。这一次,我们必须传递 --jvm 参数让 R 运行时知道我们将使用 Java,并且还要传递 - -vm 参数,将 CLASSPATH 设置为我们拥有 Java 类文件的当前目录:

R --jvm --vm.cp=.        
R version 3.6.1 (FastR)
Copyright (c) 2013-19, Oracle and/or its affiliates
Copyright (c) 1995-2018, The R Core Team
Copyright (c) 2018 The R Foundation for Statistical Computing
Copyright (c) 2012-4 Purdue University
Copyright (c) 1997-2002, Makoto Matsumoto and Takuji Nishimura
All rights reserved.
FastR is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.
R is a collaborative project with many contributors.
Type 'contributors()' for more information.
Type 'q()' to quit R.
[Previously saved workspace restored]
>

现在已经加载了 R,让我们运行指令来调用 Java 类中的 hello() 方法。我们使用 java.type() 函数来加载类。以下是互动环节:

> class <- java.type('HelloRPolyglot')
> print(class)
[polyglot value]
$main
[polyglot value]
$helloStatic
[polyglot value]
$class
[polyglot value]

在前面的交互会话中,我们可以看到类加载成功,当我们打印类时,我们看到它列出了其中的各种方法。现在让我们创建这个类的一个实例。我们使用 new() 函数来做到这一点。以下是使用 new() 函数的交互式会话的输出:

> object <- new(class)
> print(object)
[polyglot value]
$main
[polyglot value]
$helloStatic
[polyglot value]
$class
[polyglot value]
$hello
[polyglot value]

在上面的代码中,我们可以看到对象创建成功,它打印了 类中的所有方法。现在让我们调用这些方法。我们将使用类调用静态方法和对象调用hello(),通过传递一个参数。以下是交互式会话的输出:

> class$helloStatic()
Hello from Static heloo()
Hello from JS inline in HelloRPolyglot class
NULL
> object$hello('FastR')
Hello Welcome from hello
[1] "Hello Welcome from hello FastR"
>

在前面的会话中,我们可以看到调用这两个方法的输出。

让我们以一个现实生活中的例子来说明我们如何利用R绘制图形的强大功能并在中使用绘制的图形节点.js。在本章前面,我们使用了从 Kaggle 获得的包含心脏病发作数据的数据集。让我们使用该数据集在由 Node.js 生成的网页上绘制比较人们年龄和胆固醇水平的图表。

让我们用 npm init 初始化一个 Node.js 项目。以下是我们提供项目名称和其他项目参数的输出控制台:

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (plotwithr-node)
version: (1.0.0)
description:
entry point: (plotWithR.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/vijaykumarab/AB-Home/Developer/GraalVM-book/Code/chapter8/r/plotWithR-node/package.json:
{
  "name": "plotwithr-node",
  "version": "1.0.0",
  "description": "",
  "main": "plotWithR.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
Is this OK? (yes)

这应该 生成一个 Node.js 样板。我们将需要 Express.js 库来公开一个 REST 端点。现在让我们安装 express 库并使用 --save 更新 package.json< /code> 文件的依赖。这是输出:

$ npm install express --save
added 50 packages, and audited 51 packages in 2s
found 0 vulnerabilities

现在让我们编写 Node.js 代码来加载数据集(heart.csv)并将条形图渲染为 标量矢量图SVG)。为了绘图,我们将使用 Lattice 包(您可以在 https:// /www.statmethods.net/advgraphs/trellis.html)。

因此,这是 Node.js 代码:

const express = require('express')
const app = express()
app.get('/plot', function (req, res) {
  var text = ""
  text += Polyglot.eval('R',
    `svg();
     require(lattice);
     data <- read.csv("heart.csv", header = TRUE)
     print(barchart(data$age~data$chol,           main="Age vs Cholestral levels"))
     grDevices:::svg.off()
    `);
  res.send(text)
})
app.listen(3000, function () {
  console.log('Plot with R -  listening on port 3000!')
})

让我们通过代码来理解它。我们正在加载 Express.js 并定义一个 '/plot' 端点。我们正在使用 Polyglot.eval() 来运行我们的 R 代码。我们正在初始化 SVG 并加载 Lattice 包。然后我们加载 heart.csv 文件并将图形呈现为 条形图,然后添加生成的 SVG 响应到 HTML 作为 /plot 端点的响应。

现在让我们运行这段代码。下面显示了运行代码后的输出:

node --jvm --polyglot plotWithR.js
Plot with R - listening on port 3000!
Loading required package: lattice

在浏览器上转到 http://locahost:3000/plot 以调用端点。以下图显示了输出的屏幕截图:

读书笔记《supercharge-your-applications-with-graalvm》第 7 章 GraalVM Polyglot – JavaScript 和 Node.js

图 8.5 – 调用 /plot 的输出

R 是一种非常强大的 语言,用于统计计算和机器学习。这为我们提供了在同一运行时中嵌入 R 代码或从各种其他语言调用 R 代码的机会。如果我们必须在 Java 中做同样的逻辑,可能需要付出很多努力。

Summary

在本章中,我们详细介绍了 Python、R 和 Java on Truffle 解释器是如何在 Truffle 中实现的。我们还探索了这些语言提供的多语言互操作性功能以及编码示例。我们了解每种语言的解释方式的差异。本章提供了如何使用这些不同语言运行代码和编写多语言应用程序的实践演练。我们使用了非常简单的代码,以便您可以轻松理解实现多语言应用程序的概念和 API。

您应该能够使用这些知识在 GraalVM 上编写多语言应用程序。尽管在撰写本书时这些语言中的大多数仍处于实验阶段,但它们为构建高性能的多语言应用程序提供了绝佳的机会。

在下一章中,您将获得良好的实践经验和对多语言如何工作、如何在 GraalVM 上构建 Python 和 R 应用程序以及如何在这些程序之间进行互操作的理解。您还将深入了解 GraalVM 的新运行时 Java on Truffle。

Questions

  1. Truffle 上的 Java 是什么?
  2. Java 在 Truffle 上的优势是什么?
  3. Polyglot.cast() 方法有什么用?
  4. 什么是 SST 和 ST?
  5. 什么是 .pyc 文件?
  6. GraalPython 中用于交换数据和函数定义的多语言绑定方法是什么?
  7. 如何在 R 中导入其他语言定义?
  8. 如何在 R 中加载 Java 类?