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

  • 1.高性能现代Java
  • 2.占地面积小,启动迅速的Java
  • 3.结合使用JavaScript,Java,Ruby和R
  • 4.在JVM上运行本地语言
  • 5.适用于所有语言的工具
  • 6.扩展基于JVM的应用程序
  • 7.扩展本机应用程序
    1. 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/单独下载并提取了jar。

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/oracle-db-mle/releases/0.2.7/docker/下载MLE作为Docker映像。然后将其加载到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/。那里有下载和文档的链接,还有更多类似我们在此博客文章中显示的示例。