[JAVA] Open Source Programmiersprache Zickzusammenfassung

Als ich die Programmiersprache zum Schreiben von WebAssembly-Modulen recherchierte, war ich der Meinung, dass die relativ neue Open-Source-Programmiersprache zig zum Schreiben von WebAssembly-Modulen geeignet ist. Deshalb habe ich beim Schreiben und Ausführen des Quellcodes gesucht ** Sie werden in der Reihenfolge aufgelistet, in der sie im Code ** erscheinen.

Der in diesem Artikel verwendete Quellcode kann unter der folgenden URL unter macOS oder Linux erstellt werden:

Qiita unterstützt derzeit keine Hervorhebung der Zick-Code-Syntax und ist nicht sehr gut sichtbar. Auf Github wird Zig-Code jedoch auch als Syntax hervorgehoben.

0. Über die Programmiersprache zig

Die Open-Source-Programmiersprache zig ist unter der folgenden URL verfügbar.

zig ist eine in LLVM implementierte Programmiersprache, aber im Gegensatz zu C ++, Go und Rust ist die Verwendung von Heapspeicher eine Option ** wie die Sprache C, und so etwas wie "neu" ist in der Sprachsyntax enthalten. Nicht eingebaut.

Die Merkmale der Zick-Sprache sind:

Etc.

Beachten Sie jedoch, dass die Syntax und die ** Spezifikationen der Standardbibliothek noch nicht stabil sind **. Die aktuelle Version 0.4.0 und die nächste Version befinden sich in einem Stadium, in dem Inkompatibilitäten auf der Ebene der Syntax des String-Array-Typs auftreten. Im Folgenden wird in diesem Artikel alles anhand der Spezifikation ** Version 0.4.0 ** beschrieben.

Führen Sie zunächst den Befehl zig zen aus, um einen Einblick in die Richtlinien der Zickzacksprache zu erhalten:

$ zig zen

 * Communicate intent precisely.
 * Edge cases matter.
 * Favor reading code over writing code.
 * Only one obvious way to do things.
 * Runtime crashes are better than bugs.
 * Compile errors are better than runtime crashes.
 * Incremental improvements.
 * Avoid local maximums.
 * Reduce the amount one must remember.
 * Minimize energy spent on coding style.
 * Together we serve end users.

$

In der Zick-Sprache verwenden Hash-Tags wie Twitter Ziglang.

1. Installieren Sie den Befehl zig

Die Zick-Sprache ist das Hauptpaket für Homebrew, Snap, Nixos, Scoop, Chocolatey und Packman, die das Paket "Zig" ("Ziglang" in Scoop) bereitstellen. Es ist einfach, den Befehl "zig" von diesen Paketmanagern zu installieren.

Emacs Zick-Modus

Der offizielle Zick-Zack-Modus ist in MELPA registriert.

Es scheint auch einen Modus für vim, vscode, sublime zu geben:

2. Grundlagen des Befehls "Zick"

Alle Entwicklungstools werden als Unterbefehle des Befehls "zig" bereitgestellt.

Befehl zur Ausführung der Zick-Datei:

--zig run xxx.zig: Führt die main -Funktion der Quelldatei xxx.zig aus (ohne eine Binärdatei zu generieren) --zig test xxx.zig: Führt den Code in der in der Quelldatei xxx.zig beschriebenen test-Syntax der Reihe nach aus und zeigt eine Zusammenfassung der Testergebnisse an. --zig build: Führt den in der Buildbeschreibung build.zig beschriebenen Buildprozess aus

Befehle zum Kompilieren von Zick-Dateien:

--zig build-exe xxx.zig: Erzeugt ** verschiedene Ziel-Binärdateien (xxx, xxx.exe usw.) ** aus der Zick-Datei (xxx.zig). --zig build-obj xxx.zig: Aus der Zick-Datei (xxx.zig), ** C-Header-Datei ( xxx.h) und C-Objektdatei (xxx.o, xxx.obj) Usw.) ** Generieren --zig build-lib xxx.zig: Aus Zick-Datei (xxx.zig), C-Header-Datei ( xxx.h) und C-statischer Bibliotheksdatei (libxxx.a, xxx.lib usw. " ) Wird generiert --zig build-lib -dynamic xxx.zig: Aus der Zick-Datei, der C-Header-Datei (xxx.h) und der DLL-Datei ( libxxx.so, libxxx.dylib, xxx.dll und xxx.pdb etc.) etc.

Dienstprogrammbefehle:

--zig init-exe: Generieren Sie Projektvorlagen (build.zig und src / main.zig) für ausführbare Dateien in diesem Verzeichnis. --zig init-lib: Generieren Sie Projektvorlagen (build.zig und src / main.zig) für statische Bibliotheken in diesem Verzeichnis --zig fmt xxx.zig: xxx.zig wird ** mit einem Syntaxformatierer überschrieben. --zig translate-c xxx.c: Die C-Header-Datei und die einfache C-Implementierungsdatei sind zick-codiert und werden standardmäßig ausgegeben.

Befehle zur Informationsanzeige:

--zig --help: Liste der Unterbefehle und Liste der Befehlsargumente Hilfe anzeigen -- zig builtin: Zeigt das Zig-Befehl-Laufzeitumgebungsinformationsmodul@ import ("builtin")im Zick-Quellcode-Format an (es gibt Informationen wie das Build-Ziel und den Code in build.zig) Hauptsächlich verwendet in) --zig Ziele: Zeigt eine Liste von ** Build-Zielen an, die von zig build-exe usw. verarbeitet werden können. ** --zig version: Zeigt die angegebene Version des Zick-Zack-Befehls an (z. B. 0.4.0). --zig libc: Zeigt die Pfadinformationen des libc-Verzeichnisses der Ausführungsumgebung für den Zick-Befehl an. --zig zen: Zeigt die Designrichtlinie für die Zick-Sprache an

Befehlsbeispiel zig build-exe --release-fast -target wasm32-freistehend --name fft.wasm fft.zig

Diese Befehlszeile generiert aus dem Zick-Bibliothekscode eine .wasm-Moduldatei in WebAssembly. Die Befehlszeilenoptionen haben folgende Bedeutung:

---- release-fast: Gibt einen geschwindigkeitsorientierten Build an -- - relase-safe: Geschwindigkeitsorientierte, aber Trap-Verarbeitung spezifiziert eine mit Embedded erstellte ---- release-small: Gibt einen größenabhängigen Build an (ohne Traps). ---target wasm32-freestanding ist die 32-Bit-Wasm-Spezifikation von WebAssembly, die ein freistehendes Ziel für den Import angibt. ---target x84_64-windows: Geben Sie den Windows 64-Bit-Konsolenbefehl (exe) als Ziel an ---- name fft.wasm: Setzen Sie den Namen der Ausgabedatei auf fft.wasm

3. Zick-Programm: FFT-Implementierung

Als Beispiel für Code, der kompliziert genug ist, um die verschiedenen Funktionen der Sprache zu berühren, verwenden wir eine Schleifenimplementierung der Hochgeschwindigkeits-Fourier-Transformations-FFT und schreiben sie im Zickzack.

Schreiben Sie "fft.zig" als Bibliothek ohne die "main" -Funktion, schreiben Sie den Test in die "test" -Syntax und überprüfen Sie die Operation, indem Sie den Test mit "zig test fft.zig" ausführen.

Der gesamte Code von "fft.zig" umfasst weniger als 200 Zeilen einschließlich des Testcodes. Hier werden wir den Code in 7 Teile teilen, die Codeteile in der Reihenfolge von oben ausschneiden und die Funktionen und Merkmale der Zick-Sprache erklären, die in jedem Codeteil enthalten sind.


3.1. Standardbibliothek laden

Das folgende Codefragment ist der Code zum Lesen des Umfangsverhältniswerts und der sin / cos-Funktion aus der Standardbibliothek:

fft.zig(1)


const pi = @import("std").math.pi;
const sin = @import("std").math.sin;
const cos = @import("std").math.cos;

Standardbibliotheken und integrierte Funktionen

In Zick können Sie die Funktionen der ** Standardbibliothek ** von @ import (" std ") verwenden. Bezeichner, die mit "@" beginnen, wie "@ import", werden im Zick-Zack ** als integrierte Funktionen ** bezeichnet, und als Funktion dieser integrierten Funktion wird auch Typumwandlung bereitgestellt.

Variables Modell

Im Zickzack sind ** Variablen (ähnlich wie C) Modelle kontinuierlicher Speicherbereiche **.

Daher kann die Variable ** const nicht den gesamten Inhalt des Speicherbereichs ** ändern, und jedes Element im Fall einer Struktur und jedes Element im Fall eines Arrays kann nicht geändert werden. Außerdem werden Funktionsargumente im Zickzack auch als "const" -Variablen behandelt. Variablen, deren Inhalt geändert werden kann, werden mit "var" deklariert.

Typ const

zig hat einen Typ mit const. Der Typ "const" bedeutet, dass (unter Verwendung von Zeigern) Änderungsoperationen über diese ** Referenz nicht zulässig sind ** und einen anderen Effekt haben als die Variable "const".

Beispielsweise kann eine Variable mit "var a: f64 = 0.0" ihren Wert ändern und mit "var ptr: * const f64 = & a" referenziert werden. Eine Änderung dieser Referenz "ptr. * = 1.0" führt jedoch zu einem Kompilierungsfehler. (Ptr. * Bezieht sich auf den Speicherbereich an der Spitze des Zeigers im Sinne aller Elemente. * Ptr in C). Umgekehrt ist für einen Zeiger auf eine Variable mit "var a: f64 = 0.0" und einen Zeiger auf die "const" -Variable mit "const ptr: * f64 = & a" "ptr. * = 1.0" möglich.

Außerdem tritt für die Variable "const" mit "const a: f64 = 0.0" ein Kompilierungsfehler auf, es sei denn, der Zeiger vom Typ "const" ist var ptr: * const f64 = & a ".

Die Variable const zum Benennen des Typs

Die Variable "const" wird auch für das Aliasing von Typen (Variablen vom Typ "type") verwendet, z. B. "struct" / "union" / "enum" / "error". Die Zick-Typ-Prüfung wird durch ** Strukturübereinstimmung ** bestimmt, nicht durch Typnamenübereinstimmung. Daher ist es möglich, zwischen einer Variablen des Alias-Typs und einer Variablen des ursprünglichen Namenstyps zuzuweisen.

Im Code von werden die Funktionen und Variablen der Standardbibliothek verwendet, indem der Variablenname mit "const" zugewiesen wird. "@ Import (" std ")" ist jedoch ein Ausdruck und kann an einer beliebigen Stelle im Code verwendet werden.

In zig-0.4.0 sind "sin" und "cos" Funktionen, die von der Standardbibliothek bereitgestellt werden. In der nächsten Version werden sie jedoch in die integrierte Version übertragen und als "@ sin" und "@ cos" verwendet. Es scheint so als.


3.2. Definition der komplexen Zahl struct

Das folgende Codefragment ist eine Deklaration der komplexen Zahlenstruktur, die von FFT und seinem Konstruktor und ihrer Methode verarbeitet wird:

fft.zig(2)


pub export const CN = extern struct {
    pub re: f64,
    pub im: f64,
    pub fn rect(re: f64, im: f64) CN {
        return CN{.re = re, .im = im,};
    }
    pub fn expi(t: f64) CN {
        return CN{.re = cos(t), .im = sin(t),};
    }
    pub fn add(self: CN, other: CN) CN {
        return CN{.re = self.re + other.re, .im = self.im + other.im,};
    }
    pub fn sub(self: CN, other: CN) CN {
        return CN{.re = self.re - other.re, .im = self.im - other.im,};
    }
    pub fn mul(self: CN, other: CN) CN {
        return CN{
            .re = self.re * other.re - self.im * other.im,
            .im = self.re * other.im + self.im * other.re,
        };
    }
    pub fn rdiv(self: CN, re: f64) CN {
        return CN{.re = self.re / re, .im = self.im / re,};
    }
};

pub Attribut

Das pub-Attribut ist ein Attribut, das an ** Variablen und Funktionen angehängt ist, die verwendet werden, wenn ** @ import aus anderen Zick-Dateien erstellt wird (z. B. pi und sin in der Standardbibliothek). Ohne pub können Variablen und Funktionen nicht direkt verwendet werden, selbst wenn @ import ausgeführt wird.

Attribut "exportieren"

Das Attribut extport ist ein Attribut, das an ** Funktionen und Variablen angehängt ist, die aus dem ** C-Header (.h) verwendet werden. Dieses Attribut wird an das Ziel ** angehängt, das aus der Objektdatei oder Bibliotheksdatei verwendet wird.

Wenn Sie einer Funktion "export" hinzufügen, wird die dieser Funktion entsprechende Prototypdeklaration in die C-Header-Datei eingebettet. Wenn Sie einer Variablen zu einem Typ wie "struct" "export" hinzufügen, werden diese Typinformationen in die C-Header-Datei eingebettet.

Wie bei extern in C müssen Sie ** keinen Namen über Dateien hinweg ** haben.

Variation von struct

Es gibt drei Arten von "Struktur" -Typen von zig, abhängig vom Unterschied im Speicherlayout und der Handhabung beim "Export".

--struct: ** Beinhaltet die Reihenfolge der Mitglieder ** Gibt kein Speicherlayout an ** struct. Im C-Header zum Zeitpunkt des Exports wird er als Deklaration mit dem Zeiger "struct" eingebettet. --packed struct: ** Das Speicherlayout struct, das die Mitglieder in der Reihenfolge der Deklaration anordnet **. Eingebettet als struct Zeigerdeklaration in den C Header bei export -- extern struct: Das Speicherlayout struct, das die Mitglieder in der Reihenfolge der Deklaration anordnet. Im C-Header zum Zeitpunkt des Exports ist er als Elementdefinitionsdeklaration ** von ** struct ** eingebettet

Mitgliedsfunktion von struct

Im Zickzack können Sie die Funktion fn inside struct deklarieren. Diese Funktion wird in Form von "type name.function name (...)" aufgerufen. Wenn das erste Argument eine definierte "Struktur" ist, wie "add" oder "sub", können Sie es auch im ** Methodenaufruf ** -Stil (CN.add (a, b), schreiben Es kann auch als a.add (b) ) beschrieben werden.

In zig-0.4.0 gibt es, selbst wenn sich die Funktion zum Zeitpunkt des Exports im C-Header in struct befindet, kein Präfix des Strukturnamens, und der Prototyp wird mit dem Funktionsnamen deklariert, wie er ist (CN).exporting .rect () sieht nicht nach CN_rect () aus, sondern nur nach rect ()).

Numerischer Typ

Der numerische Zick-Typ gibt die Anzahl der Bits ** an. Es gibt auch einen zielabhängigen numerischen Typ für die C-Sprachschnittstelle:

Außerdem hat die rechte Seite der ** Verschiebungsoperation (>>, <<) die Anzahl der Bits -1 auf der linken Seite, wie z. B. u3, u4, u5, u6, u7 **. Verwenden Sie ganzzahlige Typen als Obergrenze (Variablen werden auf diese Typen herabgestuft).

Im Zick-Zack sind ** bool-Typen, die true und false erhalten, keine numerischen Typen **, aber sie können mithilfe der integrierten Funktion in 0/1 numerische Typen umgewandelt werden.

--type ist der Typ des Typs (einschließlich benutzerdefinierter Typen wie struct) --void ist ein wertloser Typ und auch ein Blockausdruckstyp (ohne einen Wert mit break zu übergeben) --noreturn ist der Rückgabetyp für Funktionen, die nicht normal enden, z. B. ein Prozess, der in der Mitte endet, oder eine Endlosschleife.

struct Initialisierer

Der Initialisierer (Literal) der Struktur ist Strukturname {.Mitglied Name = Wert, ...}. Die Syntax ähnelt dem Initialisierer des C99-Strukturelements ((Typ) {.member name = value, ...}).

Funktionsargumente und Rückgabewerte sind Kopien der Werte

Funktionsargumente und Rückgabewerte sind ** Kopierwerte ** Modelle. Selbst wenn es sich um eine Struktur oder ein Array handelt, wird es als Kopie behandelt. Es ist möglich, den Zeigertyp als Typ des Arguments oder des Rückgabewerts zu verwenden.

Unabhängig von der Größe des Typs wird er auf den Stapel kopiert. Wenn Sie also mit dem Array-Typ schlecht umgehen, können Sie das Stapellimit des Betriebssystems erreichen.


3.3. Testcode für Struktur CN

Das folgende Codefragment ist Code, der die deklarierte CN-Mitgliedsfunktion in der Testcodesyntax verwendet.

fft.zig(3)


test "CN" {
    const assert = @import("std").debug.assert;
    const eps = @import("std").math.f64_epsilon;
    const abs = @import("std").math.fabs;

    const a = CN.rect(1.0, 0.0);
    const b = CN.expi(pi / 2.0);
    assert(a.re == 1.0 and a.im == 0.0);
    assert(abs(b.re) < eps and abs(b.im - 1.0) < eps);
    
    const apb = a.add(b);
    const asb = a.sub(b);
    assert(abs(apb.re - 1.0) < eps and abs(apb.im - 1.0) < eps);
    assert(abs(asb.re - 1.0) < eps and abs(asb.im + 1.0) < eps);
    
    const bmb = b.mul(b);
    assert(abs(bmb.re + 1.0) < eps and abs(bmb.im) < eps);
    
    const apb2 = apb.rdiv(2.0);
    assert(abs(apb2.re - 0.5) < eps and abs(apb2.im - 0.5) < eps);
}

Test Teil

Der test" name "{...} ist der Teil, der den Code ** beschreibt, der während des Befehls ** zig test ausgeführt wird.

Ab zig-0.4.0 können Sie eine Funktion nicht direkt in test definieren. Es ist jedoch möglich, eine Funktion in "struct" auch in "test" zu deklarieren, und es ist möglich, die Funktion durch Aufrufen einer Methode zu verwenden.

Strukturmethodenaufruf

Die Funktion von "fn" in "struct" kann mit "struct name.function name ()" aufgerufen werden, insbesondere wenn das erste Argument "struct" ist, kann sie auch mit "variable name.function name ()" aufgerufen werden. Es hat die Syntax.

"A.add (b)" in diesem Code kann auch von "CN.add (a, b)" aufgerufen werden.


3.4. Testcode für CN Array

Das folgende Codefragment ist ein Testcode zur Verwendung mit einem Array von "CN".

fft.zig(4)


test "CN array" {
    const assert = @import("std").debug.assert;
    const eps = @import("std").math.f64_epsilon;
    const abs = @import("std").math.fabs;

    var cns: [2]CN = undefined;
    cns[0] = CN.rect(1.0, 0.0);
    cns[1] = CN.rect(0.0, 1.0);
    cns[0] = cns[0].add(cns[1]);
    assert(abs(cns[0].re - 1.0) < eps and abs(cns[0].im - 1.0) < eps);
}

Array von Zickzack

Der Array-Typ von Zick ist "[Anzahl der Elemente] Elementtyp", was bedeutet, ** durchgehender Bereich mit der angegebenen Anzahl von Elementen. Der mehrdimensionale Array-Typ ist "[Anzahl der äußeren Elemente] [Anzahl der inneren Elemente] Elementtyp". Im Speicherlayout sind mehrere innere Elemente mit mehreren äußeren Elementen ausgerichtet.

Wenn der Elementtyp "struct" ist, sind die Elemente in der richtigen Reihenfolge angeordnet und es wird ein Speicherlayout mit mehreren Elementen. Das Zuweisen eines Array-Elements zu einem Element überschreibt den Wert.

Die Deklaration der Uninitialisierung eines Arrays durch die Variable "var" lautet "var variablenname: [Anzahl der Elemente] Elementtyp = undefiniert". Die Anzahl der Elemente ist obligatorisch.

Der Array-Typ-Initialisierer (Literal) ist "[Anzahl der Elemente] Elementtyp {Anfangswert 0, Anfangswert 1, ....}". Sie können die gesamte Primzahl im Initialisierer weglassen. Initialisierer mit demselben Wert können auch den Elementtyp "[] {Anfangswert 0, Anfangswert 1, ...} ** Anzahl der Iterationen" haben. In diesem Fall handelt es sich um einen Array-Typ der Anzahl der Anfangswerte - der Anzahl der Elemente der Anzahl der Wiederholungen.

Die Deklaration mit dem Anfangswert des Arrays durch die Variable "var" verwendet den Initialisierer des Array-Typs und den Elementtyp "var variable name = [Anzahl der Elemente] {Anfangswert 0, Anfangswert 1, ...}". Die Deklaration mit dem Anfangswert für die Variable const``const variable name = [Anzahl der Elemente] Der Elementtyp {Anfangswert 0, Anfangswert 1, ...} wird normalerweise verwendet (Beispiel: const v = [3] u8 {1,2,3} ).

Beachten Sie, dass durch das Deklarieren einer Array-Variablen in einer Funktion ** Speicherplatz im Stapelspeicher zugewiesen wird **. Aus diesem Grund ist es bei Verwendung eines Arrays leicht, das OS-Stack-Limit (8 MB usw.) zu erreichen. Seien Sie also vorsichtig. (Die Methode zur Verwendung des Heaps usw. in der Standardbibliothek wird später beschrieben.)


3.5. Indexbitinversion für FFT

Das folgende Code-Snippet ist die Definition der Array-Index-Bit-Inversionsfunktion "revbit" in FFT und ihres Testcodes.

fft.zig(5)


fn revbit(k: u32, n0: u32) u32 {
    return @bitreverse(u32, n0) >> @truncate(u5, 32 - k);
}

test "revbit" {
    const assert = @import("std").debug.assert;
    const n: u32 = 8;
    const k: u32 = @ctz(n);
    assert(revbit(k, 0) == 0b000);
    assert(revbit(k, 1) == 0b100);
    assert(revbit(k, 2) == 0b010);
    assert(revbit(k, 3) == 0b110);
    assert(revbit(k, 4) == 0b001);
    assert(revbit(k, 5) == 0b101);
    assert(revbit(k, 6) == 0b011);
    assert(revbit(k, 7) == 0b111);
}

Eingebaute Bit-Arithmetik

In Zick sind ** CPU-Eigenbitoperationen, die durch CPU-Anweisungen bereitgestellt werden, integriert **.

--@ bitreverse (numerischer Typ, numerisch): Bit verkehrt herum (Hinweis: Der Name wird in der nächsten Version in @ bitReverse geändert) --@ ctz (numerischer Wert): Zahlen mit Nullen nach dem niedrigstwertigen Bit (Anzahl nachfolgender Nullen). Wenn es ein Potenzwert von 2 ist, ist es der gleiche Wert wie "log2 (numerischer Wert)".

Andere sind "@ clz" (Anzahl der Nullen nach dem höchstwertigen Bit), "@ popCount" (Anzahl der Einsen), "@ bswap" (verkehrt herum in Bytes) und so weiter.

Explizite Besetzung

Mit zig können Sie ** nur implizit erweitern, wenn die Anzahl der Bits für denselben numerischen Typ zunimmt. Ansonsten müssen Sie jede ** integrierte Cast-Funktion gemäß der Konvertierungsmethode ** auswählen und verwenden.

--@ truncate (Konvertierungstyp, Wert): Cast, der nur die unteren Bits auf die Anzahl der Bits des Konvertierungstyps schneidet

Bei der Verschiebungsoperation muss auf einen numerischen Typ gekürzt werden, der der Anzahl der Bits des Typs der zu verschiebenden Variablen entspricht. Da 32 2 zur 5. Potenz ist, ist es wichtig, den Typ auf "u5" zu kürzen, um den Wert des Schaltvorgangs von "u32" zu erhalten.

Es gibt viele integrierte Funktionen, aber die folgenden sind typisch

--@ bitCast (Transformationstyp, Wert): Cast, der das Bitlayout speichert. Zum Beispiel beim Konvertieren von "f64" in "u64" als Bitfeld. --@ ptrCast (Konvertierungszeigertyp, Zeigerwert): Konvertierung des Zeigertyps --@ intCast (Konvertierung des Integer-Typs, Integer-Wert): Typkonvertierung von Integer-Werten mit unterschiedlicher Anzahl von Bits --@ floatCast (Typ der konvertierenden Gleitzahl, Gleitkommazahl): Typkonvertierung der Gleitzahl mit unterschiedlicher Anzahl von Bits

--@ floatToInt (Konvertierung des Integer-Typs, Gleitkomma-Dezimalwert): Numerische Konvertierung in eine Ganzzahl --@ intToFloat (Konvertierung eines unbeweglichen Bruchteils, ganzzahliger Wert): Numerische Konvertierung in einen schwebenden Bruch --@ ptrToInt (Konvertierung des Integer-Typs, Zeigerwert): Konvertierung der Zeigeradresse in eine Ganzzahl --@ intToPtr (Konvertierungszeigertyp, ganzzahliger Wert): Konvertierung vom Zeigeradressenwert zum Zeiger --@ boolToInt (Gültigkeitswert): Konvertierung des booleschen Werts in eine Ganzzahl ( u1)


3.6. FFT-Körper

Das folgende Codefragment ist der Implementierungscode für die Schleifenversion von FFT.

fft.zig(6)


fn fftc(t0: f64, n: u32, c: [*]CN, r: [*]CN) void {
    {
        const k: u32 = @ctz(n);
        var i: u32 = 0;
        while (i < n) : (i += 1) {
            r[i] = c[@inlineCall(revbit, k, i)];
        }
    }
    var t = t0;
    var nh: u32 = 1;
    while (nh < n) : (nh <<= 1) {
        t /= 2.0;
        const nh2 = nh << 1;
        var s: u32 = 0;
        while (s < n) : (s += nh2) {
            var i: u32 = 0;
            while (i < nh) : (i += 1) {
                const li = s + i;
                const ri = li + nh;
                const re = @inlineCall(CN.mul, r[ri], @inlineCall(CN.expi, t * @intToFloat(f64, i)));
                const l = r[li];
                r[li] = @inlineCall(CN.add, l, re);
                r[ri] = @inlineCall(CN.sub, l, re);
            }
        }
    }
}
pub export fn fft(n: u32, f: [*]CN, F: [*]CN) void {
    fftc(-2.0 * pi, n, f, F);
}
pub export fn ifft(n: u32, F: [*]CN, f: [*]CN) void {
    fftc(2.0 * pi, n, F, f);
    const nf64 = @intToFloat(f64, n);
    var i: u32 = 0;
    while (i < n) : (i += 1) {
        f[i] = f[i].rdiv(nf64);
    }
}

Zeigertyp

Die Zick-Variable ist ein Speicherbereich und hat einen ** Zeigertyp **, der auf seine Startadresse zeigt. Der Zeigertyp auf den Werttyp ist ein Typ mit "" am Anfang des Typs, z. B. " u8".

In Zick können auf Zeigertypvariablen und Variablen des Typs, auf dem sie basieren, mit derselben Beschreibung zugegriffen werden **.

In Zickzack sind Arrays auch Werttypen, die kontinuierliche Speicherbereiche darstellen. Im Gegensatz zur C-Sprache werden ** Array-Typen und Zeigertypen auf Arrays klar unterschieden **.

In zig-0.4.0 wird der Array-Zeigertyp durch den Elementtyp "[*]" dargestellt. Der Array-Zeigertyp kann auch auf Elemente in der Mitte des Arrays zeigen, und wie bei C kann die Anzahl der Versatzindizes addiert oder subtrahiert werden.

Die Zeigertypvariable des Arrays kann auf die Elemente des Arrays mit derselben Beschreibung wie die Arraytypvariable zugreifen.

Andere Typen, die sich auf Array-Zeiger beziehen, sind:

-- * [Anzahl der Elemente] Elementtyp: Ein Zeiger auf das gesamte Array. Die Anzahl der Elemente (.len) ist zur Kompilierungszeit festgelegt -- [] Elementtyp: Die Anzahl der Elemente (.len) wird zur Laufzeit ermittelt ** Slice **

Ein Zick-Slice ist eine integrierte Zick-Datenstruktur, die auf einen kontinuierlichen Bereich in einem Array-Bereich verweist und über einen Array-Zeiger (.ptr) für die Startadresse und die Anzahl der Elemente ( .len) verfügt.

Zusammenfassung,

--const a: [12] u8 = [] u8 {1} ** 12; : Array-Variable. Die Anzahl der Elemente ist zur Kompilierungszeit festgelegt --const b: * const [12] u8 = & a; : Zeigervariable für das gesamte Array. Die Anzahl der Elemente ist zur Kompilierungszeit festgelegt --const c: [] const u8 = & a; : Slice-Typ. Die Anzahl der Elemente ist zur Laufzeit festgelegt --const d: [*] const u8 = & a; : Zeigervariable zum Array. Hat nicht die Anzahl der Elemente

Die drei obersten Variablen können mit "a.len", "b.len" und "c.len" abgerufen werden, aber "d" hat keine Möglichkeit, dies zu tun.

Das Folgende ist die Syntax für die Zuweisung zu anderen Slices und Array-Zeigern.

Um ein Slice aus einem Array-Zeiger (c3) zu erstellen, müssen Sie den Index von ** Endpunkt ** angeben.

Zick-Variablen können nicht im Blockbereich ausgeblendet werden

Zick-Variablen haben einen Blockbereich, aber im Gegensatz zu vielen anderen Sprachen können Variablen mit demselben Namen wie die außerhalb des Blocks definierten nicht innerhalb des Blocks definiert werden **.

Die Variable "i" wird im ersten Teil der Funktion "fftc" verwendet, um in den bitinvertierten Index zu kopieren. Wenn Sie das {``} löschen, das diesen Teil umgibt, wird ein Kompilierungsfehler angezeigt, da Sie in der folgenden Schleife i` verwenden.

Zick-Loop und Block

zig hat eine Schleife von "for" und "while".

--for Schleife: Iteratorschleife zum Array usw. --while-Schleife: Bedingte Schleife

Die "while" -Schleife weist die folgenden Variationen auf:

--while (bedingter Ausdruck) {...}: Entsprichtwhile (bedingter Ausdruck) {...}in C-Sprache --while (bedingter Ausdruck): (Ausdruck nach der Verarbeitung) {...}:for (; bedingter Ausdruck; Ausdruck nach der Verarbeitung) {...}entspricht der Sprache C.

In zig müssen ** Schleifenzählervariablen in while außerhalb der Schleife deklariert werden **.

Im Zickzack können sowohl "for" als auch "while" Ausdrücke im "else" -Teil haben, der nach dem Loopout verarbeitet wird.

Die Schleife kann einen Ergebniswert als Ausdruck haben. Der Teil "else" ist im Schleifenausdruck erforderlich, und der an den Wert "break" übergebene Wert ist der Wert des Ausdrucks.

Wenn in beiden Beispielen "cond" "wahr" ist, ist "v" "1", und wenn "falsch" ist, ist "v" "2".

Der else -Teil in Minimum Example 2 ist keine blockartige Syntax, sondern ein ** Blockausdruck **, der eine der Ausdruckssyntax von zig ist. ** Der Wert des Blockausdrucks muss in einer Bezeichnung "break" ** übergeben werden, für die Sie den Blockausdruck etwas beschriften müssen.

Inline-Funktionsaufruf

Sie können auch "inline" zu zigs "fn" hinzufügen, wodurch alle Aufrufe inline werden. Für "fn", das "inline" ist, gelten jedoch Nutzungsbeschränkungen. Beispielsweise können Sie "fn" nicht an eine Variable oder ein Argument übergeben. Im Gegensatz zur C-Sprache verursacht "inline" im Zickzack einen Kompilierungsfehler, wenn es eine Situation gibt, in der nicht einmal einer in "fn" eingefügt werden kann.

In Zick ist es möglich, ** Inline erzwingen ** für eine reguläre Funktion des Aufrufers anzugeben, anstatt alle Inline in der Definition anzugeben. Das ist der eingebaute "@ inlineCall".


3.7. FFT-Testcode

Das folgende Codefragment ist ein Testcode für die Implementierung der Schleifenversion von FFT.

`fft.zig(7)``


test "fft/ifft" {
    const warn = @import("std").debug.warn;
    const assert = @import("std").debug.assert;
    const abs = @import("std").math.fabs;
    const eps = 1e-15;

    const util = struct {
        fn warnCNs(n: u32, cns: [*]CN) void {
            var i: u32 = 0;
            while (i < n) : (i += 1) {
                warn("{} + {}i\n", cns[i].re, cns[i].im);
            }
        }
    };

    const n: u32 = 16;
    const v = [n]i32{ 1, 3, 4, 2, 5, 6, 2, 4, 0, 1, 3, 4, 5, 62, 2, 3 };
    var f: [n]CN = undefined;
    {
        var i: u32 = 0;
        while (i < n) : (i += 1) {
            f[i] = CN.rect(@intToFloat(f64, v[i]), 0.0);
        }
    }
    warn("\n[f]\n");
    util.warnCNs(n, &f);

    var F: [n]CN = undefined;
    var r: [n]CN = undefined;
    fft(n, &f, &F);
    ifft(n, &F, &r);

    warn("\n[F]\n");
    util.warnCNs(n, &F);
    warn("\n[r]\n");
    util.warnCNs(n, &r);

    {
        var i: u32 = 0;
        while (i < n) : (i += 1) {
            assert(abs(r[i].re - @intToFloat(f64, v[i])) < eps);
        }
    }
}

Die Funktionsdefinition in test kann in struct erfolgen

In zig-0.4.0 kann fn nicht auf der obersten Ebene des test-Teils beschrieben werden, aber die Funktion kann in ** struct ** beschrieben werden, und es war möglich, sie als statische Methode aufzurufen.

Verweis auf Zeigertyp

In Zick lautet die Syntax zum Abrufen eines Zeigers von einem Werttyp "& variable". Im Gegensatz zur C-Sprache können Array-Variablen nicht implizit als Array-Zeiger behandelt werden und müssen ** explizit mit & ** hinzugefügt werden.

Das Referenzieren eines Elements von einem Zeigertyp ist dasselbe wie ein Werttyp (a [i] oder a.m).

Ein zeigerspezifischer Ausdruck hat eine ** Ganzwert-Dereferenzierung *, auf die ein . * (V = a. * Oder a. * = V) folgt. Dies entspricht der Zeiger-Dereferenzierung " ptr" in der Sprache C.

Konsolenausgabe von warn

Sie können die Standardbibliothek warn verwenden, um die Konsolenausgabezeichen (to stderr) mithilfe einer Formatzeichenfolge zu verwenden. Es wird auch ausgegeben, indem ein "Zick-Test" ausgeführt wird.


3.8. Zig-Test-Ausführungsergebnis

Das Ausführen von zig test fft.zig auf diesem fft.zig ergibt die folgenden Ergebnisse:

$ zig test fft.zig
Test 1/4 CN...OK
Test 2/4 CN array...OK
Test 3/4 revbit...OK
Test 4/4 fft/ifft...
[f]
1.0e+00 + 0.0e+00i
3.0e+00 + 0.0e+00i
4.0e+00 + 0.0e+00i
2.0e+00 + 0.0e+00i
5.0e+00 + 0.0e+00i
6.0e+00 + 0.0e+00i
2.0e+00 + 0.0e+00i
4.0e+00 + 0.0e+00i
0.0e+00 + 0.0e+00i
1.0e+00 + 0.0e+00i
3.0e+00 + 0.0e+00i
4.0e+00 + 0.0e+00i
5.0e+00 + 0.0e+00i
6.2e+01 + 0.0e+00i
2.0e+00 + 0.0e+00i
3.0e+00 + 0.0e+00i

[F]
1.07e+02 + 0.0e+00i
2.329589166141268e+01 + 5.1729855807372815e+01i
-5.35477272147525e+01 + 4.2961940777125584e+01i
-4.921391810443094e+01 + -2.567438445589562e+01i
3.612708057484687e-15 + -5.9e+01i
4.9799704542057846e+01 + -2.4260170893522517e+01i
3.554772721475249e+01 + 4.89619407771256e+01i
-1.9881678099039597e+01 + 5.314406936974591e+01i
-6.3e+01 + 0.0e+00i
-1.9881678099039586e+01 + -5.314406936974591e+01i
3.55477272147525e+01 + -4.8961940777125584e+01i
4.9799704542057846e+01 + 2.4260170893522528e+01i
-3.612708057484687e-15 + 5.9e+01i
-4.921391810443094e+01 + 2.567438445589561e+01i
-5.354772721475249e+01 + -4.29619407771256e+01i
2.329589166141269e+01 + -5.1729855807372815e+01i

[r]
1.0e+00 + 0.0e+00i
3.0e+00 + -3.497202527569243e-15i
3.999999999999999e+00 + -1.0143540619928351e-16i
1.9999999999999996e+00 + 8.465450562766819e-16i
5.0e+00 + 0.0e+00i
6.0e+00 + 0.0e+00i
2.0e+00 + 4.592425496802568e-17i
4.0e+00 + -7.077671781985373e-16i
0.0e+00 + 0.0e+00i
1.0e+00 + -5.551115123125783e-17i
3.000000000000001e+00 + 9.586896263232146e-18i
4.0e+00 + 5.134781488891349e-16i
5.0e+00 + 0.0e+00i
6.2e+01 + 3.552713678800501e-15i
2.0e+00 + 4.592425496802568e-17i
2.9999999999999996e+00 + -6.522560269672795e-16i
OK
All tests passed.
$

4. Zick-Code der ausführbaren Datei mit fft.zig

Hinweis: In Zick ist der ** Startvorgang für Befehle und Bibliotheken in der Standardbibliothek ** implementiert. Die Beschreibung hier basiert auf der Standardbibliotheksimplementierung in zig-0.4.0. Die Spezifikationen hier können sich in Zukunft erheblich ändern.

Zuerst werden wir nur erklären, wie die main -Funktion verwendet wird, die aus der zig-Standardbibliothek aufgerufen wird, wenn ein Befehl ausgeführt wird, und dann das zig-Programm, das die oben erwähnte FFT-Implementierung durch @ import (" fft.zig ") aufruft.


4.1. Übersicht über die Hauptfunktionen von zig

Das Folgende ist ein Codebeispiel für die Einstiegspunkt-Hauptfunktion in der Befehlszeilenausführung, die nur Umgebungsvariablen, Befehlsargumente und Exit-Code verarbeitet:

example-main.zig


pub fn main() noreturn {
    // environment variable
    const pathOptional: ?[]const u8 = @import("std").os.getEnvPosix("SHELL");
    const path: []const u8 = pathOptional orelse "";
    @import("std").debug.warn("SHELL: {}\n", path);

    // command args
    var count: u8 = 0;
    var args = @import("std").os.args();
    const total = while (args.nextPosix()) |arg| {
        @import("std").debug.warn("{}: {}\n", count, arg);
        count += 1;
    } else count;

    // exit with code: `echo $?`
    @import("std").os.exit(total);
}

Hauptfunktionstyp und Exit-Code

Die ** Hauptfunktion ** ohne Pub-Argumente ist der Einstiegspunkt für Ihren Benutzercode.

Der Rückgabetyp von "main" ist "noreturn" (exit ohne vom Nachnamen ausgeführt zu werden), "u8" (Rückgabewert wird zum Exit-Code), "void" (Exit-Code ist 0). , ! Void (Fehler oder void).

Im Fall dieses Codes ist "os.exit ()" der darin zu verwendenden Standardbibliothek eine "noreturn" -Funktion, daher wird "main" auf "noreturn" gesetzt. Wenn Sie "os.exit ()" verwenden, übergeben Sie den Exit-Code als Argument (Typ "u8").

Wenn der Rückgabewert "! Void" ist, lautet der Exit-Code "1", wenn ein Fehler auftritt.

Fehlersatztyp und Fehlervereinigungstyp

Der durch die ** error-Syntax von zig definierte Fehlersatztyp ** ist einer der benutzerdefinierten Typen wie struct und enum und ein Aufzählungstyp, der enum ähnelt. Beispiel: const MyError = error {Fail, Dead,};

Der Unterschied zu "enum" besteht darin, dass der Fehlersatztyp den Fehler, der sein Element ist, zusammen wie folgt behandelt.

Typen mit !, Wie! U8 für Rückgabetypen und MyError! U8 für Variablentypen, sind Zigs ** Fehlerunion-Typen **. Der Fehlervereinigungstyp ist eine ** Variable oder ein Rückgabetyp, der sowohl den ** Werttyp als auch den Fehlertyp akzeptieren kann. Wenn Sie diesen Fehler-Union-Typ zum Rückgabetyp machen, sind ** Fehler im Zick-Zack-Modus ebenfalls Rückgabewerte. Der Rückgabetyp "! U8" ist der Fehlersatztyp auf der linken Seite, der aus dem Funktionsimplementierungscode abgeleitet wird, und ist tatsächlich ein Fehlervereinigungstyp wie "MyError! U8".

Um von einem Fehlervereinigungstyp zu einem Werttypausdruck zu wechseln, kann zig den Binäroperator "catch" und den Einzeltermoperator "try" verwenden.

catchOperator-Erfassungsabschnitt|err|Kann weggelassen werden, wenn der Fehlerwert nicht verwendet wird. Der Rückgabewert einer Funktion, die den Operator try verwendet, muss vom Typ error union sein.

Es ist auch möglich, die Fehlerzeit als "else" vom Typ der Fehlervereinigung zu sortieren, indem ein bedingter Ausdruck von "if" -Ausdruck oder "switch" -Ausdruck verwendet wird.

Umgebungsvariablen und optionale Typen

Umgebungsvariablen können mit der Standardbibliothek "os.getEnvPosix ()" abgerufen werden. Der Rückgabewert dieser Funktion ist ? [] Const u8.

Der Typ mit diesem ? Im Kopf ist der ** optionale Typ ** von zig. Der optionale Typ ist der ** null-Wert ** oder der Wert von zig. (Daher kann der Zeigertyp nicht "null" sein, es sei denn, es handelt sich um einen optionalen Typ.)

Ähnlich wie beim Fehlervereinigungstyp kann es sich um einen Werttyp handeln, indem ein Fallback-Wert mit dem Ausdruck "if" oder "switch" und "else" angegeben wird, wenn er "null" ist. Es hat auch einen "orelse" -Binäroperator für optionale Typen an derselben Position wie der "catch" -Binäroperator in der Fehlerunion.

--Beispiel: const v: u8 = nullOrInt () orelse 0;

Sie können es auch in einen Werttyp erzwingen (wie den mononomischen Operator "try" vom Typ "error"), indem Sie "const v: u8 = nullOrInt ()" hinzufügen.? "Und".? ". Im Gegensatz zu "try" ist jedoch kein Fehler oder "null" "return", und es scheint, dass zig-0.4.0 zu einer nicht einschließbaren erzwungenen Beendigung führt.

Befehlsargumente

Sie können Befehlszeilenargumente mit der Standardbibliothek os.args () abrufen. Dies ist ein Iterator und kann nacheinander als optionale Zeichenfolge vom Typ "? [] Const u8" mit der Methode "nextPosix ()" abgerufen werden.

Verwenden Sie eine while-Schleife **, die den Bedingungswert ** erfasst, um nacheinander vom Iterator abzurufen, bis er "null" wird. Wenn es "null" wird, wird der "else" -Teil eingegeben.

Bei der Konvertierung in eine Binärdatei ist das ** 0. Befehlsargument der Befehlsname ** und das Argument das nächste.

Für zig run können Sie ein Befehlsargument nach -- in zig run example-main.zig --a b c übergeben. In zig-0.4.0 beginnt das Argument in diesem Fall bei 0.


4.2. Haupt-Zick-Code, der fft.zig als @ import verwendet

Das folgende Programm zeigt die FFT / IFFT-Ergebnisse mit den im Test verwendeten Daten an und zeigt dann die Benchmark-Zeit für die Ausführung von FFT und IFFT von 1 Million Elementen (2 bis 20 Potenzen) an.

fft-bench-zig.zig


const heap = @import("std").heap;
const warn = @import("std").debug.warn;
const sin = @import("std").math.sin;
const milliTimestamp = @import("std").os.time.milliTimestamp;

const CN = @import("./fft.zig").CN;
const fft = @import("./fft.zig").fft;
const ifft = @import("./fft.zig").ifft;

fn warnCNs(n: u32, cns: [*]CN) void {
    var i: u32 = 0;
    while (i < n) : (i += 1) {
        warn("{} + {}i\n", cns[i].re, cns[i].im);
    }
}

pub fn main() !void {
    { //example
        const n: u32 = 16;
        const v = [n]i32{ 1, 3, 4, 2, 5, 6, 2, 4, 0, 1, 3, 4, 5, 62, 2, 3 };
        var f: [n]CN = undefined;
        {
            var i: u32 = 0;
            while (i < n) : (i += 1) {
                f[i] = CN.rect(@intToFloat(f64, v[i]), 0.0);
            }
        }
        warn("\n[f]\n");
        warnCNs(n, &f);

        var F: [n]CN = undefined;
        var r: [n]CN = undefined;
        fft(n, &f, &F);
        ifft(n, &F, &r);

        warn("\n[F]\n");
        warnCNs(n, &F);
        warn("\n[r]\n");
        warnCNs(n, &r);
    }

    { //benchmark
        //NOTE: allocators in std will be changed on 0.5.0
        var direct_allocator = heap.DirectAllocator.init();
        var arena = heap.ArenaAllocator.init(&direct_allocator.allocator);
        const allocator = &arena.allocator;
        defer direct_allocator.deinit();
        defer arena.deinit();

        const n: u32 = 1024 * 1024;
        var f = try allocator.create([n]CN);
        {
            var i: u32 = 0;
            while (i < n) : (i += 1) {
                const if64 = @intToFloat(f64, i);
                f[i] = CN.rect(sin(if64) * if64, 0.0);
            }
        }

        var F = try allocator.create([n]CN);
        var r = try allocator.create([n]CN);

        const start = milliTimestamp();
        fft(n, f, F);
        ifft(n, F, r);
        const stop = milliTimestamp();
        warn("fft-ifft: {}ms\n", stop  -start);
    }
}

@ Import aus der Zick-Datei

Sie können ** @ import ** @ import und die Variablen und Funktionen des pub-Attributs ** für Ihre eigene Zick-Quelldatei sowie die Standardbibliothek verwenden.

Tatsächlich existiert die Standardbibliothek auch als Zick-Quelldatei. Sie können den Implementierungscode überprüfen, indem Sie ihn im Installationsverzeichnis unter lib / zig / std / erweitern.

Verwendung von großem Speicher

In der Zick-Sprache verwenden Variablen grundsätzlich den Bereich des Stapelspeichers (statischer Speicher). Wenn Sie ein großes Array verwenden, ** überschreiten Sie zur Laufzeit das OS-Stack-Limit **, sodass Ihr Programm einen Off-Stack-Speicherbereich wie den Heap verwenden muss.

Die Standardbibliothek bietet einen ** Speicherzuweiser **, der den Heapspeicher verwaltet. Unten ist die Allokator-Implementierung in Zick-0.4.0:

--heap.DirectAllocator: Allokatoren zum Zuweisen von Heapspeicher (init, deinit, alloc, shrink, realloc) --heap.ArenaAllocator: Ein Allokator, der einen Speicherpool mit anderen Allokatoren konfiguriert und den bisher mit deinit zugewiesenen Speicher freigibt. --mem.Allocator: Schnittstelle zum Zuweisen von Speicher durch Eingabe (create, destroy)

In zig-0.4.0 hat der "Heap" -Zuweiser die "mem.Allocator" -Schnittstelle als Mitglied ".allocator" ("Arena.allocator" in diesem Code).

Beachten Sie, dass der Werttyp im Argument "create" von "mem.Allocator" übergeben wird, der Rückgabewert jedoch ein Zeigertyp (Fehlervereinigungstyp) für diesen Werttyp ist.

defer Aussage

Die Anweisung "defer" beschreibt im Voraus den Code, der ausgeführt wird, wenn der Block entkommt. Wenn sich im selben Block mehrere "Verzögerungen" befinden, werden diese ** in der Reihenfolge von der Rückseite ** ausgeführt. Durch Schreiben an einer nahen Stelle unter Verwendung von "Aufschieben" ist es möglich, den Initialisierungsprozess und den der Initialisierung entsprechenden Endprozess als Paar zu beschreiben.

Ein ähnliches ist "errdefer". Der "Aufschub", der ausgeführt wird, wenn der Block entweicht **, wenn der Fehler "zurückgegeben" wird. Wenn ein Block mit dem Fehler "return" verlassen wird, wird er in der Reihenfolge von hinten in einer gemischten Folge von "errdefer" und "defer" ausgeführt.


4.3. Erstellen Sie die ausführbare Datei

Aus dieser fft-Bench-Zig.zig können Sie die ausführbare Datei Fft-Bench-Zig mit der folgenden Befehlszeile generieren.

$ zig build-exe --release-fast fft-bench-zig.zig

Sie können Ihren Code auch mit zig run fft-Bench-zig.zig ausführen, ohne ihn erstellen zu müssen.


5. Kompilieren Sie fft.zig in ein WebAseembly-Modul und verwenden Sie es in JavaScript

Wenn Sie ein WebAssembly-Modul aus Code generieren, der in einer normalen Programmiersprache wie Rust geschrieben ist, müssen Sie zur Verwendung des wasm-Moduls die Implementierung der Programmiersprache in der Laufzeitbibliothek berücksichtigen, insbesondere die Funktionen im Zusammenhang mit dem Heapspeicher. Beim Importieren und Exportieren des WASM-Moduls ist es erforderlich, eine Methode für den Speicherbetrieb usw. in dieser Sprache zu haben und diese entsprechend ihrer Verwendung zu implementieren und zu verwenden.

Wenn Sie jedoch ein Wasm-Modul aus einem Zick-Zack-Programm erstellen, generieren Sie ein Wasm-Modul **, das keine Methoden zur Laufzeit der Sprache verfügbar macht. Der größte Unterschied besteht darin, dass zig im Grunde keinen Heap-Speicher verwendet (die C-Sprache ist im Grunde dieselbe, aber ich schreibe sie ohne die Verwendung von "stdlib.h", die überhaupt über Heap-Management verfügt. Das ist schwieriger).

In der zuvor implementierten "fft.zig" werden die Funktionen "fft" und "ifft" exportiert, und es ist möglich, sie so wie sie sind in eine WASM-Datei zu kompilieren.

Sie können "fft.wasm" mit der folgenden Befehlszeile generieren.

 $ zig build-exe --release-fast -target wasm32-freestanding --name fft.wasm fft.zig

5.1. JavaScript-Code, der fft.wasm in node.js verwendet

Der folgende JavaScript-Code ist das Programm fft-Bench-Nodejs.js, das die aus diesem Zick-Code generierte WASM-Datei lädt und verwendet. Ähnlich wie beim vorherigen "fft-Bench-Zig.zig" ist es ein Benchmark, der die FFT / IFFT-Konvertierung mit den Beispieldaten anzeigt und dann die Ausführungszeit von FFT / IFFT von 1 Million Elementdaten ausgibt.

fft-bench-nodejs.js


const fs = require("fs").promises;

(async function () {
  const buf = await fs.readFile("./fft.wasm");
  const {instance} = await WebAssembly.instantiate(buf, {});
  const {__wasm_call_ctors, memory, fft, ifft} = instance.exports;
  __wasm_call_ctors();
  memory.grow(1);
  
  {// example
    const N = 16, fofs = 0, Fofs = N * 2 * 8, rofs = N * 4 * 8;
    const f = new Float64Array(memory.buffer, fofs, N * 2);
    const F = new Float64Array(memory.buffer, Fofs, N * 2);
    const r = new Float64Array(memory.buffer, rofs, N * 2);

    const fr0 = [1,3,4,2, 5,6,2,4, 0,1,3,4, 5,62,2,3];
    fr0.forEach((v, i) => {
      [f[i * 2], f[i * 2 + 1]] = [v, 0.0];
    });

    fft(N, fofs, Fofs);
    ifft(N, Fofs, rofs);

    console.log(`[fft]`);
    for (let i = 0; i < N; i++) {
      console.log([F[i * 2], F[i * 2 + 1]]);
    }

    console.log(`[ifft]`);
    for (let i = 0; i < N; i++) {
      console.log([r[i * 2], r[i * 2 + 1]]);
    }
  }
  
  { // benchmark
    const N = 1024 * 1024;
    const fr0 = [...Array(N).keys()].map(i => Math.sin(i) * i);
    const f0 = fr0.map(n => [n, 0]);

    const BN = N * 2 * 8 * 3, fofs = 0, Fofs = N * 2 * 8, rofs = N * 4 * 8;
    while (memory.buffer.byteLength < BN) memory.grow(1);
    const f = new Float64Array(memory.buffer, fofs, N * 2);
    const F = new Float64Array(memory.buffer, Fofs, N * 2);
    const r = new Float64Array(memory.buffer, rofs, N * 2);
    fr0.forEach((v, i) => {
      [f[i * 2], f[i * 2 + 1]] = [v, 0.0];
    });

    console.time(`fft-ifft`);
    fft(N, fofs, Fofs);
    ifft(N, Fofs, rofs);
    console.timeEnd(`fft-ifft`);
  }
})().catch(console.error);

Sie können das generierte fft.wasm und dieses fft-Bench-Nodejs.js in dasselbe Verzeichnis legen und mit Node fft-Bench-Node.js ausführen.

Initialisierung des von zig generierten wasm-Moduls

Da es keine Funktion gibt, die in der Zick-Runt-Laufzeit von außen "importiert" werden muss, erstellen Sie sie mit "warte auf WebAssembly.instantiate (buf, {})".

export`` enthält zusätzlich zu den Funktionen export im Code __wasm_call_ctors und memmory`.

__wasm_call_ctors ist eine Funktion, und wenn der Zick-Code eine Initialisierungsverarbeitung enthält, führt er die Initialisierungsverarbeitung durch Aufrufen dieser **__wasm_call_ctors ()aus.

memory ist ein WebAssembly Memory-Objekt, das anfangs leer ist. Wenn Sie Speicher in Ihrem Code verwenden möchten, müssen Sie die Methode "memory.grow ()" aufrufen, um einen Speicherbereich zuzuweisen. Das Argument ist die Anzahl der zu erweiternden Speicherseiten, 64 KB pro Seite.

Speicher in WebAssembly

Der memory.buffer in export ist das ArrayBuffer-Objekt des Typed Array. Umschließen Sie diesen memory.buffer von der JavaScript-Seite mit Float64Array usw., geben Sie einen Wert ein und verweisen Sie darauf.

Der ** Zeiger ist der Adresswert **, und der WebAssembly-Speicheradresswert verwendet den ** memory.buffer-Byte-Offsetwert ** als ArrayBuffer. Beachten Sie, dass zig für jede Anzahl von Bytes des numerischen Typs eine Ausrichtung hat. Für f64 muss der Adresswert ein Vielfaches von 8 sein, und das zweite Argument des Konstruktors Float64Array sollte dieses Vielfache von 8 verwenden.

Referenz: Beim Erstellen eines Wasms, der "Importe" akzeptiert

zig erstellt wasm, für das die Sprachlaufzeit nicht von einem externen Modul bereitgestellt werden muss, und es ist nicht unmöglich, ein externes Modul, das Sie vorbereitet haben, in wasm zu importieren und zu verwenden.

Wenn Sie imports eine extern implementierte Funktion zuweisen und verwenden möchten, können Sie einen ** extern -Funktionsprototyp wie extern fn sin (x: f64) f64; deklarieren und in Ihrem Code ** verwenden. Ist möglich. In zig-0.4.0 ist der Name des externen Moduls in der wasm-Datei auf "env" festgelegt und kann als "WebAssembly.instantiate (buf, {env: {sin: Math.sin}})" angegeben werden. ..

Code-Exportlimit für Web Assembly

Es gibt weitere Einschränkungen für die Funktionen, die als WebAssembly auf die JavaScript-Seite exportiert werden können. Beispielsweise unterliegen in zig-0.4.0 ** Funktionen ** mit einem Rückgabewert vom Typ struct, wie z. B. CN.rect (), dieser Einschränkung. Seien Sie vorsichtig beim Hinzufügen von "Export" in Ihrem Zick-Code, da Sie nicht in Wasm konvertieren können, wenn es für diese Einschränkung auch nur einen "Export" gibt.

Als Gegenmaßnahme ist es möglich, die Zick-Datei des Implementierungscodes, der nur "pub" enthält, in die Zick-Datei zu unterteilen, die nur "@ import" die Implementierungs-Zick-Datei ist, und jedem Ziel einen geeigneten "Export" hinzuzufügen. Ich werde.

6. Machen Sie fft.zig zu einer Objektdatei und verwenden Sie sie in der Sprache C.

Der Zick-Code kann mit dem Befehl zig build-obj in eine von C verfügbare Objektdatei konvertiert werden. Gleichzeitig wird auch eine der Objektdatei entsprechende C-Header-Datei generiert.

Erstellen Sie hier ein C-Sprachprogramm, das fft.zig verwendet, kompilieren Sie es und generieren Sie eine ausführbare Datei.

6.1 Generierung von Objektdateien und C-Header-Dateien

Generieren Sie die Objektdatei "fft.o" und die Header-Datei "fft.h" aus "fft.zig" mit der folgenden Befehlszeile:

$ zig build-obj --release-fast fft.zig

Die generierte Header-Datei fft.h hat folgenden Inhalt.

fft.h


#ifndef FFT_H
#define FFT_H

#include <stdint.h>

#ifdef __cplusplus
#define FFT_EXTERN_C extern "C"
#else
#define FFT_EXTERN_C
#endif

#if defined(_WIN32)
#define FFT_EXPORT FFT_EXTERN_C __declspec(dllimport)
#else
#define FFT_EXPORT FFT_EXTERN_C __attribute__((visibility ("default")))
#endif

struct CN {
    double re;
    double im;
};

FFT_EXPORT void fft(uint32_t n, struct CN * f, struct CN * F);
FFT_EXPORT void ifft(uint32_t n, struct CN * F, struct CN * f);

#endif

Sie können sehen, dass diese Header-Datei fft.h die Funktionen export und struct im Zick-Code enthält.


6.2 C-Sprachprogramm, das die FFT-Implementierung aufruft

Das Folgende ist der C11-Code "fft-Bench-cc" der "Haupt" -Funktion, der FFT / IFFT mit demselben Datenbeispiel ausführt und anzeigt, das im Test verwendet wurde, und dann 10 Millionen FFT / IFFT-Datenelemente ausführt. ist.

fft-bench-c.c


#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include "./fft.h"

int main() {
  {// example
    const int n = 16;
    const int v[16] = {1,3,4,2, 5,6,2,4, 0,1,3,4, 5,62,2,3};
    struct CN f[n], F[n], r[n];
    for (size_t i = 0; i < n; i++) f[i] = (struct CN) {.re = v[i], .im = 0.0};

    puts("[f]");
    for (size_t i = 0; i < n; i++) printf("%f + %fi\n", f[i].re, f[i].im);

    fft(n, f, F);
    ifft(n, F, r);

    puts("[F]");
    for (size_t i = 0; i < n; i++) printf("%f + %fi\n", F[i].re, F[i].im);
    puts("[r]");
    for (size_t i = 0; i < n; i++) printf("%f + %fi\n", r[i].re, r[i].im);
  }
  
  {// benchmark
    const int n = 1024 * 1024;
    struct CN * f = calloc(n, sizeof (struct CN));
    for (size_t i = 0; i < n; i++) {
      f[i] = (struct CN) {.re = sin(i) * i, .im = 0.0};
    }

    struct CN * F = calloc(n, sizeof (struct CN));
    struct CN * r = calloc(n, sizeof (struct CN));
    fft(n, f, F);
    ifft(n, F, r);
    free(f);
    free(F);
    free(r);
  }
  return 0;
}

Wenn Sie Strukturen oder Funktionen verwenden, nur weil sie im Zickzack implementiert sind, müssen Sie auf der ** C-Seite ** nichts Besonderes tun.

6.3. Bauen

Die Befehlszeile zum Generieren der ausführbaren Datei fft-Bench-c lautet wie folgt (die Implementierung des C-Compilers setzt clang oder gcc voraus):

$ cc -Wall -Wextra -std=c11 -pedantic -O3 -o fft-bench-c fft-bench-c.c fft.o

(Diese Option gibt an, dass der Code vom nicht erweiterten C11-Standard selbst interpretiert werden soll.)

7. Lesen Sie die in C geschriebene Objektdatei aus dem Zick-Zack-Programm

Im Gegensatz zu "fft-Bench-c.c" ist es auch möglich, eine in C-Sprache geschriebene Objektdatei mit dem Zick-Programm unter Verwendung der C-Header-Datei zu importieren.

Implementieren Sie hier FFT mit dem C11-Code fft-c.c und beschreiben Sie die C-Header-Datei fft-c.h, um die Implementierung von zig zu verwenden. Lesen und implementieren Sie dann mit "fft-Bench-Cimport.zig", das dasselbe tut wie "Fft-Bench-Zig.zig", "Fft-C.h".

7.1. FFT-Implementierungsprogramm von C11

Das Folgende ist die FFT-Implementierung "fft-c.c" unter Verwendung der komplexen Zahl "double complex" in der C11-Spezifikation.

fft-c.c


#include <complex.h> // complex, I, cexp
#include <math.h> // M_PI
#include <stdbool.h> // bool
#include <stddef.h> // size_t
#include <stdint.h> // uint64_t

typedef double complex CN;

static inline uint32_t reverse_bit(uint32_t s0) {
  uint32_t s1 = ((s0 & 0xaaaaaaaa) >> 1) | ((s0 & 0x55555555) << 1);
  uint32_t s2 = ((s1 & 0xcccccccc) >> 2) | ((s1 & 0x33333333) << 2);
  uint32_t s3 = ((s2 & 0xf0f0f0f0) >> 4) | ((s2 & 0x0f0f0f0f) << 4);
  uint32_t s4 = ((s3 & 0xff00ff00) >> 8) | ((s3 & 0x00ff00ff) << 8);
  return ((s4 & 0xffff0000) >> 16) | ((s4 & 0x0000ffff) << 16);
}

static inline uint32_t rev_bit(uint32_t k, uint32_t n) {
  return reverse_bit(n) >> (8 * sizeof (uint32_t) - k);
}

static inline uint32_t trailing_zeros(uint32_t n) {
  uint32_t k = 0, s = n;
  if (!(s & 0x0000ffff)) {k += 16; s >>= 16;}
  if (!(s & 0x000000ff)) {k += 8; s >>= 8;}
  if (!(s & 0x0000000f)) {k += 4; s >>= 4;}
  if (!(s & 0x00000003)) {k += 2; s >>= 2;}
  return (s & 1) ? k : k + 1;
}

static void fftc(double t, uint32_t N, const CN c[N], CN ret[N]) {
  uint32_t k = trailing_zeros(N);
  for (uint32_t i = 0; i < N; i++) ret[i] = c[rev_bit(k, i)];
  for (uint32_t Nh = 1; Nh < N; Nh <<= 1) {
    t *= 0.5;
    for (uint32_t s = 0; s < N; s += Nh << 1) {
      for (uint32_t i = 0; i < Nh; i++) { //NOTE: s-outside/i-inside is faster
        uint32_t li = s + i;
        uint32_t ri = li + Nh;
        CN re = ret[ri] * cexp(t * i * I);
        CN l = ret[li];
        ret[li] = l + re;
        ret[ri] = l - re;
      }
    }
  }
}

extern CN rect(double re, double im) {
  return re + im * I;
}
extern void fft(uint32_t N, const CN f[N], CN F[N]) {
  fftc(-2.0 * M_PI, N, f, F);
}
extern void ifft(uint32_t N, const CN F[N], CN f[N]) {
  fftc(2.0 * M_PI, N, F, f);
  for (size_t i = 0; i < N; i++) f[i] /= N;
}

Der Algorithmus ist der gleiche wie "fft.zig".

Da "Doppelkomplex" ein Wert ist, bei dem der "Doppel" -Wert des Realteils und des Imaginärteils nacheinander angeordnet sind, hat er das gleiche Speicherlayout ** wie das "CN" von ** "fft.zig". Wir bieten auch eine Konstruktorfunktion "rect" an, die aus dem "double" -Wert eine komplexe Zahl erstellt, damit Sie den "double complex" nicht im Zick-Zack bearbeiten müssen.

7.2. C-Header-Datei zur Verwendung mit Zickzack

Die C-Header-Datei "fft-c.h" enthält Inhalte, die der Methode der Header-Datei entsprechen, die von "build-obj" von zig geschrieben wurde.

fft-c.h


#ifndef FFT_C_H
#define FFT_C_H
#include <stdint.h>

#ifdef __cplusplus
#define FFT_C_EXTERN_C extern "C"
#else
#define FFT_C_EXTERN_C
#endif

#if defined(_WIN32)
#define FFT_C_EXPORT FFT_C_EXTERN_C __declspec(dllimport)
#else
#define FFT_C_EXPORT FFT_C_EXTERN_C __attribute__((visibility ("default")))
#endif

struct CN {
    double re;
    double im;
};

FFT_C_EXPORT struct CN rect(double re, double im);
FFT_C_EXPORT void fft(uint32_t n, struct CN f[], struct CN F[]);
FFT_C_EXPORT void ifft(uint32_t n, struct CN F[], struct CN f[]);

#endif

Da zig keinen Typ hat, der dem komplexen Zahlentyp entspricht, wird in der vom zig-Programm gelesenen C-Header-Datei stattdessen struct with double2 verwendet.

7.3. Zick-Programm zum Erfassen von C-Header-Dateien

Ähnlich wie beim vorherigen "fft-Bench-Zig.zig" ist es ein Benchmark, der die FFT / IFFT-Konvertierung mit den Beispieldaten anzeigt und dann die Ausführungszeit von FFT / IFFT von 1 Million Elementdaten ausgibt.

fft-bench-cimport.zig


const heap = @import("std").heap;
const warn = @import("std").debug.warn;
const sin = @import("std").math.sin;
const milliTimestamp = @import("std").os.time.milliTimestamp;

const libfft = @cImport({
    @cInclude("fft-c.h");
});
const CN = libfft.CN;
const rect = libfft.rect;
const fft = libfft.fft;
const ifft = libfft.ifft;

fn warnCNs(n: u32, cns: [*]CN) void {
    var i: u32 = 0;
    while (i < n) : (i += 1) {
        warn("{} + {}i\n", cns[i].re, cns[i].im);
    }
}

pub fn main() !void {
    { //example
        const n: u32 = 16;
        const v = [n]i32{ 1, 3, 4, 2, 5, 6, 2, 4, 0, 1, 3, 4, 5, 62, 2, 3 };
        var f: [n]CN = undefined;
        {
            var i: u32 = 0;
            while (i < n) : (i += 1) {
                f[i] = rect(@intToFloat(f64, v[i]), 0.0);
            }
        }
        warn("\n[f]\n");
        warnCNs(n, &f);

        var F: [n]CN = undefined;
        var r: [n]CN = undefined;
        fft(n, @ptrCast([*c]CN, &f), @ptrCast([*c]CN, &F));
        ifft(n, @ptrCast([*c]CN, &F), @ptrCast([*c]CN, &r));

        warn("\n[F]\n");
        warnCNs(n, &F);
        warn("\n[r]\n");
        warnCNs(n, &r);
    }
    
    { //benchmark
        //NOTE: allocators in std will be changed on 0.5.0
        var direct_allocator = heap.DirectAllocator.init();
        var arena = heap.ArenaAllocator.init(&direct_allocator.allocator);
        const allocator = &arena.allocator;
        defer direct_allocator.deinit();
        defer arena.deinit();

        const n: u32 = 1024 * 1024;
        var f: [*]CN = try allocator.create([n]CN);
        {
            var i: u32 = 0;
            while (i < n) : (i += 1) {
                const if64 = @intToFloat(f64, i);
                f[i] = rect(sin(if64) * if64, 0.0);
            }
        }

        var F: [*]CN = try allocator.create([n]CN);
        var r: [*]CN = try allocator.create([n]CN);

        const start = milliTimestamp();
        fft(n, f, F);
        ifft(n, F, r);
        const stop = milliTimestamp();
        warn("fft-ifft: {}ms\n", stop - start);
    }
}

@ cImport eingebaut

Durch die Verwendung von ** @ cImport integriert ** kann es als Zick Struktur oder Funktion aus dem C-Header importiert werden. Geben Sie in @ cImport den Namen der Header-Datei an, die von ** @ cInclude integriert ** interpretiert werden soll.

C Zeigertyp

Der Zeigertyp von Zickzack ist in den Zeigertyp des Werttyps und den Zeigertyp für das Array unterteilt, aber der Zeigertyp von C unterscheidet nicht zwischen ihnen. Wenn der Zeigertyp im Argument oder im Rückgabewert der Funktion "@ cImport" verwendet wird, wird er daher durch "[* c] Werttyp" beschrieben, der jedem Typ im Zick ** C-Zeigertyp * entsprechen kann. Interpretiert als *.

Hier ist die Funktion "fft" aus dem C-Header vom Typ "fft" (n: i32, f: [* c] CN, F: [* c] CN) ".

Dieser C-Zeigertyp "[* c] CN" wird von "* CN" und "[] CN" ohne Casting zugewiesen (später Code). Zig-0.4.0 erfordert jedoch eine explizite Umwandlung von "@ ptrCast" aus "& f: * [16] CN", die "[] CN" ohne Umwandlung zugewiesen wird (erste Hälfte des Codes).

-- * [16] CN = Keine Besetzung erforderlich => [*] CN = Keine Besetzung erforderlich => [* c] CN -- * [16] CN = Erforderlich @ ptrCast => [* c] CN

Referenz: So verwenden Sie eine Objektdatei ohne @ cImport`` extern fn

Sie können sehen, was Sie mit @ cImport tun, indem Sie den Befehl zig translate-c fft-c.h ausführen und den Zick-Code für diese Ausgabe anzeigen. Da jedoch alle Inhalte der Standardbibliothek "# include" enthalten sind, wird eine große Menge angezeigt.

Wenn Sie nur das notwendige Teil herausnehmen,

zig:fft-c.h.zig


pub const CN = extern struct {
    re: f64,
    im: f64,
};
pub extern "./fft-c.o" fn rect(re; f64, im: f64) CN;
pub extern "./fft-c.o" fn fft(n: u32, f: [*c]CN, F: [*c]CN) void;
pub extern "./fft-c.o" fn ifft(n: u32, F: [*c]CN, f: [*c]CN) void;

Es wird der gleiche Inhalt sein wie. Wenn Sie diesen Inhalt einbetten, benötigen Sie nicht "fft-c.h" oder die "zig build" -Option "-isystem", um den Include-Pfad hinzuzufügen. Wenn Sie den Objektdateipfad (. / Erforderlich) unmittelbar nach extern einbetten, ist --object fft-c.o ebenfalls nicht erforderlich. (Wenn . / Fehlt, wird versucht, als Bibliothek aufzulösen (" fft-c " wird als -lfft-c behandelt).)

Wenn Sie im Zickzack einen externen Funktionsprototyp haben, erklären Sie, dass Sie die gleichnamige Funktion in der Bibliothek oder Objektdatei verwenden werden.

7.4. Bauen

Sie können es mit der folgenden Befehlszeile erstellen.

$ cc -Wall -Wextra -std=c11 -pedantic -O3 -c fft-c.c
$ zig build-exe --release-fast --object fft-c.o -isystem . fft-bench-cimport.zig

Die Option "--object fft-c.o" gibt die Objektdatei an und "-isystem" gibt das Verzeichnis an, in dem die Headerdatei mit "@ cInclude" gelesen werden soll.

Auch in diesem Fall können Sie es ausführen, ohne eine ausführbare Datei mit zig run --object fft.o -isystem .fft-Bench-cimport.zig zu erstellen.

8. Build-Beschreibung von build.zig

Es scheint, dass zig darauf abzielt, plattformübergreifende Builds mit dem Befehl "zig build" zu ermöglichen, ohne ein externes Build-System wie "make" zu verwenden.

Beschreiben Sie in build.zig, das den Build-Inhalt durch Ausführen von zig build beschreibt, die Funktion ** pub`` build **.

In diesem wird der im Build auszuführende Inhalt als ** Schrittdiagramm ** definiert. Jede Kompilierung und Befehlsausführung ist ein Schritt. Für jeden Schritt können Sie die Schrittabhängigkeiten angeben, die im Voraus ausgeführt werden müssen.

Es gibt auch einen ** benannten Schritt **, bei dem es sich um das Build-Ziel handelt, zu dem Sie nacheinander jeden Kompilierungs- und Befehlsausführungsschritt hinzufügen können. Es gibt einen ** Standardschritt ** als Build-System, der beschreibt, was von zig build ausgeführt wird, ohne den Schrittnamen anzugeben. (Einige der genannten Schritte werden zu den Standardschritten hinzugefügt.)

Ab zig-0.4.0 scheint jedoch nicht alles, was in build.zig beschrieben werden kann, vollständig zu sein. Um beispielsweise eine C-Datei zu kompilieren oder zu löschen, müssen Sie den Befehl in der Ausführungsumgebung direkt mit einem Argument ausführen. (Die 0.4.0-Dokumentation enthält einen Schritt namens "addCExecutable", der jedoch in zig-0.4.0 nicht vorhanden ist.)

build.zig


const builtin = @import("builtin");
const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    { //[case: WebAssembly wasm] build fft.wasm
        const wasmStep = b.step("fft.wasm", "build fft.wasm");
        const wasm = b.addExecutable("fft.wasm", "fft.zig");
        wasm.setTarget(builtin.Arch.wasm32, builtin.Os.freestanding, builtin.Abi.musl);
        wasm.setBuildMode(builtin.Mode.ReleaseFast);
        wasm.setOutputDir(".");
        wasmStep.dependOn(&wasm.step);
        b.default_step.dependOn(wasmStep);
    }
    { //[case: zig code only] build fft-bench-zig command
        const exeStep = b.step("fft-bench-zig", "build fft-bench-zig command");
        const zigExe = b.addExecutable("fft-bench-zig", "fft-bench-zig.zig");
        zigExe.setBuildMode(builtin.Mode.ReleaseFast);
        zigExe.setOutputDir(".");
        exeStep.dependOn(&zigExe.step);
        b.default_step.dependOn(exeStep);
    }
    { //[case: c exe with zig lib] build fft-bench-c command
        // build fft.h and fft.o
        const objStep = b.step("fft.o", "build fft.h and fft.o");
        const fftObj = b.addObject("fft", "fft.zig");
        fftObj.setBuildMode(builtin.Mode.ReleaseFast);
        fftObj.setOutputDir(".");
        objStep.dependOn(&fftObj.step);

        // build fft-bench-c command (cc: expected clang or gcc)
        const cExeStep = b.step("fft-bench-c", "build fft-bench-c command");
        cExeStep.dependOn(objStep);
        const cExe = b.addSystemCommand([][]const u8{
            "cc", "-Wall", "-Wextra", "-O3", "-o", "fft-bench-c", "fft-bench-c.c", "fft.o",
        });
        cExeStep.dependOn(&cExe.step);
        b.default_step.dependOn(cExeStep);
    }
    { //[case: zig exe with c lib] build fft-bench-cimport command
        const exeStep = b.step("fft-bench-cimport", "build fft-bench-cimport command");

        const cLibStep = b.step("fft-c", "build fft-c.o");
        const cLibObj = b.addSystemCommand([][]const u8{
            "cc", "-Wall", "-Wextra", "-std=c11", "-pedantic", "-O3", "-c", "fft-c.c",
        });
        cLibStep.dependOn(&cLibObj.step);
        exeStep.dependOn(cLibStep);

        const mainObj = b.addExecutable("fft-bench-cimport", "fft-bench-cimport.zig");
        mainObj.addIncludeDir("."); //NOTE: same as `-isystem .`
        mainObj.setBuildMode(builtin.Mode.ReleaseFast);
        mainObj.addObjectFile("fft-c.o");
        mainObj.setOutputDir(".");
        exeStep.dependOn(&mainObj.step);

        b.default_step.dependOn(exeStep);
    }
    
    { // clean and dist-clean
        const cleanStep = b.step("clean", "clean-up intermediate iles");
        const rm1 = b.addSystemCommand([][]const u8{
            "rm", "-f", "fft.wasm.o", "fft-bench-zig.o", "fft-bench-c.o", "fft-c.o", "fft-bench-cimport.o",
        });
        cleanStep.dependOn(&rm1.step);
        const rmDir = b.addRemoveDirTree("zig-cache");
        cleanStep.dependOn(&rmDir.step);

        const distCleanStep = b.step("dist-clean", "clean-up build generated files");
        distCleanStep.dependOn(cleanStep);
        const rm2 = b.addSystemCommand([][]const u8{
            "rm", "-f", "fft.wasm", "fft-bench-zig", "fft-bench-c", "fft.o", "fft.h", "fft-bench-cimport",
        });
        distCleanStep.dependOn(&rm2.step);
    }
}

In diesem Schritt machen wir 6 benannte Schritte. Von oben für jeden Block:

-- fft.wasm: wasm Schritte zur Dateierzeugung --fft-Bench-Zig: Schritte zum Generieren einer ausführbaren Datei nur aus der Zick-Datei --fft-Bench-c: Schritte zum Erstellen einer Zick-Datei zu einer Objektdatei und zum Erstellen einer ausführbaren Datei mit der c-Datei, die sie als Hauptdatei verwendet --fft-Bench-Cimport: Schritte, um eine C-Datei zu einer Objektdatei zu machen und eine ausführbare Datei mit der Zick-Datei zu erstellen, die sie als Hauptdatei verwendet --clean und dist-clean: Schritte zum Löschen der vom Build generierten Dateien

Mit Ausnahme von "sauber" und "dist-sauber" wurden sie zu den Standardschritten hinzugefügt.

Sie können sehen, welche benannten Schritte mit zig build --help verfügbar sind.

Ich denke, dass es eine große Änderung im zukünftigen Versions-Upgrade sein wird, aber es kann eine Referenz dafür sein, wie man es in jedem der vier zu erstellenden Fälle als Build-Beschreibung konfiguriert.

9. Benchmark-Ergebnisse

Hier ist das Ergebnis des Benchmarks der zuletzt generierten ausführbaren Datei. Es ist die Zeit, die für FFT / IFFT von 1 Million Elementen in der zweiten Hälfte benötigt wird.

command time (ms) runtime
node fft-bench-nodejs.js 945.458ms node-12.6.0
./fft-bench-zig 856ms zig-0.4.0
./fft-bench-cimport 802ms Apple LLVM version 10.0.1 (clang-1001.0.46.4)

Als Referenz in wasm wurde der FFT / IFFT-Benchmark mit demselben Inhalt, der in "Web Assembly Circumstances in 2019" ausgeführt wurde, in derselben Umgebung ausgeführt. Die Ergebnisse sind ebenfalls aufgeführt.

command time (ms) runtime
node call-js-fft.js 1074.170ms node-12.6.0
node --experimental-modules call-fft-dynamic.js (fast) 1117.932ms node-12.6.0
node --experimental-modules call-fft-dynamic.js (slow) 2177.661ms node-12.6.0

Die Tatsache, dass es beim Einbetten einer mathematischen Funktion mit zig nicht erforderlich ist, "sin" oder "cos" zu "importieren" und "sin" aufzurufen, kann in Bezug auf die Ausführungsgeschwindigkeit groß sein, selbst wenn die v8-Optimierung der mathematischen Eigenschaften hinzugefügt wird ( Im zickbasierten Wasm wurden die Funktionen sin und cos selbst inline dargestellt.

Recommended Posts

Open Source Programmiersprache Zickzusammenfassung
Empfohlene Programmiersprache
Beliebtes Programmiersprachen-Ranking
Beliebtes Programmiersprachen-Ranking
Zusammenfassung der Grundlagen der Java-Sprache
[Programmiersprache] Liste der Benutzernamen
Über die Programmiersprache Crystal