GraalVM-要做的十件事

14次阅读

共计 19906 个字符,预计需要花费 50 分钟才能阅读完成。

GraalVM 有很多不同的部分,因此,如果您以前听过这个名字,或者甚至看过我们的一些谈话,那么肯定可以做的事情您还不知道。在本文中,我们将列出 GraalVM 的一些不同功能,并向您展示它们可以为您做些什么。

  • 1. 高性能现代 Java
  • 2. 占地面积小,启动迅速的 Java
  • 3. 结合使用 JavaScript,Java,Ruby 和 R
  • 4. 在 JVM 上运行本地语言
  • 5. 适用于所有语言的工具
  • 6. 扩展基于 JVM 的应用程序
  • 7. 扩展本机应用程序
  • 8.Java 代码作为本机库
  • 9. 数据库中的多语种
  • 10. 创建自己的语言

您可以复制的一切,我展示这篇文章 GraalVM 19.3.0,这是可以从 graalvm.org 下载。我在 macOS 上使用企业版,该版本可在此处免费评估,但说明也可在 Linux 上使用。他们中的大多数人还将与社区版一起使用。
在阅读的同时,继续并运行这些程序!我在 GraalVM 上运行的代码可以从 github.com/chrisseaton/graalvm-ten-things/ 中复制。
设定
我已经从 www.oracle.com/downloads/graalvm-downloads.html 下载了适用于 macOS 的基于 JDK8 的 GraalVM Enterprise Edition,并将其中的程序放到了我的 $PATH。默认情况下,这为我提供了 Java 和 JavaScript 语言。

$ git clone https://github.com/chrisseaton/graalvm-ten-things.git
$ cd foo
$ tar -zxf graalvm-ee-java8-darwin-amd64-19.3.0.tar.gz
    # or graalvm-ee-java8-linux-amd64-19.3.0.tar.gz on Linux
$ export PATH=graalvm-ee-java8-19.3.0/Contents/Home/bin:$PATH
    # or PATH=graalvm-ee-java8-19.3.0/bin:$PATH on Linux

GraalVM 随附了 JavaScript,并具有一个名为的软件包管理器 gu,可让您安装其他语言。我已经安装了 Ruby,Python 和 R 语言。我还安装了该 native-image 工具。这些都可以从 GitHub 下载。

$ gu install native-image
$ gu install ruby
$ gu install python
$ gu install R

现在,当您运行时 java,js 您将获得那些运行时的 GraalVM 版本。

$ java -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit GraalVM EE 19.3.0 (build 25.231-b11-jvmci-19.3-b05, mixed mode)
$ js --version
GraalVM JavaScript (GraalVM EE Native 19.3.0)

1. 高性能现代 Java

GraalVM 中的 Graal 名称来自 GraalVM 编译器。GraalVM 是一个可以全部处理的编译器,这意味着它是作为库编写的编译器的单个实现,可以用于许多不同的事情。例如,我们使用 GraalVM 编译器提前编译和及时编译,以编译多种编程语言和多种体系结构。
使用 GraalVM 的一种简单方法是将其用作 Java JIT 编译器。
我们将使用此示例程序,该程序为您提供文档中的前十个单词。它使用了现代 Java 语言功能,例如流和收集器。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class TopTen {public static void main(String[] args) {Arrays.stream(args)
                .flatMap(TopTen::fileLines)
                .flatMap(line -> Arrays.stream(line.split("\\b")))
                .map(word -> word.replaceAll("[^a-zA-Z]", ""))
                .filter(word -> word.length() > 0)
                .map(word -> word.toLowerCase())
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
                .entrySet().stream()
                .sorted((a, b) -> -a.getValue().compareTo(b.getValue()))
                .limit(10)
                .forEach(e -> System.out.format("%s = %d%n", e.getKey(), e.getValue()));
    }

    private static Stream<String> fileLines(String path) {
        try {return Files.lines(Paths.get(path));
        } catch (IOException e) {throw new RuntimeException(e);
        }
    }

}

GraalVM 包含一个 javac 编译器,但是对于本演示而言,它与标准编译器没有什么不同,因此,javac 如果需要,您可以使用系统。

$ javac TopTen.java

如果我们运行 javaGraalVM 中包含的命令,我们将自动使用 Graal JIT 编译器 - 不需要额外的配置。我将使用该 time 命令来获取从头到尾运行整个程序所需的实际时间,而不是设置复杂的微基准测试,而我将使用大量输入,以便我们在这里或那里大约要花几秒钟的时间。该 large.txt 文件为 150 MB。

$ make large.txt
$ time java TopTen large.txt
sed = 502701
ut = 392657
in = 377651
et = 352641
id = 317627
eu = 317627
eget = 302621
vel = 300120
a = 287615
sit = 282613
real  0m12.950s
user  0m17.827s
sys 0m0.622s

GraalVM 用 Java 编写,而不是像大多数其他 Java 的 JIT 编译器那样用 C ++ 编写。我们认为,这可以使我们比现有的编译器更快地进行改进,它具有强大的新优化功能,例如用于 HotSpot 的标准 JIT 编译器中无法提供的部分转义分析。这可以使您的 Java 程序运行明显更快。
要在没有 GraalVM JIT 编译器进行比较的情况下运行,我可以使用 flag -XX:-UseJVMCICompiler。JVMCI 是 GraalVM 和 JVM 之间的接口。您也可以将其与标准 JVM 进行比较。

$ time java -XX:-UseJVMCICompiler TopTen large.txt
sed = 502701
ut = 392657
in = 377651
et = 352641
id = 317627
eu = 317627
eget = 302621
vel = 300120
a = 287615
sit = 282613
real  0m19.602s
user  0m20.357s
sys 0m0.498s

这表明 GraalVM 在使用标准 HotSpot 编译器运行 Java 程序所需的时间大约是挂钟时间的三分之二。在我们习惯将单位数百分比的性能提升视为显着的领域中,这是一个很大的交易。
如果使用社区版,您仍然可以获得比 HotSpot 更好的结果,但是它不如企业版那么好。
Twitter 是当今在生产中使用 GraalVM 的一家公司,他们说,对于他们而言,它在节省的实际资金方面获得了回报。Twitter 正在使用 GraalVM 运行 Scala 应用程序— GraalVM 在 JVM 字节码级别上工作,因此可用于任何 JVM 语言。
这是使用 GraalVM 的第一种方法 - 只是作为现有 Java 应用程序的嵌入式更好的 JIT 编译器。

2. 占地面积小,启动迅速的 Java

Java 平台对于长时间运行的进程和最高的性能特别强大,但是短期运行的进程可能会遭受更长的启动时间和相对较高的内存使用率。
例如,如果我们以较小的输入(大约 1 KB 而不是 150 MB)运行相同的应用程序,那么运行这么小的文件似乎花费了不合理的长时间,并且内存很大,为 70 MB。。我们 - l 用来打印所用的内存以及所用的时间。

$ make small.txt
$ /usr/bin/time -l java TopTen small.txt
      # -v on Linux instead of -l
sed = 6
sit = 6
amet = 6
mauris = 3
volutpat = 3
vitae = 3
dolor = 3
libero = 3
tempor = 2
suscipit = 2
        0.17 real         0.28 user         0.04 sys
  70737920  maximum resident set size
...

GraalVM 为我们提供了解决此问题的工具。我们说过 GraalVM 就像一个编译器库,可以用许多不同的方式使用。其中之一是提前编译为本地可执行映像,而不是在运行时即时编译。这类似于常规编译器的 gcc 工作方式。

$ native-image --no-server --no-fallback TopTen
[topten:37970]    classlist:   1,801.57 ms
[topten:37970]        (cap):   1,289.45 ms
[topten:37970]        setup:   3,087.67 ms
[topten:37970]   (typeflow):   6,704.85 ms
[topten:37970]    (objects):   6,448.88 ms
[topten:37970]   (features):     820.90 ms
[topten:37970]     analysis:  14,271.88 ms
[topten:37970]     (clinit):     257.25 ms
[topten:37970]     universe:     766.11 ms
[topten:37970]      (parse):   1,365.29 ms
[topten:37970]     (inline):   3,829.55 ms
[topten:37970]    (compile):  34,674.51 ms
[topten:37970]      compile:  41,412.71 ms
[topten:37970]        image:   2,741.41 ms
[topten:37970]        write:     619.13 ms
[topten:37970]      [total]:  64,891.52 ms

此命令将生成一个名为的本机可执行文件 topten。该可执行文件不是 JVM 的启动器,它没有链接到 JVM,也没有以任何方式捆绑 JVM。native-image 确实可以编译您的 Java 代码以及您使用的所有 Java 库,一直到简单的机器代码。对于运行时组件(如垃圾收集器),我们正在运行自己的新 VM,称为基板虚拟机,就像 GraalVM 一样,也是用 Java 编写的。
如果我们查看 topten 使用的库,您会发现它们只是标准系统库。我们也可以仅将这一个文件移动到从未安装过 JVM 的系统上,然后在该系统上运行以确认它不使用 JVM 或任何其他文件。它也非常小 - 该可执行文件小于 8 MB。

$ otool -L topten    # ldd topten on Linux
topten:
  libSystem.B.dylib (current version 1252.250.1)
  CoreFoundation (current version 1575.12.0)
  /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
$ du -h topten 
7.5M  topten

如果运行可执行文件,我们可以看到它比在 JVM 上运行相同程序的启动速度快大约一个数量级,并且使用的内存少大约一个数量级。它是如此之快,以至于您没有注意到在命令行上使用它所花费的时间—当您使用 JVM 运行短运行命令时,您不会感觉到总是会得到暂停。

$ /usr/bin/time -l ./topten small.txt
sed = 6
sit = 6
amet = 6
mauris = 3
volutpat = 3
vitae = 3
dolor = 3
libero = 3
tempor = 2
suscipit = 2
        0.02 real         0.00 user         0.00 sys
   3158016  maximum resident set size
...

该 native-image 工具有一些限制,例如在编译过程中必须使用所有类,还存在一些与反射有关的限制。与基本编译相比,它还有一些其他优点,因为静态初始值设定项在编译期间运行,因此您可以减少每次加载应用程序时所做的工作。
这是您可以使用 GraalVM 的第二种方法 - 一种以低占用空间和快速启动的方式分发和运行现有 Java 程序的方法。它还使您摆脱了配置问题,例如在运行时找到正确的 jar 文件,并允许您拥有较小的 Docker 映像。

3. 结合使用 JavaScript,Java,Ruby 和 R

除了 Java 之外,GraalVM 还包括 JavaScript,Ruby,R 和 Python 的新实现。这些都是使用称为 Truffle 的新语言实现框架编写的,该框架使实现简单而高性能的语言解释器成为可能。当您使用 Truffle 编写语言解释器时,Truffle 会自动代表您使用 GraalVM 为您的语言提供 JIT 编译器。因此 GraalVM 不仅是 Java 的 JIT 编译器和提前的本机编译器,而且还可以是 JavaScript,Ruby,R 和 Python 的 JIT 编译器。
GraalVM 中的语言旨在替代现有语言。例如,我们可以安装一个 Node.js 模块:

$ npm install color
...
+ color@3.1.1
added 6 packages from 6 contributors and audited 7 packages in 6.931s

我们可以使用该模块编写一个小程序 color.js,将 RGB HTML 颜色转换为 HSL:

var Color = require('color');
process.argv.slice(2).forEach(function (val) {console.log(Color(val).hsl().string());
});

然后,我们可以按照通常的方式运行它:

$ node color.js '#42aaf4'
hsl(204.89999999999998, 89%, 60.8%)

GraalVM 中的语言可以协同工作 - 有一个 API,可让您从一种语言运行另一种语言的代码。这样,您就可以编写多语种程序 - 用多种语言编写的程序。
您可能想这样做,是因为您想用一种语言编写大多数应用程序,但是您想使用另一种语言的生态系统中的一个库。例如,假设我们想编写将 Node.js 中的 CSS 颜色名称转换为十六进制的应用程序,但是我们想使用 Ruby 颜色库进行转换。

var express = require('express');
var app = express();

color_rgb = Polyglot.eval('ruby', `
  require 'color'
  Color::RGB
`);

app.get('/css/:name', function (req, res) {color = color_rgb.by_name(req.params.name).html()
  res.send('<h1 style="color: '+ color +'" >'+ color +'</h1>');
});

app.listen(8080, function () {console.log('serving at http://localhost:8080')
});

我们指定了一些 Ruby 代码以字符串形式运行,但是请注意,我们在其中没有做很多事情 - 我们只需要这些库,然后返回一个 Ruby 对象。从 Ruby 使用这个对象的方法通常是这样说的 Color::RGB.by_name(name).html。如果您 color_rgb 进一步研究 JavaScript 的用法,您会发现实际上我们是从 JavaScript 调用这些方法的,即使它们是 Ruby 对象和方法,并且将它们传递给 JavaScript 字符串,然后将结果连接起来,是 Ruby 字符串,还有其他 JavaScript 字符串。
我们将同时安装 Ruby 和 JavaScript 依赖项。

$ gem install color
Fetching: color-1.8.gem (100%)
Successfully installed color-1.8
1 gem installed
$ npm install express
+ express@4.17.0
added 50 packages from 37 contributors and audited 143 packages in 22.431s

添加了来自 37 个贡献者的 50 个软件包,并在 22.431s 中审核了 143 个软件包
然后,我们需要运行 node 两个选项:–polyglot 说我们想访问其他语言,并且 –jvm 因为 node 默认情况下本机图像不包括 JavaScript。

$ node --polyglot --jvm color-server.js
serving at http://localhost:8080

然后像在浏览器中一样正常打开 http:// localhost:8080 / css / aquamarine 或其他颜色名称。

让我们尝试使用更多语言和模块的更大示例。
JavaScript 对于任意大的整数没有很好的解决方案。我发现了几个类似的模块,big-integer 但是这些模块效率不高,因为它们将数字的组件存储为 JavaScript 浮点数。Java 的 BigInteger 类效率更高,因此让我们使用它来执行一些任意大的整数运算。
JavaScript 还不包括对图形的任何内置支持,而 R 确实对此提供了出色的支持。让我们使用 R 的 svg 模块绘制三角函数的 3D 散点图。
在这两种情况下,我们都可以使用 GraalVM 的 polyglot API,并且可以将其他语言的结果组合到 JavaScript 中。

const express = require('express')
const app = express()

const BigInteger = Java.type('java.math.BigInteger')

app.get('/', function (req, res) {
  var text = 'Hello World from Graal.js!<br>'

  // Using Java standard library classes
  text += BigInteger.valueOf(10).pow(100)
          .add(BigInteger.valueOf(43)).toString() + '<br>'

  // Using R interoperability to create graphs
  text += Polyglot.eval('R',
    `svg();
     require(lattice);
     x <- 1:100
     y <- sin(x/10)
     z <- cos(x^1.3/(runif(1)*5+10))
     print(cloud(x~y*z, main="cloud plot"))
     grDevices:::svg.off()
    `);

  res.send(text)
})

app.listen(3000, function () {console.log('Example app listening on port 3000!')
})

在浏览器中打开 http:// localhost:3000 / 以查看结果。

这是我们使用 GraalVM 可以做的第三件事 - 运行以多种语言编写的程序,并一起使用这些语言中的模块。我们认为这是一种语言和模块的商品化,您可以使用最适合您的问题的任何一种语言,以及想要的任何库,无论它来自哪种语言。

4. 在 JVM 上运行本地语言

GraalVM 支持的另一种语言是 C。GraalVM 可以以与运行 JavaScript 和 Ruby 等语言相同的方式运行 C 代码。
GraalVM 实际支持的是运行 LLVM 工具链的输出(即 LLVM 位代码),而不是直接支持 C。这意味着您可以将现有工具与 C 以及其他可以输出 LLVM 的语言一起使用,例如 C ++,Fortran 和可能的语言。其他语言。为了使演示更简单,我运行了一个特殊的 gzip 单文件版本,该版本由 Stephen McCamant 维护。为了简单起见,它只是 gzip 源代码和 autoconf 配置串联到一个文件中。我还必须修补一些东西,以使其在 macOS 和 clang 上都能正常工作,但并不能使其在 GraalVM 上正常工作。
然后,我们可以使用标准 clang(LLVM C 编译器)进行编译,并且希望将其编译为 LLVM 位代码,而不是本机程序集,因为这就是 GraalVM 可以运行的。我正在使用 clang4.0.1。

$ clang -c -emit-llvm gzip.c

然后,我们使用 lli 命令(LLVM 位代码解释器)使用 GraalVM 直接运行此代码。让我们尝试使用我的 system 压缩文件 gzip,然后使用 gzip 在 GraalVM 上运行来解压缩。

$ cat small.txt
Lorem ipsum dolor sit amet...
$ gzip small.txt
$ lli gzip.bc -d small.txt.gz
$ cat small.txt
Lorem ipsum dolor sit amet...

另外,也可以使用 clangGraalVM 附带的 C / C ++ 代码编译为 LLVM 位代码。为此,您应该启用预构建的 LLVM 工具链支持,并将 LLVM_TOOLCHAIN 环境变量指向包含一组构建工具(例如 C 编译器和链接器)的目录,该工具可以将本机项目编译为位代码。

$ gu install llvm-toolchain
$ export LLVM_TOOLCHAIN=$(lli --print-toolchain-path)

然后,您可以将 gzip.c 源代码编译为具有嵌入式 LLVM 位代码的可执行文件,并按以下方式运行它:

$ $LLVM_TOOLCHAIN/clang gzip.c -o gzip
$ gzip small.txt
$ lli gzip -d small.txt.gz
$ cat small.txt
Lorem ipsum dolor sit amet...

GraalVM 中的 Ruby 和 Python 实现使用此技术来为这些语言运行 C 扩展。这意味着您可以在 VM 内运行 C 扩展,即使我们支持这些旧的本机扩展接口,它也可以保持高性能。
这是使用 GraalVM 可以完成的第四件事 - 运行以诸如 C 和 C ++ 之类的本地语言编写的程序,还可以运行针对诸如 Python 和 Ruby 之类的语言的 C 扩展,而现有的 JVM 实现(如 JRuby)则无法做到。

5. 适用于所有语言的工具

如果您使用 Java 编程,则可能已经习惯了非常高质量的工具,例如 IDE,调试器和分析器。并非所有语言都具有这类工具,但是如果您在 GraalVM 中使用一种语言,则可以使用它们。
所有 GraalVM 语言(目前不包括 Java)都是使用通用的 Truffle 框架实现的。这使我们可以一次实现调试器之类的功能,并使其对所有语言都可用。
要尝试此操作,我们将编写一个基本的 FizzBuzz 程序,因为它会将内容打印到屏幕上,并且具有仅在某些迭代中使用的清晰分支,因此我们可以更轻松地设置一些断点。我们将从 JavaScript 实现开始。

function fizzbuzz(n) {if ((n % 3 == 0) && (n % 5 == 0)) {return 'FizzBuzz';} else if (n % 3 == 0) {return 'Fizz';} else if (n % 5 == 0) {return 'Buzz';} else {return n;}
}

for (var n = 1; n <= 20; n++) {print(fizzbuzz(n));
}

我们可以使用 js 可执行文件,使用 GraalVM 正常运行此 JavaScript 程序。

$ js fizzbuzz.js
1
2
Fizz
4
Buzz
Fizz
...

我们还可以使用 flag 运行程序 –inspect。这将为我们提供一个可在 Chrome 中打开的链接,并将在调试器中暂停该程序。

$ js --inspect fizzbuzz.js
Debugger listening on port 9229.
To start debugging, open the following URL in Chrome:
    chrome-devtools://devtools/bundled/inspector.html?ws=127.0.0.1:9229/6c478d4e-1350b196b409
...

然后,我们可以在 FizzBuzz 行上设置一个断点,然后继续执行。中断时,我们将看到的值 n,并且可以再次继续,或者浏览调试接口的其余部分。

Chrome 调试器通常与 JavaScript 一起使用,但是 GraalVM 中的 JavaScript 没有什么特别的。此标志也可用,并且可以在我们的 Python,Ruby 和 R 的实现中使用。我不会向您显示每个程序的源,但是您以完全相同的方式运行它们,并且为每个程序获得相同的 Chrome 调试器接口。

$ graalpython --inspect fizzbuzz.py
$ ruby --inspect fizzbuzz.rb
$ Rscript --inspect fizzbuzz.r

您可能已经从 Java 使用过的另一个工具是 VisualVM。它为您提供了一个用户界面,您可以将其连接到计算机上或网络上某个位置上正在运行的 JVM,以检查各个方面,例如它如何使用内存和线程。
GraalVM 将 VisualVM 包含在标准 jvisualvm 命令中。

$ jvisualvm &> /dev/null &

如果在 TopTen 从前运行 Java 应用程序的同时运行它,我们可以观察一段时间内的内存使用情况,也可以执行堆转储并检查在堆中使用内存的对象类型。

$ java TopTen large.txt

我已经编写了这个 Ruby 程序来随着时间的推移产生一些垃圾。

require 'erb'

x = 42

template = ERB.new <<-EOF
  The value of x is: <%= x %>
EOF

loop do
  puts template.result(binding)
end

如果您使用 VisualVM 运行 JRuby 之类的标准 JVM 语言,您将很失望,因为您将看到底层的 Java 对象,而不是有关该语言对象的任何信息。
如果我们改用 GraalVM 版本的 Ruby,VisualVM 将识别 Ruby 对象本身。我们需要使用 –jvm 命令来使用 VisualVM,因为它不支持 Ruby 的本机版本。

$ ruby --jvm render.rb

如果需要,我们可以看到相同的基础 Java 对象的堆视图转储,或者在“摘要”下可以选择 Ruby Heap 并查看适当的 Ruby 对象。

Truffle 框架是语言和工具的一种纽带。如果您使用 Truffle 来编程语言,并且使用 Truffle 的工具 API 来对诸如调试器之类的工具进行编程,则每种工具都可以使用每种语言,并且只需要编写一次该工具即可。
因此,可以使用 GraalVM 的第五种方式是作为一个平台来获取语言的高质量工具,这些语言并不总是支持它们来构建定制工具,例如 Chrome Debugger 或 VisualVM。

6. 扩展基于 JVM 的应用程序

这些语言和工具不仅可以用作独立的语言实现,而且可以在多种语言的用例中一起使用,也可以嵌入到 Java 应用程序中。使用新的 org.graalvm.polyglotAPI,您可以加载和运行其他语言的代码,并使用它们中的值。

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;

public class ExtendJava {public static void main(String[] args) {
        String language = "js";
        try (Context context = Context.newBuilder().allowNativeAccess(true).build()) {for (String arg : args) {if (arg.startsWith("-")) {language = arg.substring(1);
                } else {Value v = context.eval(language, arg);
                    System.out.println(v);
                }
            }
        }
    }
}

如果您使用 GraalVM 中的 javacand java 命令,则导入 org.graalvm… 将已经在您的类路径中,因此您可以编译并运行此代码而无需任何额外的标志。

$ javac ExtendJava.java
$ java ExtendJava '14 + 2'
16
$ java ExtendJava -js 'Math.sqrt(14)'
3.7416573867739413
$ java ExtendJava -python '[2**n for n in range(0, 8)]'
[1, 2, 4, 8, 16, 32, 64, 128]
$ java ExtendJava -ruby '[4, 2, 3].sort'
[2, 3, 4]

这些版本的语言是相同的高性能多语种版本,你使用像命令得到 node 和 ruby 作为 GraalVM 可执行文件。
这是使用 GraalVM 的第六种方法—作为将 Java 语言中嵌入许多不同语言的单个界面。使用 polyglot API,您可以获取来宾语言对象并将其用作 Java 接口和其他复杂的互操作性。

7. 扩展本机应用程序

GraalVM 已经包括一个像这样构建的本机库—它是一个库,可让您运行从本机应用程序以任何 GraalVM 语言编写的代码。像 V8 这样的 JavaScript 运行时,以及像 CPython 这样的 Python 解释程序,通常都是可嵌入的,这意味着它们可以作为一个库链接到另一个应用程序中。通过链接到这一多语言嵌入库,GraalVM 允许您在嵌入式上下文中使用任何语言。
获取 GraalVM 时已经构建了该库,但是默认情况下它仅包含内置语言 JavaScript。您可以使用下面的命令来重建多语种库,以包括其他语言,但是您需要从 OTN 下载基于 Mac OS(19.3.0)的 JDK8 的 Oracle GraalVM Enterprise Edition Native Image Early Adopter。重建确实需要花费几分钟,因此,如果您遵循以下步骤,则可能只想尝试使用 JavaScript –如果只需要 JavaScript,就不需要重建。

$ gu install --force --file native-image-installable-svm-svmee-java8-darwin-amd64-19.3.0.jar
$ gu rebuild-images libpolyglot

我们可以编写一个简单的 C 程序,以任何通过命令行传递的 GraalVM 语言运行命令。我们 ExtendJava 将从上面的示例开始做等效的工作,但是将 C 作为宿主语言。

#include <stdlib.h>
#include <stdio.h>

#include <polyglot_api.h>

int main(int argc, char **argv) {
  poly_isolate isolate = NULL;
  poly_thread thread = NULL;
  
  if (poly_create_isolate(NULL, &isolate, &thread) != poly_ok) {fprintf(stderr, "poly_create_isolate error\n");
    return 1;
  }
  
  poly_context context = NULL;
  
  if (poly_create_context(thread, NULL, 0, &context) != poly_ok) {fprintf(stderr, "poly_create_context error\n");
    goto exit_isolate;
  }
  
  char* language = "js";
  
  for (int n = 1; n < argc; n++) {if (argv[n][0] == '-') {language = &argv[n][1];
    } else {
      poly_value result = NULL;

      if (poly_open_handle_scope(thread) != poly_ok) {fprintf(stderr, "poly_open_handle_scope error\n");
        goto exit_context;
      }
      
      if (poly_context_eval(thread, context, language, "eval", argv[n], &result) != poly_ok) {fprintf(stderr, "poly_context_eval error\n");
        
        const poly_extended_error_info *error;
        
        if (poly_get_last_error_info(thread, &error) != poly_ok) {fprintf(stderr, "poly_get_last_error_info error\n");
          goto exit_scope;
        }

        fprintf(stderr, "%s\n", error->error_message);
        goto exit_scope;
      }
      
      char buffer[1024];
      size_t length;
      
      if (poly_value_to_string_utf8(thread, result, buffer, sizeof(buffer), &length) != poly_ok) {fprintf(stderr, "poly_value_to_string_utf8 error\n");
        goto exit_scope;
      }

      if (poly_close_handle_scope(thread) != poly_ok) {fprintf(stderr, "poly_close_handle_scope error\n");
        goto exit_context;
      }

      buffer[length] = '\0';
      printf("%s\n", buffer);
    }
  }

  if (poly_context_close(thread, context, true) != poly_ok) {fprintf(stderr, "poly_context_close error\n");
    goto exit_isolate;
  }

  if (poly_tear_down_isolate(thread) != poly_ok) {fprintf(stderr, "poly_tear_down_isolate error\n");
    return 1;
  }
  
  return 0;

exit_scope:
  poly_close_handle_scope(thread);
exit_context:
  poly_context_close(thread, context, true);
exit_isolate:
  poly_tear_down_isolate(thread);
  return 1;
}

然后,我们可以使用系统 C 编译器来编译和运行该程序,并链接到 GraalVM 中的本机多语言库。同样,它不需要 JVM。

$ clang -L$GRAALVM_HOME/jre/lib/polyglot -I${GRAALVM_HOME}/jre/lib/polyglot -lpolyglot -o extendc -O1 extendc.c -rpath $GRAALVM_HOME
$ otool -L extendc
extendc:
  @rpath/jre/lib/polyglot/libpolyglot.dylib (compatibility version       0.0.0, current version 0.0.0)
  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.250.1)
$ ./extendc '14 + 2'
16
$ ./extendc -js 'Math.sqrt(14)'
3.7416573867739413
$ ./extendc -python '[2**n for n in range(0, 8)]'
[1, 2, 4, 8, 16, 32, 64, 128]
$ ./extendc -ruby '(0...8).map {|n| 2 ** n}'
[1, 2, 4, 8, 16, 32, 64, 128]

您可以使用 GraalVM 完成这第七件事 - 在本机应用程序中使用单个库来嵌入任何 GraalVM 语言。

8. Java 代码作为本机库

Java 具有许多高质量的库的强大生态系统,这些库通常在其他生态系统(包括本机应用程序和其他托管语言)中不可用。如果您想使用本机应用程序中的 Java 库,则可以嵌入 JVM,但这会变得非常庞大和复杂。
GraalVM 使您可以采用现成的 Java 库或自己编写的 Java 库,并将其编译为独立的本机库以供其他本机语言使用。与之前的本机编译一样,它们不需要运行 JVM。
我编写了一个应用程序,该应用程序使用了出色的 Apache SIS 地理空间库来计算地球上两点之间的大圆距离。我使用 SIS 0.8,我从 http://sis.apache.org/ 单独下 …。

import org.apache.sis.distance.DistanceUtils;

public class Distance {public static void main(String[] args) {final double aLat   = Double.parseDouble(args[0]);
        final double aLong  = Double.parseDouble(args[1]);
        final double bLat   = Double.parseDouble(args[2]);
        final double bLong  = Double.parseDouble(args[3]);
        System.out.printf("%f km%n", DistanceUtils.getHaversineDistance(aLat, aLong, bLat, bLong));
    }

}

我们可以像平常一样编译它,然后用它来计算伦敦(纬度 51.507222,经度 -0.1275)和纽约(40.7127,-74.0059)之间的距离。

$ javac -cp sis.jar -parameters Distance.java
$ java -cp sis.jar:. Distance 51.507222 -0.1275 40.7127 -74.0059
5570.25 km

就像我们对 topten 程序所做的那样,我们可以将其编译为本地可执行文件。

$ native-image --no-server --no-fallback -cp sis.jar:. Distance
...
$ ./distance 51.507222 -0.1275 40.7127 -74.0059
5570.25 km

我们还可以将其构建为本地共享库,而不是可执行文件。为此,我们将一个或多个方法声明为 @CEntryPoint。

...
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.function.CEntryPoint;

public class Distance {

    ...

    @CEntryPoint(name = "distance")
    public static double distance(IsolateThread thread,
          double a_lat, double a_long,
          double b_lat, double b_long) {return DistanceUtils.getHaversineDistance(a_lat, a_long, b_lat, b_long);
    }
    
    ...

}

我们不需要更改 javac 命令行,因为 GraalVM 会自动将这些新 API 放到类路径中。然后,我们可以编译为共享库和自动生成的头文件。

$ native-image --no-server -cp sis.jar:. --shared -H:Name=libdistance
$ otool -L libdistance.dylib   # .so on Linux
libdistance.dylib:
  .../graalvm-ten-things/libdistance.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.250.1)
CoreFoundation (compatibility version 150.0.0, current version 1575.17.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
$ du -h libdistance.dylib
1.8M  libdistance.dylib

然后,我们可以编写一些 C 程序来使用该库。我们的本机库的接口确实有一个小小的仪式 - 因为 VM 需要管理堆,线程,垃圾收集器和其他服务,因此我们需要创建系统实例,并将其告知我们的主线程。

#include <stdlib.h>
#include <stdio.h>

#include <libdistance.h>

int main(int argc, char **argv) {
  graal_isolate_t *isolate = NULL;
  graal_isolatethread_t *thread = NULL;
  
  if (graal_create_isolate(NULL, &isolate, &thread) != 0) {fprintf(stderr, "graal_create_isolate error\n");
    return 1;
  }
  
  double a_lat   = strtod(argv[1], NULL);
  double a_long  = strtod(argv[2], NULL);
  double b_lat   = strtod(argv[3], NULL);
  double b_long  = strtod(argv[4], NULL);
  
  printf("%.2f km\n", distance(thread, a_lat, a_long, b_lat, b_long));

  if (graal_detach_thread(thread) != 0) {fprintf(stderr, "graal_detach_thread error\n");
    return 1;
  }
  
  return 0;
}

我们使用标准系统工具对此进行编译,并且可以运行我们的可执行文件(LD_LIBRARY_PATH=. 在 Linux 上设置)。

$ clang -I. -L. -ldistance distance.c -o distance
$ otool -L distance
distance:
.../graalvm-blog-post/libdistance.dylib (compatibility version 0.0.0, current version 0.0.0)
libSystem.B.dylib (compatibility version 1.0.0, current version 1252.250.1)
$ ./distance 51.507222 -0.1275 40.7127 -74.0059
5570.25 km

这是我们使用 GraalVM 可以完成的第八件事 - 将 Java 代码编译为一个本机库,然后可以在本机应用程序中使用它而无需使用完整的 JVM。

9. 数据库中的多语种

用于嵌入语言的多语言库的一种应用是在 Oracle 数据库中。我们已经使用它来创建 Oracle 数据库多语言引擎(MLE),其中包括对使用 GraalVM 语言和 SQL 模块的支持。
例如,假设我们已经有一个用 JavaScript 编写的前端,并且我们正在使用 JavaScript 模块对电子邮件地址进行一些验证 validator。如果我们对用 SQL 或 PLSQL 编写的数据库中的同一应用程序具有某种逻辑,我们希望能够使用完全相同的验证器,以使结果相同。
您可以从 https://oracle.github.io/orac…。然后将其加载到 Docker 中。

$ docker load --input mle-docker-0.2.7.tar.gz

我们要运行该映像,然后在完成加载(可能需要几分钟)后,在其中执行一个 Bash 终端。

$ docker run mle-docker-0.2.7
$ docker ps
$ docker exec -ti <container_id> bash -li

如果我们可以 sqlplus 在此 Bash 终端中运行交互式 SQL 工具,以连接到数据库,则它已启动并正在运行。

$ sqlplus scott/tiger@localhost:1521/ORCLCDB

现在,仍然在 Docker 中运行的 Bash 终端中,我们安装该 validator 模块,然后运行命令 dbjs 将其部署到数据库中。然后,我们 sqlplus 再次运行。

$ npm install validator
$ npm install @types/validator
$ dbjs deploy -u scott -p tiger -c localhost:1521/ORCLCDB validator
$ sqlplus scott/tiger@localhost:1521/ORCLCDB

现在,我们可以将 validator 模块用作 SQL 表达式的一部分。

SQL> select validator.isEmail('hello.world@oracle.com') from dual;
VALIDATOR.ISEMAIL('HELLO.WORLD@ORACLE.COM')
-------------------------------------------
                                          1
SQL> select validator.isEmail('hello.world') from dual;
VALIDATOR.ISEMAIL('HELLO.WORLD')
--------------------------------
                               0

这是我们使用 GraalVM 可以做的第九件事 - 在 Oracle 数据库内部运行 GraalVM 语言,以便您可以在数据库逻辑内部的前端或后端使用相同的逻辑,而不必始终将其从数据库中拉出到应用服务器。

10. 创建自己的语言

Oracle 实验室和我们的学术合作者已经能够用一个相对较小的团队来实现 JavaScript,R,Ruby,Python 和 C 的新高性能实现,因为我们已经开发了 Truffle 框架来简化这一过程。
Truffle 是一个 Java 库,可帮助您编写语言的抽象语法树(AST)解释程序。AST 解释器可能是实现语言的最简单方法,因为它直接在解析器的输出上工作,并且不涉及任何字节码或常规编译器技术,但是通常很慢。因此,我们将其与称为部分评估的技术相结合,该技术允许 Truffle 仅仅基于 AST 解释器就可以使用 GraalVM 编译器为您的语言自动提供即时编译。
您可以使用 Truffle 来实现自己的新编程语言,创建现有编程语言的高性能实现或实现特定于域的语言。我们在项目中谈论了很多关于 Truffle 和 Graal 的细节,但是我们常常忘记提及 Truffle 是实现语言的简单方法。您会自动获得调试器之类的功能。任何只完成了编程语言实施本科课程的人都应该具备所需的基本技能。Oracle 实验室只需几个月的实习生,就能比以前的任何工作更快地实现 Ruby 的基本版本。
我们这里没有空间来显示完整的语言,即使是很小的一种语言,但是 SimpleLanguage 是一个可执行的教程,说明如何使用 Truffle 基于简化的 JavaScript 样式语言来创建自己的语言。例如看实现了的 if 说法。
Oracle 实验室以外的其他人使用 Truffle 编写的其他语言包括 Smalltalk 变体,Newspeak 变体和 Lisp 变体。Lisp 示例包含一个您可以遵循的教程。

结论

GraalVM 提供了非常多样化的新功能集–在该平台上,您可以构建更强大的语言和工具,并将其置于更多环境中。它使您可以选择所需的语言和模块,无论程序在何处运行或已在使用哪种语言。
要尝试 GraalVM,请访问 https://www.graalvm.org/。那里有下载和文档的链接,还有更多类似我们在此博客文章中显示的示例。
本篇文章由一文多发平台 ArtiPub 自动发布

正文完
 0