En écoutant Rust, le langage de programmation système (?), Il y a pas mal d'histoires sur FFI (Interface de fonction étrangère). Je vais. Donc je l'appelle depuis Java que j'écris habituellement.
Ceux qui savent lire un peu Java et Rust et savent qu'il existe un outil de gestion de projet pour Java appelé Maven. Ce n'est pas difficile, il s'agit du niveau de création d'un modèle pour le projet.
L'environnement de développement utilise VS Code. L'environnement d'exécution Rust et Java est construit avec Docker. DockerFiile La base est un échantillon d'environnement de développement Java (Debean 10) fourni par Microsoft, que j'ai utilisé pour moi-même. Le seul changement est que la version Java est passée de 14 à 11. Putain
Installez le compilateur Rust ici. Ajoutez ce qui suit.
DockerFiile
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
RUN set -eux; \
\
url="https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init"; \
wget "$url"; \
chmod +x rustup-init; \
./rustup-init -y --no-modify-path --default-toolchain nightly; \
rm rustup-init; \
chmod -R a+w $RUSTUP_HOME $CARGO_HOME; \
rustup --version; \
cargo --version; \
rustc --version;
RUN apt-get update && apt-get install -y lldb python3-minimal libpython3.7 python3-dev gcc \
&& apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts
Comme le contenu
La raison pour laquelle le Rust à installer est ** tous les soirs ** sera décrite plus tard. Rust Le fichier créé côté Rust cette fois est le suivant
tree
workspace
│ Cargo.toml
│
├─sample-jna
│ │ Cargo.toml
│ │
│ └─src
│ lib.rs
│
└─scripts
cargo-build.sh
C'est parce que je voulais utiliser la commande cargo au plus haut niveau de l'espace de travail.
Explication ci-dessous Cargo.toml
Cargo.toml
[workspace]
members = ["sample-jna"]
[profile.release]
lto = true
Les deux premières lignes reconnaissent le répertoire sample-jna dans l'espace de travail en tant que projet. ** lto = true ** est une option pour réduire la taille du fichier pendant la construction.
sample-jna/Cargo.toml
Cargo.toml
[package]
name = "sample-jna"
version = "0.1.0"
authors = ["uesugi6111 <[email protected]>"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
** [package] ** est créé avec ** cargo new **, donc il n'y a pas de problème. ** crate-type ** of ** [lib] ** est le type après compilation. La bibliothèque dynamique supposée à appeler depuis un autre langage est écrite dans Reference pour spécifier ** cdylib **. , Suis le.
lib.rs C'est le corps principal de la bibliothèque. Cette fois, j'ai préparé un programme qui énumère les nombres premiers jusqu'à l'argument avec un algorithme similaire au tamis Eratostenes que j'ai écrit auparavant et renvoie le nombre.
lib.rs
#[no_mangle]
pub extern fn sieve_liner(n: usize) -> usize{
let mut primes = vec![];
let mut d = vec![0usize; n + 1];
for i in 2..n + 1 {
if d[i] == 0 {
primes.push(i);
d[i] = i;
}
for p in &primes {
if p * i > n {
break;
}
d[*p * i] = *p;
}
}
primes.len()
}
Dans une compilation normale, le nom de la fonction est converti en un autre nom, et lorsque vous l'appelez à partir d'un autre programme, vous ne connaîtrez pas le nom. Pour éviter cela, ajoutez ** # [no_mangle] ** à la fonction.
cargo-build.sh
cargo-build.sh
#!/bin/bash
cargo build --release -Z unstable-options --out-dir ./src/main/resources
Ce sera le script de construction de la bibliothèque. --release Spécifie la construction avec l'option release. -Z unstable-options --out-dir ./src/main/resources C'est une option pour spécifier le répertoire à afficher après la construction. Cependant, cette option n'est disponible que pour ** tous les soirs **. Par conséquent, ** nightly ** est spécifié pour une installation dans l'environnement créé avec Docker.
La destination du répertoire est définie sur l'emplacement où il sera placé dans le fichier jar lors de sa compilation côté Java.
Java Le fichier créé côté Java est le suivant.
workspace
│ pom.xml
└─src
└─main
├─java
│ └─com
│ └─mycompany
│ └─app
│ App.java
│
└─resources
Le répertoire est profond, mais cela ne veut rien dire.
pom.xml
Ajoutez ce qui suit à ** \
pom.xml
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.6.0</version>
</dependency>
App.java
App.java
package com.mycompany.app;
import java.util.ArrayList;
import java.util.List;
import com.sun.jna.Library;
import com.sun.jna.Native;
public class App {
private static final int N = 100000000;
public interface SampleJna extends Library {
SampleJna INSTANCE = Native.load("/libsample_jna.so", SampleJna.class);
int sieve_liner(int value);
};
public static void main(String[] args) {
System.out.println("N = " + N);
System.out.println("FFI :" + executeFFI(N) + "ms");
System.out.println("Java :" + executeJava(N) + "ms");
}
public static long executeFFI(int n) {
long startTime = System.currentTimeMillis();
SampleJna.INSTANCE.sieve_liner(n);
return System.currentTimeMillis() - startTime;
}
public static long executeJava(int n) {
long startTime = System.currentTimeMillis();
sieveLiner(n);
return System.currentTimeMillis() - startTime;
}
public static int sieveLiner(int n) {
List<Integer> primes = new ArrayList<>();
int d[] = new int[n + 1];
for (int i = 2; i < n + 1; ++i) {
if (d[i] == 0) {
primes.add(i);
d[i] = i;
}
for (int p : primes) {
if (p * i > n) {
break;
}
d[p * i] = p;
}
}
return primes.size();
}
}
Implémentez la même logique que celle implémentée côté Rust et comparez les temps d'exécution. Appeler la bibliothèque SampleJna INSTANCE = Native.load("/libsample_jna.so", SampleJna.class); Je le fais à. Il semble être décrit sous la forme de Native.load (Chemin de la bibliothèque, interface qui définit la bibliothèque). Cette fois, la bibliothèque sera placée directement sous main / resources, elle est donc écrite avec un chemin absolu (?).
Maven Jusque-là, vous pouvez vérifier le fonctionnement, mais je l'ai également défini lorsque j'ai pensé en faire un pot. Flux pour créer un pot --Compilez Rust et placez-le dans le répertoire des ressources du côté Java --Compilation côté Java
Cela se fait en une seule action en utilisant les fonctions de Maven.
maven-assembly-plugin Inclure les bibliothèques dépendantes dans jar
maven-assembly-plugin
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>/</classpathPrefix>
<mainClass>com.mycompany.app.App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
exec-maven-plugin Requis pour exécuter des scripts shell pendant le traitement maven.
exec-maven-plugin
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<id>dependencies</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<workingDirectory>${project.basedir}</workingDirectory>
<executable>${project.basedir}/scripts/cargo-build.sh </executable>
</configuration>
</execution>
</executions>
</plugin>
Un petit commentaire phase Définissez la durée d'exécution du script shell. Maven a un concept de cycle de vie, alors spécifiez celui qui correspond à la synchronisation que vous souhaitez exécuter. Référence executable Spécifiez ici la cible que vous souhaitez exécuter.
pom.xml Fichiers qui ont été adaptés jusqu'à présent
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>my-app</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.6.0</version>
</dependency>
</dependencies>
<properties>
<jdk.version>11</jdk.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>/</classpathPrefix>
<mainClass>com.mycompany.app.App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<id>dependencies</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<workingDirectory>${project.basedir}</workingDirectory>
<executable>${project.basedir}/scripts/cargo-build.sh </executable>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Procédez comme suit à la racine de l'espace de travail
mvn package
ensuite
[INFO] --- maven-assembly-plugin:3.3.0:single (make-assembly) @ my-app ---
[INFO] Building jar: /workspace/target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
Un journal comme celui-ci est généré et la compilation est terminée.
Après cela, exécutez la sortie jar dans le chemin affiché.
Exemple
java -jar ./target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar
production
N = 100000000
FFI :1668ms
Java :3663ms
Le temps (ms) de Java et FFI (Rust), qui a été pris en comptant le nombre de nombres premiers jusqu'à 10 ^ 8, a été affiché. Je ne sais pas s'il y a beaucoup de frais généraux car Java est plus rapide avec N plus petit.
Pour le moment, il a déménagé, il ne sera donc terminé qu'une fois. Ce sera la source utilisée https://github.com/uesugi6111/java-rust
Il y a beaucoup d'histoires auxquelles je ne touche généralement pas, et je ne comprends toujours pas grand-chose, mais je vais les étudier petit à petit.
Recommended Posts