Øriginally published in deepu.tech.
Please follow me on Twitter for updates and let me know what can be improved in the post.
Have you heard of GraalVM? If you haven't you should check it out. It is an exciting technology, you know the kind that gets a polyglot developer going 😉
从网站:
GraalVM是一种通用虚拟机,用于运行以JavaScript,Python,Ruby,R,基于JVM的语言(如Java,Scala,Groovy,Kotlin,Clojure)以及基于LLVM的语言(如C和C ++)编写的应用程序。
GraalVM is one of its kind. It is a polyglot VM developed at Oracle and apart from its polyglot capabilities it also has been proven to be quite performant and has a smaller memory footprint. It has support for building native images and some modern Java microservice frameworks like Micronaut and Quarkus support GraalVM as it provides faster startup and smaller footprint which is ideal in microservice architectures.
那么GraalVM的功能是什么? 让我们快速浏览
GraalVM features
- Drop in JDK replacement - Some benchmarks show GraalVM to be faster and less memory hungry than other JDK vendors, I haven't personally run any benchmarks
- Drop in NodeJS replacement - Uses GraalVM instead of V8 as the engine for NodeJS
- Faster runtime for Ruby and R than the default implementations
- Ahead-of-time(AOT) compiled native images
- Polyglot capabilities - Java(Any JVM language), JavaScript, Ruby, Python, R, C/C++/Rust(LLVM) along with language interoperability
- The Truffle Language Implementation framework to implement third-party language support
Install GraalVM
Before we start, let us setup GraalVM. I used SDKMAN, you can also follow this if you like to install it manually.
- First install SDKMAN if you don't have it already
sdk list java
# you can use a newer version if available
sdk install java 19.3.1.r11-grl
sdk use java 19.3.1.r11-grl
# Check everything
java -version
node -v
lli --version
上面将安装GraalVM并将其设置为爪哇,节点和丽丽上下文。请注意: If you start a new terminal session,you would have to run sdk use 爪哇 19.3.1.r11-grl再次。
- Install LLVM toolchain, Python and Ruby support
gu install llvm-toolchain
export LLVM_TOOLCHAIN=\$(lli --print-toolchain-path)
gu install python
gu install ruby
- Install Rust
curl https://sh.rustup.rs -sSf | sh
就是这样,我们准备好了!
Lets have some fun
作为一名多语言开发人员,GraalVM对我来说很有趣,因为我可以同时使用我喜欢的多种语言,并充分利用每种语言的最佳优势。 让我们探索GraalVM提供的多语言功能,请注意,对Python,Ruby,R和Rust的支持仍处于试验阶段,因此您的学习里程可能会有所不同。 今天,我们将使用Java,JavaScript,Ruby,Rust,Python和C ++构建程序。
I wanted to use Rust and Go as well. While Rust mostly works via the GraalVM
lli
command line, it has a lot of limitations when embedded in polyglot mode. After a lot of fiddling around, I did manage to get it working. For Golang, it might be possible with this Go LLVM compiler as shown here, but it's having its own set of issues as well when I tried. So I have given up on Golang for now. Let me know if any of you got it working.
我们将有一个用Java编写的简单(愚蠢的)应用程序,它针对Java内部的每个步骤都组成了不同语言的方法
- 蟒蛇:FiltersouttheFibonaccinumbersfromthegiveninputarray的爪哇Script:FindthecubeofeachnumberintheoutputarrayfromthepreviousstepC++:Getthesumofthenumbersintheoutputarrayfromthepreviousstep锈:Findthecube-rootofthenumberfromthepreviousstep红宝石:Findfactorialofthenumberfromthepreviousstep爪哇:Finallyprinttheresult(thisisalsothewrapperprogram)
If you prefer a more complex example, check this out.
Step 1: Java
让我们从我们的Java包装程序开始Polyglot.java
import java.io.*;
import org.graalvm.polyglot.*;
class Polyglot {
// We create a polyglot context to evaluate source files
static Context polyglotCtx = Context.newBuilder().allowAllAccess(true).build();
// Utility method to load and evaluate a source file
static Value loadSource(String language, String fileName) throws IOException {
File file = new File(fileName);
Source source = Source.newBuilder(language, file).build();
return polyglotCtx.eval(source);
}
// Utility method to convert arrays between languages
static int[] getIntArrayFromValue(Value val) {
int[] out = new int[(int) val.getArraySize()];
if (val.hasArrayElements()) {
for (int i = 0; i < val.getArraySize(); i++) {
out[i] = val.getArrayElement(i).asInt();
}
}
return out;
}
public static void main(String[] args) throws IOException {
int[] input = new int[] { 4, 2, 8, 5, 20, 1, 40, 13, 23 };
/* PYTHON: Get the Fibonacci numbers from the array */
loadSource("python", "pythonpart.py");
Value getFibonacciNumbersFn = polyglotCtx.getBindings("python").getMember("getFibonacciNumbers");
int[] fibNumbers = getIntArrayFromValue(getFibonacciNumbersFn.execute(input));
/* JAVASCRIPT: Find cube of numbers in the output array */
loadSource("js", "jspart.js");
Value findCubeOfNumbersFn = polyglotCtx.getBindings("js").getMember("findCubeOfNumbers");
int[] sqrNumbers = getIntArrayFromValue(findCubeOfNumbersFn.execute(fibNumbers));
/* C++: Get the sum of the numbers in the output array */
loadSource("llvm", "cpppart");
Value getSumOfArrayFn = polyglotCtx.getBindings("llvm").getMember("getSumOfArray");
int sum = getSumOfArrayFn.execute(sqrNumbers, sqrNumbers.length).asInt();
/* Rust: Find the cube root of sum */
Value cubeRootFn = loadSource("llvm", "rustpart.bc").getMember("cube_root");
// println! macro does not work from Rust when embedded, some issue with mangling
System.out.println("Rust => Find cube root of the number");
Double cubeRoot = cubeRootFn.execute(sum).asDouble();
/* RUBY: Find factorial of the number */
Value factorialFn = loadSource("ruby", "rubypart.rb");
long out = factorialFn.execute(cubeRoot).asLong();
System.out.println("Sum: " + sum);
System.out.println("Cube Root: " + cubeRoot);
System.out.println("Factorial: " + out);
}
}
实用程序功能是为了简化代码,现在让我们看一下组成功能的每个步骤。
Step 2: Python
我们正在执行getFibonacciNumbers功能位于文件中pythonpart.py并将其传递给我们的输入数组。
/* PYTHON: Get the Fibonacci numbers from the array */
loadSource("python", "pythonpart.py");
Value getFibonacciNumbersFn = polyglotCtx.getBindings("python").getMember("getFibonacciNumbers");
int[] fibNumbers = getIntArrayFromValue(getFibonacciNumbersFn.execute(input));
让我们来看看pythonpart.py,这是一个标准的python程序,它接收一个数组并从中过滤出斐波那契数并返回结果数组。
import math
def isPerfectSquare(num):
n = int(math.sqrt(num))
return (n * n == num)
# Function to check if the number is in Fibonacci or not
def getFibonacciNumbers(array):
print("Python => Filtering Fibonacci number from the array");
out = []
n = len(array)
count = 0
for i in range(n):
if (isPerfectSquare(5 * array[i] * array[i] + 4) or
isPerfectSquare(5 * array[i] * array[i] - 4)):
out.append(array[i]);
count = count + 1
if (count == 0):
print("None present");
return out
Step 3: JavaScript
我们正在执行findCubeOfNumbers功能位于文件中jspart.js并从Python函数传递结果。
/* JAVASCRIPT: Find cube of numbers in the output array */
loadSource("js", "jspart.js");
Value findCubeOfNumbersFn = polyglotCtx.getBindings("js").getMember("findCubeOfNumbers");
int[] sqrNumbers = getIntArrayFromValue(findCubeOfNumbersFn.execute(fibNumbers));
让我们来看看jspart.js,这是一个标准JavaScript函数,它接受一个数组并映射到元素上并返回该数组。 但是我们不得不打电话Array.prototype.map.call而不只是array.map因为Java传递的数组不是标准的JS数组。
function findCubeOfNumbers(array) {
console.log("JS => Getting cube of numbers in the array");
return Array.prototype.map.call(array, it => Math.pow(it, 3));
}
Step 4: C++
我们正在执行getSumOfArray功能位于cpppart二进制文件。 我们在这里传递JS函数的结果和数组的长度。 我们在这里必须使用编译后的二进制文件,这与解释语言的Python,Ruby和JavaScript不同。
/* C++: Get the sum of the numbers in the output array */
loadSource("llvm", "cpppart");
Value getSumOfArrayFn = polyglotCtx.getBindings("llvm").getMember("getSumOfArray");
int sum = getSumOfArrayFn.execute(sqrNumbers, sqrNumbers.length).asInt();
二进制文件的来源在cpppart.cpp文件。 使用以下内容编译
export LLVM_TOOLCHAIN=$(lli --print-toolchain-path)
$LLVM_TOOLCHAIN/clang++ -shared cpppart.cpp -lpolyglot-mock -o cpppart
让我们来看看cpppart.cpp,它是导出功能的标准C ++程序。 它以数组及其长度作为参数并返回一个数字
#include<iostream>
using namespace std;
// Function to find the sum of integer array
// extern "C" is required to suppress mangling
extern "C" int getSumOfArray(int array[], int size) {
printf("C++ => Find sum of numbers in an array\n");
int i, sum = 0;
for(i = 0; i < size; i++) {
sum += array[i];
}
return sum;
}
Step 5: Rust
我们正在执行立方根功能位于文件中rustpart.bc,来自锈部分。 我们在这里传递C ++函数的结果。
/* Rust: Find the cube root of sum */
Value cubeRootFn = loadSource("llvm", "rustpart.bc").getMember("cube_root");
// println! macro does not work from Rust when embedded, some issue with mangling
System.out.println("Rust => Find cube root of the number");
Double cubeRoot = cubeRootFn.execute(sum).asDouble();
让我们来看看锈部分,这是一个标准的Rust函数,需要一个数字才能找到其立方根并返回它。 但是我们必须指定#[no_mangle]注释,我们显然也不能使用任何包装箱。 具有原始args / output的简单函数似乎可以工作,但是嵌入时,更复杂的函数不起作用。
#[no_mangle]
fn cube_root(arg: f64) -> f64 {
arg.cbrt()
}
fn main(){}
我们使用以下代码将Rust源代码编译为二进制代码锈编译器--emit = llvm-bc旗
rustc --emit=llvm-bc rustpart.rs
Step 6: Ruby
我们正在执行阶乘功能位于文件中rubypart.rb。 我们在这里传递Rust函数的结果。
/* RUBY: Find factorial of the number */
Value factorialFn = loadSource("ruby", "rubypart.rb");
long out = factorialFn.execute(cubeRoot).asLong();
让我们来看看rubypart.rb,这是一个标准的Ruby lambda函数,该函数接受一个数字并返回其阶乘。
factorial = -> (num) {
puts "Ruby => Find factorial of the number"
(1..num).inject {|product, num| product * num }
}
最后,我们使用Java打印输出。
Run the program
要运行此程序,我们需要先编译C ++,Rust和Java文件,然后使用GraalVM提供的JVM运行它。 下面是步骤,您可以将其保存为运行并执行它。
export LLVM_TOOLCHAIN=$(lli --print-toolchain-path)
$LLVM_TOOLCHAIN/clang++ -shared cpppart.cpp -lpolyglot-mock -o cpppart || exit
rustc --emit=llvm-bc rustpart.rs || exit
javac Polyglot.java && java Polyglot
它将产生以下输出:
Conclusion
这不是很有趣吗? 那么,这种多语言功能有用吗? 取决于这,GraalVM的多语言功能仍未准备就绪,但仍很有用,因为它为真正的语言互操作性打开了大门,想象一下能够使用程序中任何语言的库,这对于 许多带有GraalVM的C,Ruby,R,JS和Java库,但是随着支持的改善,我们将能够不受限于一种语言的束缚。 对于像Ruby这样的语言,GraalVM似乎比例如标准CRuby或JRuby快得多,这是有希望的,因为这意味着您在将多种语言嵌入程序中时不必担心开销。
GraalVM是我最近遇到的最具革命性的技术之一,我希望多语种语言支持能够立即投入生产,再加上其本机映像功能,它将成为真正多语种应用程序的非常强大的平台。
如果您喜欢这篇文章,请留下喜欢或评论。
You can follow me on Twitter and LinkedIn.
封面图片来源:基于各个项目的官方徽标。