Artikelserie
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.
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
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.
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.
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".
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.
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.
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.
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.
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
[^ 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.
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.
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".
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.
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."
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.
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