Führen Sie Rust von Java mit JNA (Java Native Access) aus.

Einführung

Motivation

Beim Anhören von Rust, der Systemprogrammiersprache (?), Gibt es einige Geschichten über FFI (Schnittstelle für Fremdfunktionen). Ich werde. Also nenne ich es von Java, das ich normalerweise schreibe.

Zielgruppe

Diejenigen, die Java und Rust ein wenig lesen können und wissen, dass es für Java ein Projektmanagement-Tool namens Maven gibt. Es ist nicht schwierig, es geht um die Ebene der Erstellung einer Vorlage für das Projekt.

Umgebung

Die Entwicklungsumgebung verwendet VS-Code. Die Rust- und Java-Ausführungsumgebung wurde mit Docker erstellt. DockerFiile Die Basis ist ein Beispiel einer Java-Entwicklungsumgebung (Debean 10), die von Microsoft bereitgestellt, gespalten und für mich selbst gespielt wurde. Die einzige Änderung ist, dass die Java-Version von 14 auf 11 geändert wurde. Fucking

Installieren Sie dort den Rust-Compiler. Fügen Sie Folgendes hinzu.

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

Wie der Inhalt

Der Grund, warum der zu installierende Rost ** nächtlich ** ist, wird später beschrieben. Rust Die diesmal auf der Rost-Seite erstellte Datei lautet wie folgt

tree


workspace
│  Cargo.toml
│
├─sample-jna
│  │  Cargo.toml
│  │
│  └─src
│          lib.rs
│
└─scripts
       cargo-build.sh

Dies liegt daran, dass ich den Frachtbefehl auf der obersten Ebene des Arbeitsbereichs verwenden wollte.

Erklärung unten Cargo.toml

Cargo.toml


[workspace]
members = ["sample-jna"]

[profile.release]
lto = true

In den beiden oberen Zeilen wird das Verzeichnis sample-jna im Arbeitsbereich als Projekt erkannt. ** lto = true ** ist eine Option zum Reduzieren der Dateigröße während des Builds.

sample-jna/Cargo.toml

Cargo.toml


[package]
name = "sample-jna"
version = "0.1.0"
authors = ["uesugi6111 <[email protected]>"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

** [Paket] ** wird mit ** Ladung neu ** erstellt, es gibt also kein Problem. ** Kistentyp ** von ** [lib] ** ist der Typ nach der Kompilierung. Die angenommene dynamische Bibliothek, die aus einer anderen Sprache aufgerufen werden soll, wird in Referenz geschrieben, um ** cdylib ** anzugeben. , Folge es.

lib.rs Dies ist der Hauptteil der Bibliothek. Dieses Mal habe ich ein Programm vorbereitet, das die Primzahlen bis zum Argument mit einem Algorithmus auflistet, der dem zuvor geschriebenen Eratostenes-Sieb ähnelt und die Zahl zurückgibt.

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() 
}

Bei der normalen Kompilierung wird der Funktionsname in einen anderen Namen konvertiert, und wenn Sie ihn von einem anderen Programm aus aufrufen, kennen Sie den Namen nicht. Um dies zu verhindern, fügen Sie der Funktion ** # [no_mangle] ** hinzu.

cargo-build.sh

cargo-build.sh


#!/bin/bash
cargo build --release -Z unstable-options --out-dir ./src/main/resources

Es wird das Build-Skript der Bibliothek sein. --release Gibt den Build mit der Release-Option an. -Z unstable-options --out-dir ./src/main/resources Es ist eine Option, das Verzeichnis anzugeben, das nach dem Erstellen ausgegeben werden soll. Diese Option ist jedoch nur für ** nächtliche ** verfügbar. Daher ist ** nightly ** für die Installation in einer mit Docker erstellten Umgebung angegeben.

Das Ziel des Verzeichnisses wird auf den Speicherort festgelegt, an dem es in der JAR-Datei abgelegt wird, wenn es auf der Java-Seite kompiliert wird.

Java Die auf der Java-Seite erstellte Datei lautet wie folgt.

workspace
│  pom.xml
└─src
   └─main
      ├─java
      │  └─com
      │      └─mycompany
      │          └─app
      │                  App.java
      │
      └─resources

Das Verzeichnis ist tief, aber es hat nichts zu bedeuten.

pom.xml Fügen Sie Folgendes zu ** \ <Abhängigkeiten> ** hinzu

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();
    }

}

Implementieren Sie dieselbe Logik wie auf der Rust-Seite und vergleichen Sie die Ausführungszeiten. Die Bibliothek anrufen SampleJna INSTANCE = Native.load("/libsample_jna.so", SampleJna.class); Ich mache es bei. Es scheint in Form von Native.load (Pfad der Bibliothek, Schnittstelle, die die Bibliothek definiert) beschrieben zu werden. Dieses Mal wird die Bibliothek direkt unter main / resources platziert, sodass sie mit einem absoluten Pfad (?) Geschrieben wird.

Maven Bis zu diesem Punkt können Sie den Vorgang überprüfen, aber ich habe ihn auch festgelegt, als ich darüber nachdachte, daraus ein Glas zu machen. Fließen, um Glas zu erstellen --Kompilieren Sie Rust und legen Sie es im Ressourcenverzeichnis auf der Java-Seite ab

Dies erfolgt in einer Aktion mit den Funktionen von Maven.

maven-assembly-plugin Fügen Sie abhängige Bibliotheken in jar ein

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 Erforderlich, um Shell-Skripte während der Maven-Verarbeitung auszuführen.

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>

Ein kleiner Kommentar phase  Legen Sie den Zeitpunkt für die Ausführung des Shell-Skripts fest. Maven hat ein Konzept für den Lebenszyklus. Geben Sie daher das Konzept an, das dem Zeitpunkt entspricht, den Sie ausführen möchten. Referenz executable Geben Sie hier das Ziel an, das Sie ausführen möchten.

pom.xml Bisher angepasste Dateien

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>

Lauf

Führen Sie die folgenden Schritte im Stammverzeichnis des Arbeitsbereichs aus

mvn package

Dann

[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] ------------------------------------------------------------------------

Ein solches Protokoll wird ausgegeben und die Kompilierung abgeschlossen.

Führen Sie danach die JAR-Ausgabe im angezeigten Pfad aus.

Beispiel


java -jar ./target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar

Ausgabe

N = 100000000
FFI  :1668ms
Java :3663ms

Die Zeit (ms) von Java und FFI (Rust), die durch Zählen der Anzahl der Primzahlen bis zu 10 ^ 8 benötigt wurde, wurde ausgegeben. Ich weiß nicht, ob es viel Overhead gibt, weil Java mit N kleiner schneller ist.

Letzte

Vorerst ist es umgezogen, daher wird es einmal fertiggestellt. Es wird die verwendete Quelle sein https://github.com/uesugi6111/java-rust

Es gibt viele Geschichten, die ich normalerweise nicht anfasse, und ich verstehe immer noch nicht viel, aber ich werde sie nach und nach untersuchen.

Recommended Posts

Führen Sie Rust von Java mit JNA (Java Native Access) aus.
JNA-Mustersammlung (Java Native Access)
Greifen Sie über Java auf API.AI zu
Führen Sie Batch mit Docker-Compose mit Java-Batch aus
Führen Sie Java VM mit Web Assembly aus
Code Java von Emacs mit Eclim
Führen Sie node.js von Android Java aus (Verarbeitung)
Führen Sie eine Batchdatei von Java aus
Greifen Sie über eine Java-Anwendung auf Teradata zu
Arbeiten Sie mit Google-Tabellen aus Java
Einfacher Datenbankzugriff mit Java Sql2o
Führen Sie in Java8 geschriebene Anwendungen in Java6 aus
Rufen Sie die Java-Bibliothek von C mit JNI auf
API-Integration von Java mit Jersey Client
Einführung in Java ab 0 Teil 1
Java-Entwicklung mit Codenvy: Hello World! Run
Führen Sie Java-Code von cpp auf cocos2dx aus
Zugriff auf geschützte Felder von Enkelkindern (Java / PHP)
Greifen Sie über Java mit Axis2 Enterprise WSDL auf Forec.com zu
R von Java ausführen Ich möchte rJava ausführen
Verwenden Sie native Bibliotheken von Scala über Java CPP + Java
[Java] Stellen Sie die Zeit im Browser mit jsoup ein
Textextraktion in Java aus PDF mit pdfbox-2.0.8
Verwenden Sie Matplotlib aus Java oder Scala mit Matplotlib4j
Erstellen Sie mit Gradle mit VSCode Java → Ausführen
Behandlung des Zugriffs auf Refactor-Eigenschaften mit Java Method Util
Kommen Sie mit Java-Containern in Cloud Run zurecht
Zugriffsmodifikator [Java]
[Tutorial] Eclipse herunterladen → Anwendung mit Java ausführen (Plejaden)
[Tutorial] Eclipse herunterladen → Webanwendung mit Java ausführen (Plejaden)
Rufen Sie eine Methode mit Kotlins Rückrufblock von Java aus auf
Ich habe versucht, nativen Java / Objective-C-Code von Flutter aus aufzurufen
[Hinweis] Erstellen Sie mit Docker eine Java-Umgebung von Grund auf neu
Lesen Sie Temperatur / Luftfeuchtigkeit von Raspberry Pi 3 & DHT11 mit Java ab
Versuchen Sie, Java mit GraalVM in ein natives Modul zu integrieren
Serverloses Java EE beginnend mit Quarkus und Cloud Run