Exécuter Rust depuis Java avec JNA (Java Native Access)

introduction

Motivation

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.

Public cible

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.

environnement

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>

Courir

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.

Dernier

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

Exécuter Rust depuis Java avec JNA (Java Native Access)
Collection de modèles JNA (Java Native Access)
Accédez à API.AI depuis Java
Exécuter un lot avec docker-compose avec Java batch
Exécuter Java VM avec Web Assembly
Coder Java depuis Emacs avec Eclim
Exécutez node.js depuis Android Java (traitement)
Exécuter le fichier de commandes à partir de Java
Accéder à Teradata depuis une application Java
Travailler avec des feuilles de calcul Google à partir de Java
Accès facile à la base de données avec Java Sql2o
Exécuter des applications écrites en Java8 en Java6
Appeler la bibliothèque Java à partir de C avec JNI
Intégration API de Java avec Jersey Client
Introduction à Java à partir de 0 Partie 1
Développement Java avec Codenvy: Hello World! Run
Exécutez du code Java à partir de cpp sur cocos2dx
Accéder aux champs protégés des petits-enfants (Java / PHP)
Accédez à Forec.com depuis Java en utilisant Axis2 Enterprise WSDL
Exécuter R à partir de Java Je veux exécuter rJava
Utiliser les bibliothèques natives de Scala via Java CPP + Java
[Java] Réglez l'heure depuis le navigateur avec jsoup
Extraction de texte en Java à partir de PDF avec pdfbox-2.0.8
Utilisez Matplotlib depuis Java ou Scala avec Matplotlib4j
En utilisant Gradle avec VSCode, compilez Java → exécutez
Refactoriser la gestion des accès aux propriétés avec Java Method Util
S'entendre avec les conteneurs Java dans Cloud Run
Modificateur d'accès [Java]
[Tutoriel] Télécharger Eclipse → Lancer l'application avec Java (Pléiades)
[Tutoriel] Télécharger Eclipse → Lancer l'application Web avec Java (Pléiades)
Appeler une méthode avec le bloc de rappel de Kotlin depuis Java
J'ai essayé d'appeler du code natif Java / Objective-C depuis Flutter
[Note] Créez un environnement Java à partir de zéro avec docker
Lire la température / l'humidité avec Java de Raspberry Pi 3 & DHT11
Essayez de créer Java dans un module natif avec GraalVM
Java EE sans serveur à partir de Quarkus et Cloud Run