Ruby / Rust-Kooperation (5) Numerische Berechnung mit Rutie ② Veggie

Artikelserie

Einführung

Letztes Mal habe ich Rutie verwendet, das Ruby und Rust verbindet, um eine einfache numerische Berechnungsfunktion von Rust von Ruby aufzurufen. In Bezug auf die Geschwindigkeit war es viel langsamer, als Ruby die gleiche Berechnung durchführen zu lassen. Dies liegt wahrscheinlich daran, dass die Kosten für den Aufruf von Rust-Funktionen von Ruby angemessen waren. Im Vergleich zu den Kosten war die numerische Berechnung, die ich durchführen musste, zu leicht.

Rufen wir mit Rutie erneut Rust von Ruby auf, um numerische Berechnungen durchzuführen. Was ist anders als beim letzten Mal

Der Ort. Insbesondere letzteres ist wichtig, und wenn dies getan werden kann, werden die Dinge, die in Zusammenarbeit mit Rust getan werden können, erheblich erweitert.

Gegenstand

Ich möchte die Bezier-Kurve berechnen. Referenz: [Begje Curve-Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%99%E3%82%B8%E3%82%A7%E6%9B%B2%E7%B7% 9A)

Für ein kubisches Gemüse geben Sie vier Punkte in der Ebene $ \ boldsymbol {p} _0 $, $ \ boldsymbol {p} _1 $, $ \ boldsymbol {p} _2 $, $ \ boldsymbol {p} _3 $ Die Kurve ist entschieden. Diese Kurve wird wie folgt als Mediatorvariable angezeigt.

\boldsymbol{p}(t) = (1-t)^3 \boldsymbol{p}_0 + 3t(1-t)^2 \boldsymbol{p}_1 + 3t^2(1-t) \boldsymbol{p}_2 + t^3 \boldsymbol{p}_3 \quad\quad (0 \leqq t \leqq 1)

Wie Sie sofort sehen können, ist es bei $ t = 0 $ $ \ boldsymbol p_0 $ und bei $ t = 1 $ $ \ boldsymbol p_3 $. Mit anderen Worten, es ist eine Kurve, die bei $ \ boldsymbol p_0 $ beginnt und bei $ \ boldsymbol p_3 $ endet. $ \ Boldsymbol {p} _1 $ und $ \ boldsymbol {p} _2 $ bestehen im Allgemeinen nicht (können je nach Bedingungen bestehen).

$ \ Boldsymbol p_1 $ und $ \ boldsymbol p_2 $ werden auch Kontrollpunkte genannt, $ \ boldsymbol p_1- \ boldsymbol p_0 $ ist ein Tangentenvektor bei $ t = 0 $ und $ \ boldsymbol p_3- \ boldsymbol p_2 $ ist Es ist ein Tangentenvektor bei $ t = 1 $.

Was ich jetzt tun möchte, ist die Position $ \ boldsymbol p (t) $ bei jedem $ t $, wenn $ \ boldsymbol p_0 $, $ \ boldsymbol p_1 $, $ \ boldsymbol p_2 $, $ \ boldsymbol p_3 $ gegeben ist. Bekommen.

Da die $ x $ - und $ y $ -Koordinaten nicht miteinander in Beziehung stehen, gilt für $ a_0 $, $ a_1 $, $ a_2 $, $ a_3 $,

B(t) = (1-t)^3 a_0 + 3t(1-t)^2 a_1 + 3t^2(1-t) a_2 + t^3 a_3

Sie können sich eine Funktion des Formulars vorstellen. Bereiten Sie dies für $ x $ -Koordinaten bzw. $ y $ -Koordinaten vor.

Diese Funktion nennt man das Bernstein-Polynom des Grades [^ bern]. Mit anderen Worten, das Thema lautet diesmal "Berechnen Sie den Wert des Bernstein-Polynoms vom Grad 3".

[^ bern]: Der Name dieses Polymorphs wird manchmal als "Bernstein polyploid" im englischen Stil oder als "Bernsch ** ta ** in polypoly" im deutschen Stil geschrieben, ist aber nach dem ehemaligen Mathematiker der Sowjetunion Бернштейн benannt. Daher wurde es im russischen Stil als "Bernsch ** te ** in Polypoly" gesetzt. Wikipedia [Sergei Bernstein](https://ja.wikipedia.org/wiki/%E3%82%BB%E3%83%AB%E3%82%B2%E3%82%A4%E3%83 % BB% E3% 83% 99% E3% 83% AB% E3% 83% B3% E3% 82% B7% E3% 83% A5% E3% 83% 86% E3% 82% A4% E3% 83% B3 ), Der Geburtsort ist Odessa, eine Stadt am Schwarzen Meer. Es ist derzeit die Republik Ukraine, aber es scheint, dass es zu dieser Zeit das russische Reich war. Dieser Nachname ist auf Idisch (jüdisch deutsch) und bedeutet Bernstein ko </ rt> Bernstein haku </ rt> </ ruby>.

Politik

Für verschiedene $ t $ berechnen wir mit demselben Koeffizienten ($ a_0 $, $ a_1 $, $ a_2 $, $ a_3 $). Erstellen wir also eine Klasse. Geben Sie einen Koeffizienten an, um eine Instanz zu erstellen, und geben Sie dann $ t $ an, um den Wert des Polypolys zu berechnen.

Nennen wir die Klasse CubicBezier. Nein, ich mache Bernstein-Polynomberechnungen, daher ist CubicBernstein vielleicht besser geeignet, aber "Bezier" ist besser.

Bei Implementierung in Ruby

class CubicBezier
  def initialize(a0, a1, a2, a3)
    @a0, @a1, @a2, @a3 = a0, a1, a2, a3
  end
  
  def value_at(t)
    s = 1 - t
    @a0 * s * s * s + 3 * @a1 * t * s * s + 3 * @a2 * t * t * s + @a3 * t * t * t
  end

  alias [] value_at
end

Es fühlt sich an wie

Der Grund, warum der Alias "[]" auf "value_at" angewendet wird, ist, dass es eher wie Ruby erscheint, mit "[]" zu berechnen.

Wie auch immer, ich werde eine Klasse mit der gleichen Funktion in Rutie implementieren.

Implementierung: Rostseite

So implementieren Sie eine Klasse in Rutie mit Instanzvariablen. Glücklicherweise enthält Ruties Code Erklärungen und Beispiele, also habe ich versucht, Fehler zu machen, während ich sie mir ansah, und ich habe etwas gefunden, das funktioniert hat. Ich verstehe die Theorie nicht.

Bis zur Bearbeitung von Cargo.toml

Das Gleiche wie vorher

cargo new cubic_bezier --lib

Ich werde das machen. Und auf Cargo.toml

Cargo.toml


[dependencies]
lazy_static = "1.4.0"
rutie = "0.8.1"

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

Einstellen. Diesmal brauchen wir eine Lazy_static-Kiste.

(Ergänzung 2020-10-01) Die Version von Rutie wurde auf "0.7.0" gesetzt, aber auf die neueste Version "0.8.1" geändert. Dadurch wird die Warnung über den Namen "CubicBezier" beim Kompilieren mit Rust 1.46 entfernt. Bitte lassen Sie mich wissen, wenn es eine Person gibt, die sagt "Ich konnte mit 0.7.0 kompilieren, aber nicht mit 0.8.1".

Körper

Politik

Ich bin mir überhaupt nicht sicher, aber bei der Definition einer Ruby-Klasse, die Instanzvariablen mit Rutie verwendet, scheint die Methode darin zu bestehen, eine Rust-Struktur (struct) vorzubereiten und sie zu verpacken (hier zu verpacken). Ich bin mir nicht sicher, was es bedeutet. In diesem Fall möchten wir eine Ruby-Klasse namens CubicBezier erstellen, die die Instanzvariablen "a0", "a1", "a2" und "a3" enthält. Daher definieren wir zunächst eine Struktur mit solchen Feldern. Wenn der Name der Struktur CubicBezier ist, wird sie überfordert sein, sodass ich keine andere Wahl habe, als RustCubicBezier zu verwenden. Definieren Sie CubicBezier, um es zu verpacken.

Code

Hier ist der gesamte Code.

src/lib.rs


#[macro_use]
extern crate lazy_static;

#[macro_use]
extern crate rutie;

use rutie::{Object, Class, Float};

pub struct RustCubicBezier {
    a0: f64,
    a1: f64,
    a2: f64,
    a3: f64,
}

impl RustCubicBezier {
    pub fn value_at(&self, t: f64) -> f64 {
        let s = 1.0 - t;
        self.a0 * s * s * s + 3.0 * self.a1 * t * s * s + 3.0 * self.a2 * t * t * s + self.a3 * t * t * t
    }
}

wrappable_struct!(RustCubicBezier, CubicBezierWrapper, CUBIC_BEZIER_WRAPPER);

class!(CubicBezier);

methods!(
    CubicBezier,
    rtself,

    fn cubic_bezier_new(a0: Float, a1: Float, a2: Float, a3: Float) -> CubicBezier {
        let a0 = a0.unwrap().to_f64();
        let a1 = a1.unwrap().to_f64();
        let a2 = a2.unwrap().to_f64();
        let a3 = a3.unwrap().to_f64();

        let rcb = RustCubicBezier{a0: a0, a1: a1, a2: a2, a3: a3};

        Class::from_existing("CubicBezier").wrap_data(rcb, &*CUBIC_BEZIER_WRAPPER)
    }

    fn value_at(t: Float) -> Float {
        let t = t.unwrap().to_f64();
        Float::new(rtself.get_data(&*CUBIC_BEZIER_WRAPPER).value_at(t))
    }
);

#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn Init_cubic_bezier() {
    Class::new("CubicBezier", None).define(|klass| {
        klass.def_self("new", cubic_bezier_new);
        klass.def("value_at", value_at);
        klass.def("[]", value_at);
    });
}

In den folgenden Abschnitten werden jedem Teil Erklärungen hinzugefügt.

RustCubicBezier

Definition von Strukturen und ihren Methoden:

pub struct RustCubicBezier {
    a0: f64,
    a1: f64,
    a2: f64,
    a3: f64,
}

impl RustCubicBezier {
    pub fn value_at(&self, t: f64) -> f64 {
        let s = 1.0 - t;
        self.a0 * s * s * s + 3.0 * self.a1 * t * s * s + 3.0 * self.a2 * t * t * s + self.a3 * t * t * t
    }
}

Ich denke nicht, dass die Definition von "RustCubicBezier" viel Erklärung braucht.

Rostfunktionen funktionieren als Methoden, wenn das erste Argument als "& self" definiert ist.

Verpackung

Dies ist der Teil, den ich nicht wirklich verstehe.

wrappable_struct!(RustCubicBezier, CubicBezierWrapper, CUBIC_BEZIER_WRAPPER);

Es scheint die Beziehung zwischen der zuvor definierten Struktur RustCubicBezier und dem Wrapper zu zeigen.

Die Dokumentation für das Makro wrappable_struct! Finden Sie hier: rutie :: wrappable_struct --Rust (Rutie 0.7.0-Version)

(Ergänzung 2020-10-01) Die neueste Version von Rutie ist derzeit 0.8.1, aber aus irgendeinem Grund hat die Version 0.8 keine Dokumente generiert. Da die Seite nicht vorhanden ist, bleibt der Link zum Dokument in Version 0.7.0.

Ich habe das Gefühl, dass es irgendwie sagt, dass die Struktur von Rust mit einem Ruby-Objekt umwickelt werden kann (ich kann nicht gut Englisch).

Das erste Argument scheint den Namen der Rust-Struktur anzugeben, die Sie umschließen möchten. Es scheint, dass diese Struktur öffentlich sein muss, also habe ich "pub" hinzugefügt, als ich sie zuvor definiert habe. Das zweite Argument scheint der Name der Struktur (Wrapper) zum Umschließen des ersten Arguments zu sein. Diese Struktur wird automatisch vom Makro definiert. In diesem Code wird der als zweites Argument angegebene CubicBezierWrapper jedoch nirgendwo anders angezeigt. Das dritte Argument ist der Name der statischen Variablen [^ include], die den Wrapper enthält.

[^ enthalten]: Da es im Originaltext "enthalten" war, war es "enthalten", aber bedeutet es einfach "als Wert haben" (~ wird zugewiesen)?

♪ Leitender Ingenieur, der aufgehört hat zu lernen
♪ Du bist tot
♪ Letzte Chance spannend zu lernen
♪ Wiederholen Sie Slice Rust Chunk
♪ Schimpfe mit mir, Dämonen-Compiler
♪ Mein Kopf ist schon in Schwierigkeiten

Nein, das ist bei Wrappern nicht der Fall [^ wrapper].

[^ wrapper]: Ich weiß nichts über Hip Hop, der mit Musik nicht vertraut ist, also weiß ich nicht, ob die Texte von Rap so sind, aber ich habe sie in Text geschrieben.

Klassen- und Methodendefinitionen

Erste Klasse. Das ist einfach.

class!(CubicBezier);

Dann die Methode.

methods!(
    CubicBezier,
    rtself,

    fn cubic_bezier_new(a0: Float, a1: Float, a2: Float, a3: Float) -> CubicBezier {
        let a0 = a0.unwrap().to_f64();
        let a1 = a1.unwrap().to_f64();
        let a2 = a2.unwrap().to_f64();
        let a3 = a3.unwrap().to_f64();

        let rcb = RustCubicBezier{a0: a0, a1: a1, a2: a2, a3: a3};

        Class::from_existing("CubicBezier").wrap_data(rcb, &*CUBIC_BEZIER_WRAPPER)
    }

    fn value_at(t: Float) -> Float {
        let t = t.unwrap().to_f64();
        Float::new(rtself.get_data(&*CUBIC_BEZIER_WRAPPER).value_at(t))
    }
);

Klicken Sie hier für die Definition des Makros Methoden!: https://docs.rs/rutie/0.7.0/src/rutie/dsl.rs.html#356-398

Die Bedeutung des zweiten Arguments des "Methoden!" - Makros, das ich beim letzten Mal nicht verstanden habe, schien vage verstanden zu sein (später beschrieben).

Hier werden zwei Methoden definiert.

cubic_bezier_new erstellt eine Instanz (diese wird new).

value_at berechnet den Wert der Bernstein-Funktion für t.

Wie ich letztes Mal geschrieben habe, ist diese Funktionsdefinition ein Argument des Methoden! Makros und keine Funktion von Rust wie sie ist. Es wird eine Funktionsdefinition von Rust durch die Funktion des Makros, aber zu diesem Zeitpunkt mache ich es. Wenn Sie mit Rust-Makros vertraut sind, können Sie dies wahrscheinlich anhand des obigen Links verstehen.

In cubic_bezier_new wird basierend auf dem Argument eine Struktur vom Typ RustCubicBezier generiert, die umbrochen werden soll. Von der letzten Zeile

Class::from_existing("CubicBezier").wrap_data(rcb, &*CUBIC_BEZIER_WRAPPER)

Aber da bin ich mir auch nicht sicher. Class :: from_existing (" CubicBezier ") scheint die CubicBezier -Klasse kurz zu bekommen. Ist es so etwas wie "const_get (" CubicBezier ")" in Ruby?

Die Dokumentation für wrap_data ist hier (eines Tages lesen): rutie::Class - Rust

value_at ist viel einfacher zu verstehen. Die Leber

rtself.get_data(&*CUBIC_BEZIER_WRAPPER)

Apropos. An diesem Punkt kam endlich das zweite Argument "rtself" der "Methoden!" Macro heraus. Dieser Ausdruck scheint eine umschlossene RustCubicBezier-Struktur zurückzugeben. rtself ist wahrscheinlich eine Ruby-ähnliche Rolle.

Definition der Initialisierungsfunktion

Die gleiche Note wie beim letzten Mal. Die "Initialisierungsfunktion" wird von mir vorläufig benannt und ist möglicherweise nicht geeignet.

#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn Init_cubic_bezier() {
    Class::new("CubicBezier", None).define(|klass| {
        klass.def_self("new", cubic_bezier_new);
        klass.def("value_at", value_at);
        klass.def("[]", value_at);
    });
}

Es definiert eine Funktion, die von außen aufgerufen werden kann und den Namen "Init_cubic_bezier" trägt. Vielleicht können Sie auf diese Weise tatsächlich Ruby-Klassen und -Methoden erstellen.

Es scheint "def_self" für Klassenmethoden zu verwenden (dies ist das gleiche wie beim letzten Mal) und "def" für beispielsweise Methoden. Für "value_at" auf der Rost-Seite werden "value_at" und "[]" auf der Ruby-Seite zugewiesen. Jetzt sind CubicBezier # value_at und CubicBezier # [] wie Aliase.

Hmmm, es gibt viele Dinge, die ich nicht verstehe, aber ich habe es geschafft, den Code auf der Rust-Seite zu erhalten, indem ich mich auf den Beispielcode bezog. Der Code, der "das Prinzip nicht versteht, aber etwas kombiniert, das irgendwie verwendet werden kann", ähnelt einer 999 drei neun </ ruby> -Lokomotive [^ g999].

[^ g999]: Die Lokomotive des Galaxy Super Express 999, die in "Galaxy Railroad 999" von Reiji Matsumoto erscheint, ist eine Technologie einer unbekannten Zivilisation, die aus den Ruinen des Weltraums entdeckt wurde (ich kenne den Inhalt noch nicht, kann aber irgendwie verwendet werden. ) Wird kombiniert. Es muss eine solche Einstellung gewesen sein.

kompilieren

Im Stammverzeichnis des Projekts

cargo build --release

Dann können Sie target / release / libmy_rutie_math.dylib erstellen. Die Erweiterung ist jedoch wahrscheinlich ".so" unter Linux und ".dll" unter Windows (".dylib" ist für macOS). Diese Datei ist die einzige, die auf der Ruby-Seite verwendet wird.

Implementierung: Ruby Seite

Der Code auf der Rust-Seite war etwas verwirrend, während der Code auf der Ruby-Seite ziemlich einfach war.

Der folgende Code befindet sich nach wie vor im Stammverzeichnis des Rust-Projekts. (Wenn nicht, nehmen Sie das zweite Argument der init -Methode (oder den lib_path von Rutie.new) entsprechend.

require "rutie"

Rutie.new(:cubic_bezier, lib_path: "target/release").init "Init_cubic_bezier", __dir__

cb = CubicBezier.new(1.0, 2.0, 1.5, 0.0)
0.0.step(1, by: 0.1) do |t|
  puts cb[t]
end

Jetzt können Sie den kubischen Bezier in Ruby berechnen. Wenn Sie es richtig verwenden möchten, tragen Sie es nicht wie oben seitlich, sondern legen Sie es in "Gemfile"

Gemfile


gem "rutie", "~> 0.0.4"

Schreiben Sie so etwas wie "Bundle.require".

Bonus: Zeichnen wir ein Bild der Bezier-Kurve

Nachdem Sie die Bezier-Kurve berechnet haben, zeichnen wir ein Bild. Verwenden Sie Kairo.

require "rutie"
require "cairo"

Rutie.new(:cubic_bezier, lib_path: "target/release").init "Init_cubic_bezier", __dir__

size = 400

surface = Cairo::ImageSurface.new Cairo::FORMAT_RGB24, size, size
context = Cairo::Context.new surface

context.rectangle 0, 0, size, size
context.set_source_color :white
context.fill

points = [[50, 100], [100, 300], [300, 350], [350, 50]]

bezier_x = CubicBezier.new(*points.map{ |x, _| x.to_f })
bezier_y = CubicBezier.new(*points.map{ |_, y| y.to_f })

context.set_source_color :gray
context.set_line_width 2
context.move_to(*points[0])
context.line_to(*points[1])
context.move_to(*points[2])
context.line_to(*points[3])
context.stroke

n = 100 #Abteilungsnummer
context.set_source_color :orange
(1...n).each do |i|
  t = i.fdiv(n)
  context.circle bezier_x[t], bezier_y[t], 1.5
  context.fill
end

context.set_source_color :red
points.each do |x, y|
  context.circle x, y, 4
  context.fill
end

surface.write_to_png "bezier.png "

Die Erklärung wird weggelassen (Fragen sind willkommen). Ich habe so ein Bild gemacht. bezier.png

Die roten Punkte sind die vier Punkte, die die kubische Bezier-Kurve definieren. Die graue Linie ist der Tangentenvektor. Die kleinen orangefarbenen Punkte sind die Punkte auf der Bezier-Kurve, die mit CubicBezier # [] berechnet wurden. Wenn Sie sich diese orangefarbenen Punkte ansehen, sehen Sie, dass "Oh, ich denke, ich kann es richtig berechnen."

Prüfstandstest

Jetzt ist es Zeit für den Benchmark-Test. Erstens bestand einer der Zwecke dieses Versuchs darin, ein Beispiel für die Beschleunigung mit Rust zu finden.

Ich werde wieder Benchmark_driver verwenden, wenn Sie es noch nicht installiert haben

gem i benchmark_driver

Installieren mit.

Testcode

require "benchmark_driver"

Benchmark.driver do |r|
  r.prelude <<~PRELUDE
    require "rutie"
    Rutie.new(:cubic_bezier, lib_path: "target/release").init "Init_cubic_bezier", "#{__dir__}"

    class RubyCubicBezier
      def initialize(x0, x1, x2, x3)
        @x0, @x1, @x2, @x3 = x0, x1, x2, x3
      end

      def [](t)
        s = 1.0 - t
        @x0 * s * s * s + 3.0 * @x1 * t * s * s + 3.0 * @x2 * t * t * s + @x3 * t * t * t
      end
    end

    xs = [0.12, 0.48, 0.81, 0.95]
    rust_cubic_bezier = CubicBezier.new(*xs)
    ruby_cubic_bezier = RubyCubicBezier.new(*xs)
  PRELUDE

  r.report "rust_cubic_bezier[0.78]"
  r.report "ruby_cubic_bezier[0.78]"
end

Führen Sie dies aus und vergleichen Sie die Ruby-Implementierung mit der Rust-Implementierung. Um ehrlich zu sein, besteht eine gute Chance, dass die Rust-Version langsamer ist, da Rust von Ruby aus aufgerufen wird.

Das Ergebnis ist:

rust_cubic_bezier[0.78]:   6731741.2 i/s
ruby_cubic_bezier[0.78]:   4733084.6 i/s - 1.42x  slower

Oder ich habe gewonnen, die Rust-Version hat gewonnen! Hyahoi! !!

Nun, es ist ungefähr 1,4-mal schneller, also ist es keine große Sache. Deutlich sein. Ich halte es jedoch für sinnvoll zu zeigen, dass auch eine solche (relativ einfache) Funktion mit Rust beschleunigt werden kann. Ich hoffte auch, dass dies mit Dutzenden von Zeilen Rust-Code erreicht werden könnte.

In Zukunft möchte ich die Praktikabilität untersuchen, indem ich Rust verschiedene Prozesse ausführen lasse.

Recommended Posts

Ruby / Rust-Kooperation (5) Numerische Berechnung mit Rutie ② Veggie
Ruby / Rust-Verknüpfung (4) Numerische Berechnung mit Rutie
Ruby / Rust-Kooperation (3) Numerische Berechnung mit FFI
Rubin / Rost-Kooperation (6) Extraktion der Morphologie
Objektorientierte numerische Berechnung
Bedingte numerische Berechnung