Was ist Monomorphisierung im Kontext zu C ++?

78

Dave Hermans jüngster Vortrag in Rust sagte, dass sie diese Eigenschaft von C ++ ausgeliehen haben. Ich konnte nichts rund um das Thema finden. Kann jemand bitte erklären, was Monomorphisierung bedeutet?

unj2
quelle

Antworten:

135

Monomorphisierung bedeutet, spezielle Versionen generischer Funktionen zu generieren. Wenn ich eine Funktion schreibe, die das erste Element eines Paares extrahiert:

fn first<A, B>(pair: (A, B)) -> A {
    let (a, b) = pair;
    return a;
}

und dann rufe ich diese Funktion zweimal auf:

first((1, 2));
first(("a", "b"));

Der Compiler generiert zwei Versionen von first(), eine für Paare von Ganzzahlen und eine für Paare von Zeichenfolgen.

Der Name leitet sich vom Programmiersprachenbegriff "Polymorphismus" ab - was eine Funktion bedeutet, die mit vielen Arten von Daten umgehen kann. Monomorphisierung ist die Umwandlung von polymorphem in monomorphen Code.

Niko Matsakis
quelle
Ist es ein anderer Name für statischen Versand ?
Tshepang
9
@Tshepang Nicht wirklich, es ist eher der Unterschied zwischen C ++ - Vorlagen und Java-Generika.
Tavian Barnes
Scheint mir dasselbe zu sein wie das, was wir einfach als (implizite) Vorlagenspezialisierung in C ++ bezeichnen würden. Nicht zu verwechseln mit Monomorphismus, der das logische Gegenteil von Polymorphismus im Sinne des Umgangs mit einem Subtyp über die Schnittstelle seines Elternteils wäre.
stellarpower
17

Ich bin mir nicht sicher, ob sich noch jemand damit befasst, aber in der Rust-Dokumentation wird tatsächlich erwähnt, wie durch diesen Prozess keine Kostenabstraktion erzielt wird. Aus der Leistung von Code unter Verwendung von Generika :

Möglicherweise fragen Sie sich, ob bei Verwendung generischer Typparameter Laufzeitkosten anfallen. Die gute Nachricht ist, dass Rust Generika so implementiert, dass Ihr Code mit generischen Typen nicht langsamer ausgeführt wird als mit konkreten Typen.

Rust erreicht dies, indem es eine Monomorphisierung des Codes durchführt, der zur Kompilierungszeit Generika verwendet. Bei der Monomorphisierung wird generischer Code in spezifischen Code umgewandelt, indem die konkreten Typen ausgefüllt werden, die beim Kompilieren verwendet werden.

In diesem Prozess führt der Compiler das Gegenteil der Schritte aus, die wir zum Erstellen der generischen Funktion in Listing 10-5 verwendet haben: Der Compiler überprüft alle Stellen, an denen generischer Code aufgerufen wird, und generiert Code für die konkreten Typen, mit denen der generische Code aufgerufen wird .

Schauen wir uns an einem Beispiel an, das die Option enum der Standardbibliothek verwendet:

let integer = Some(5);
let float = Some(5.0);

Wenn Rust diesen Code kompiliert, führt er eine Monomorphisierung durch. Während dieses Vorgangs liest der Compiler die Werte, die in Optionsinstanzen verwendet wurden, und identifiziert zwei Arten von Optionen: eine ist i32 und die andere ist f64. Daher wird die generische Definition von Option in Option_i32 und Option_f64 erweitert, wodurch die generische Definition durch die spezifischen ersetzt wird.

Die monomorphisierte Version des Codes sieht wie folgt aus. Die generische Option wird durch die vom Compiler erstellten spezifischen Definitionen ersetzt:

// Filename: src/main.rs

enum Option_i32 {
    Some(i32),
    None,
}

enum Option_f64 {
    Some(f64),
    None,
}

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}

Da Rust generischen Code in Code kompiliert, der den Typ in jeder Instanz angibt, zahlen wir keine Laufzeitkosten für die Verwendung von Generika. Wenn der Code ausgeführt wird, funktioniert er genauso, als ob wir jede Definition von Hand dupliziert hätten. Der Prozess der Monomorphisierung macht die Generika von Rust zur Laufzeit äußerst effizient.

Micheal
quelle
1
Willkommen bei Stack Overflow und vielen Dank für die Antwort. Wenn Sie eine Antwort bereitstellen, die hauptsächlich aus einem Link besteht, ist es im Allgemeinen hilfreich, den Inhalt des verlinkten Artikels kurz zusammenzufassen. Auf diese Weise bleibt Ihr Beitrag nützlich, wenn der Link unterbrochen wird. Auf diese Weise erhalten Sie auch eher positive Stimmen für Ihre Antwort.
New Pagodi
4

Ich bin mir nicht sicher. Könntest du auf das Gespräch verlinken? Es könnte eine beiläufige Bemerkung gewesen sein.

Herman könnte einen Begriff für so etwas wie eine Vorlagenspezialisierung geprägt haben, die aus der Vorlage, die eine polymorphe Struktur darstellt, Typen / Objekte erzeugt, die nicht miteinander zusammenhängen (nicht polymorph oder "monomorph").

Kartoffelklatsche
quelle
1

Es gibt eine schöne Erklärung der Monomorphisierung im Rostbuch

Bei der Monomorphisierung wird generischer Code in spezifischen Code umgewandelt, indem die konkreten Typen ausgefüllt werden, die beim Kompilieren verwendet werden.

Wenn Sie im Buchbeispiel Variablen definiert haben mit Some:

let integer = Some(5);
let float = Some(5.0);

Wenn Rust diesen Code kompiliert, führt er eine Monomorphisierung durch. Während dieses Prozesses liest der Compiler die Werte, die in Option<T> Instanzen verwendet wurden, und identifiziert zwei Arten von Option<T>: eine ist i32und die andere ist f64. Als solches erweitert es die generische Definition von Option<T>in Option_i32und ersetzt Option_f64dadurch die generische Definition durch die spezifischen.

Die monomorphisierte Version des Codes sieht wie folgt aus. Das Generikum Option<T>wird durch die vom Compiler erstellten spezifischen Definitionen ersetzt:

Dateiname: src / main.rs

enum Option_i32 {
    Some(i32),
    None,
}

enum Option_f64 {
    Some(f64),
    None,
}

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}
Wolendranh
quelle