Lorsque je recherchais le langage de programmation pour l'écriture de modules WebAssembly, j'ai senti que le langage de programmation open source relativement nouveau zig convenait à l'écriture de modules WebAssembly, j'ai donc cherché en écrivant et en exécutant le code source ** Ils sont répertoriés dans l'ordre dans lequel ils apparaissent dans le code **.
Le code source utilisé dans cet article peut être construit sur macOS ou Linux à l'URL suivante:
Qiita ne prend actuellement pas en charge la coloration syntaxique du code zig et n'est pas très visible, mais sur github, le code zig est également mis en évidence sur la syntaxe.
Le langage de programmation open source zig est disponible à l'URL suivante.
zig est un langage de programmation implémenté dans LLVM, mais contrairement à C ++, Go et Rust, ** l'utilisation de la mémoire du tas est une option ** comme le langage C, et quelque chose comme new
est dans la syntaxe du langage. Non intégré.
Les caractéristiques du langage zig sont:
zig
**zig run xxx.zig
)zig
( zig test xxx.zig
)build.zig
qui génère le fichier d'exécution, etc. est également écrite en langage zig, et il a un ** système de construction ** qui peut exécuter la construction avec la commande zig
( zig build
)zig fmt xxx.zig
)
--Types numériques avec un nombre spécifié de bits, ʻifet
switch comme expressions, syntaxe de boucle, instructions
defer` pour l'exécution d'échappement de bloc, tranches de tableau, collouts, expressions d'exécution au moment de la compilation, etc. ** Langages modernes spécification**struct
/ ʻunion / ʻenum
dans le fichier d'en-tête (.h) de la bibliothèque C directement comme fonctions zig et struct
/ ʻunion / ʻenum
** Un mécanisme pour intégrer des programmes C (@ cImport
) est incorporé en tant que langage **Etc.
Cependant, notez que la syntaxe et les ** spécifications de la bibliothèque standard ne sont pas encore stables **. La version actuelle 0.4.0 et la version suivante sont à un stade où des incompatibilités se produisent au niveau de la syntaxe de type tableau de chaînes. Et ci-dessous, dans cet article, tout est décrit en fonction de la ** spécification de la version 0.4.0 **.
Tout d'abord, exécutez la commande zig zen
pour avoir un aperçu de la politique de langage zig:
$ 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.
$
Dans le langage zig, les balises de hachage telles que Twitter utilisent ziglang.
1. Installez la commande zig
Le langage zig est le principal gestionnaire de paquets homebrew, snap, nixos, scoop, chocolatey, packman, qui fournit le paquet zig
( ziglang
in scoop). Il est facile d'installer la commande zig
à partir de ces gestionnaires de paquets.
Le zig-mode
officiel de la langue zig est enregistré dans MELPA.
Il semble également y avoir un mode pour vim, vscode, sublime:
zig
Tous les outils de développement sont fournis en tant que sous-commandes de la commande zig
.
commande d'exécution de fichier zig:
--zig run xxx.zig
: Exécute la fonction main
du fichier source xxx.zig
(sans générer de binaire)
--zig test xxx.zig
: Exécute le code dans la syntaxe test
décrite dans le fichier source xxx.zig
dans l'ordre et affiche un résumé des résultats du test.
--zig build
: Exécute le processus de construction décrit dans la description de construction build.zig
commandes de compilation de fichiers zig:
--zig build-exe xxx.zig
: Génère ** divers binaires cibles (xxx
, xxx.exe
, etc.) ** à partir du fichier zig (xxx.zig
).
--zig build-obj xxx.zig
: Depuis le fichier zig (xxx.zig
), ** fichier d'en-tête C ( xxx.h
) et fichier objet C (xxx.o
, xxx.obj
) Etc.) ** Générer
--zig build-lib xxx.zig
: Depuis le fichier zig (xxx.zig
), le fichier d'en-tête C ( xxx.h
) et le fichier de bibliothèque statique C (libxxx.a
, xxx.lib
, etc. " ) Est généré
--zig build-lib -dynamic xxx.zig
: Depuis le fichier zig, le fichier d'en-tête C (xxx.h
) et le fichier DLL ( libxxx.so
, libxxx.dylib
, xxx.dll
et xxx.pdb
etc.) etc.
Commandes utilitaires:
--zig init-exe
: Génère des modèles de projet (build.zig
et src / main.zig
) pour les fichiers exécutables dans ce répertoire.
--zig init-lib
: Génère des modèles de projet (build.zig
et src / main.zig
) pour les bibliothèques statiques dans ce répertoire
--zig fmt xxx.zig
: xxx.zig
est ** écrasé par un formateur de syntaxe.
--zig translate-c xxx.c
: le fichier d'en-tête C et le fichier d'implémentation C simple sont codés en zig et sortis en standard.
Commandes d'affichage des informations:
--zig --help
: Liste des sous-commandes et liste des arguments de commande Afficher l'aide
-- zig builtin
: affiche le module d'informations sur l'environnement d'exécution de la commande zig @ import ("builtin") ʻen format de code source zig (il y a des informations telles que la cible de construction et le code dans
build.zig Principalement utilisé dans) --
zig cibles: Affiche une liste de ** cibles de construction qui peuvent être gérées par
zig build-exe etc. ** --
zig version: affiche la version spécifiée de la commande zig (telle que 0.4.0) --
zig libc: Affiche les informations sur le chemin du répertoire libc de l'environnement d'exécution de la commande zig. --
zig zen`: affiche la politique de conception pour le langage zig
zig build-exe --release-fast -target wasm32-freestanding --name fft.wasm fft.zig
Cette ligne de commande génère un fichier de module .wasm
dans WebAssembly à partir du code de la bibliothèque zig.
Les options de ligne de commande ont les significations suivantes:
---- release-fast
: Spécifie une construction axée sur la vitesse
-- - relase-safe
: axé sur la vitesse, mais le traitement des interruptions spécifie un construit avec intégré
---- release-small
: Spécifie une construction sensible à la taille (sans interruptions)
---target wasm32-freestanding
est la spécification wasm 32 bits de WebAssembly, qui spécifie une cible autonome à importer. --
-target x84_64-windows: spécifiez la commande de la console Windows 64 bits (exe) comme cible. --
-- name fft.wasm: Définit le nom du fichier de sortie sur
fft.wasm`
fft
ou fft.exe
) basé sur le nom du fichier zig principal.Comme exemple de code suffisamment compliqué pour toucher les différentes fonctions du langage, nous adoptons une implémentation en boucle de la FFT à transformée de Fourier ultra-rapide et l'écrivons en zig.
--Référence: "Understanding Fast Fourier Transform FFT"
Écrivez «fft.zig» comme une bibliothèque sans la fonction «main», écrivez le test dans la syntaxe «test» et vérifiez l'opération en exécutant le test avec «zig test fft.zig».
Le code entier de «fft.zig» est inférieur à 200 lignes, y compris le code de test. Ici, nous allons diviser le code en 7 parties, découper les morceaux de code dans l'ordre à partir du haut et expliquer les fonctions et les caractéristiques du langage zig inclus dans chaque morceau de code.
Le fragment de code suivant est le code permettant de lire la valeur du rapport de circonférence et la fonction sin / cos à partir de la bibliothèque standard:
fft.zig(1)
const pi = @import("std").math.pi;
const sin = @import("std").math.sin;
const cos = @import("std").math.cos;
Dans zig, vous pouvez utiliser les fonctionnalités de ** bibliothèque standard ** par @ import (" std ")
.
Les identificateurs qui commencent par «@», tels que «@ import», sont appelés ** fonctions intégrées ** en zig, et la conversion de type est également fournie en fonction de cette fonction intégrée.
En zig, ** les variables (similaires à C) sont des modèles de zones de mémoire continues **.
Par conséquent, la variable ** const
ne peut pas changer tout le contenu de la zone mémoire **, et chaque membre dans le cas d'une structure et chaque élément dans le cas d'un tableau ne peuvent pas être modifiés.
De plus, dans zig, les arguments de fonction sont également traités comme des variables const
. Les variables dont le contenu peut être modifié sont déclarées avec var
.
const
zig a un type avec const
. Le type const
signifie que (en utilisant des pointeurs) les opérations de changement via cette ** référence ne sont pas autorisées **, et a un effet différent de la variable const
.
Par exemple, une variable avec var a: f64 = 0.0
peut changer sa valeur et peut être référencée avec var ptr: * const f64 = & a
, mais une modification de cette référence ptr. * = 1.0
entraînera une erreur de compilation. (Ptr. *
Se réfère à la zone mémoire à la pointe du pointeur dans la signification de tous les membres. * Ptr
en C).
Inversement, pour un pointeur vers une variable avec var a: f64 = 0.0
et un pointeur vers la variable const
avec const ptr: * f64 = & a
, ptr. * = 1.0
est possible.
De plus, pour la variable const
avec const a: f64 = 0.0
, une erreur de compilation se produira à moins que le pointeur du type const`` var ptr: * const f64 = & a
.
const
pour nommer le typeLa variable const
est également utilisée pour créer des alias vers des types (variables de type type
) tels que struct
/ ʻunion / ʻenum
/ ʻerror`.
La vérification du type de zig est déterminée par ** la correspondance de structure **, et non par la correspondance de nom de type. Par conséquent, il est possible d'affecter entre une variable de type alias et une variable de type nom d'origine.
Dans le code de, les fonctions et les variables de la bibliothèque standard sont utilisées en affectant le nom de la variable avec const
, mais` @ import (" std ") ʻest une expression et peut être utilisée n'importe où dans le code.
De plus, dans zig-0.4.0, sin
et cos
sont des fonctions fournies par la bibliothèque standard, mais dans la prochaine version, elles seront transférées vers le intégré et utilisées comme @ sin
et @ cos
. Il semble que.
struct
L'extrait de code suivant est une déclaration de la structure de nombre complexe gérée par FFT et son constructeur et sa méthode:
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
L'attribut pub
est un attribut attaché aux ** variables et fonctions qui sont utilisés lorsque ** @ import
est créé à partir d'autres fichiers zig (tels que pi
et sin
dans la bibliothèque standard).
Sans pub
, même si @ import
est fait, les variables et les fonctions ne peuvent pas être utilisées directement.
L'attribut ʻextport` est un attribut attaché aux ** fonctions et variables utilisées à partir de l'en-tête ** C (.h). Il s'agit d'un attribut attaché à la cible ** utilisé à partir du fichier objet ou du fichier bibliothèque.
Si vous ajoutez ʻexport à une fonction, la déclaration prototype correspondant à cette fonction est intégrée dans le fichier d'en-tête C. Si vous ajoutez ʻexport
à une variable d'un type comme struct
, les informations de type seront incorporées dans le fichier d'en-tête C.
Comme pour «externe» en C, ** ne doit pas être nommé dans tous les fichiers **.
struct
Il existe trois types de type struct
de zig, en fonction de la différence de disposition de la mémoire et de gestion dans ʻexport`.
--struct
: ** Inclut l'ordre des membres ** Ne spécifie pas la disposition de la mémoire ** struct
. Dans l'en-tête C à ʻexport, il est incorporé en tant que déclaration avec le pointeur
struct. --
packed struct: ** La disposition de la mémoire
structqui organise les membres dans l'ordre de déclaration **. Incorporé comme une déclaration de pointeur
struct dans l'en-tête C à ʻexport
--ʻExtern struct: La disposition de la mémoire
structqui organise les membres dans l'ordre de déclaration. Il est intégré en tant que déclaration de définition de membre ** de **
struct dans l'en-tête C au moment de ʻexport
.
struct
En zig, vous pouvez déclarer la fonction fn
inside struct
. Cette fonction est appelée sous la forme de type nom.fonction nom (...)
. Si le premier argument est un struct
défini, tel que ʻadd ou
sub, vous pouvez également l'écrire dans le style ** method call ** (
CN.add (a, b) ,
Il peut également être décrit comme a.add (b) `).
Dans zig-0.4.0, même si la fonction est dans struct
, dans l'en-tête C à ʻexport, il n'y a pas de préfixe du nom de la structure, et le prototype est déclaré avec le nom de la fonction tel quel (
CN). Quand .rect () ʻest ʻexport, il devient juste
rect () ʻau lieu deCN_rect ()
.
Le type numérique de zig spécifie le nombre de bits **. Il existe également un type numérique dépendant de la cible pour l'interface du langage C:
--Types non numériques: type
, noreturn
, void
, bool
--Type entier signé: ʻi8, ʻi16
, ʻi32, ʻi64
, ʻi128`
, ʻu16
, ʻu32, ʻu64
, ʻu128`
--Type de fraction flottante: «f16», «f32», «f64», «f128», ʻusize
, c_short
, c_ushort
, c_int
, c_uint
, c_long
, c_ulong
, c_longlong
, c_ulonglong
, c_longdouble
De plus, le côté droit de ** opération de décalage (>>
, <<
) a le nombre de bits -1 sur le côté gauche tel que ʻu3, ʻu4
, ʻu5, ʻu6
, ʻu7` **. Utilisez des types entiers comme limite supérieure (les variables sont descendues vers ces types).
Dans zig, ** les types bool
qui reçoivent true
et false
ne sont pas des types numériques **, mais ils peuvent être convertis en types numériques 0/1 en utilisant le.
--type
est le type de type (y compris les types personnalisés tels que struct
)
--void
est un type sans valeur et est également un type d'expression de bloc (sans passer une valeur avec break
)
--noreturn
est le type de retour pour les fonctions qui ne se terminent pas normalement, comme un processus se terminant au milieu ou une boucle infinie.
struct
L'initialiseur (littéral) de la structure est nom de la structure {. Nom du membre = valeur, ...}
. La syntaxe est similaire à l'initialiseur de membre de structure C99 ((type) {.member name = value, ...}
).
Les arguments de fonction et les valeurs de retour sont des modèles ** valeurs de copie **. Même s'il s'agit d'un struct
ou d'un tableau, il est traité comme une copie.
Il est possible d'utiliser le type pointeur comme type d'argument ou de valeur de retour.
Quelle que soit la taille du type, il sera copié sur la pile, donc si vous gérez mal le type de tableau, vous risquez d'atteindre la limite de pile du système d'exploitation.
L'extrait de code suivant est du code qui utilise la fonction membre CN
déclarée dans la syntaxe du code de test.
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
Le test" name "{...}
est la partie qui décrit le code ** qui est exécuté pendant la commande ** zig test
.
Depuis zig-0.4.0, vous ne pouvez pas définir une fonction directement dans test
.
Cependant, il est possible de déclarer une fonction dans struct
même dans test
, et il est possible d'utiliser la fonction en appelant une méthode.
La fonction de fn
dans struct
peut être appelée parnom de la structure.nom de la fonction ()
, en particulier lorsque le premier argument est que struct
, elle peut également être appelée parnom de variable.nom de fonction ()
, Il a la syntaxe.
ʻA.add (b) dans ce code peut aussi être appelé par
CN.add (a, b) `.
L'extrait de code suivant est un code de test à utiliser avec un tableau de «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);
}
Le type de tableau de zig est «type d'élément [nombre d'éléments]», ce qui signifie ** zone continue avec le nombre d'éléments spécifié. Le type de tableau multidimensionnel est «type d'élément [nombre d'éléments externes] [nombre d'éléments internes]». Dans la disposition de la mémoire, plusieurs éléments internes sont alignés avec plusieurs éléments externes.
Si le type d'élément est struct
, les membres sont disposés dans l'ordre, et cela devient une disposition de mémoire avec plusieurs éléments.
L'affectation d'un élément de tableau à un élément écrase la valeur.
La déclaration de non-initialisation d'un tableau par la variable var
est var nom de la variable: [nombre d'éléments] type d'élément = undefined
.
Le nombre d'éléments est obligatoire.
L'initialiseur de type de tableau (littéral) est [nombre d'éléments] type d'élément {valeur initiale 0, valeur initiale 1, ....}
.
Vous pouvez omettre le nombre premier total dans l'initialiseur.
Les initialiseurs avec la même valeur peuvent aussi avoir un [] type d'élément {valeur initiale 0, valeur initiale 1, ...} ** nombre d'itérations
. Dans ce cas, il s'agit d'un tableau de type du nombre de valeurs initiales ✕ du nombre d'éléments du nombre de répétitions.
La déclaration avec la valeur initiale du tableau par la variable var
utilise l'initialiseur du type tableau, et le type d'élément var variable name = [nombre d'éléments] {valeur initiale 0, valeur initiale 1, ...}
.
Déclaration avec valeur initiale pour const
variableconst variable name = [nombre d'éléments] Type d'élément {valeur initiale 0, valeur initiale 1, ...} ʻest généralement utilisé (exemple:
const v = [3] u8 {1,2,3} `).
Notez que déclarer une variable tableau dans une fonction ** alloue de l'espace dans la mémoire de la pile **. Pour cette raison, il est facile d'atteindre la limite de pile du système d'exploitation (8 Mo, etc.) lors de l'utilisation d'un tableau, donc soyez prudent. (La méthode d'utilisation du tas, etc. dans la bibliothèque standard sera décrite plus tard.)
L'extrait de code suivant est la définition de la fonction d'inversion de bits d'index de tableau revbit
dans FFT et son code de test.
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);
}
Dans zig, ** les opérations sur les bits intrinsèques du processeur fournies par les instructions du processeur sont fournies comme intégrées **.
--@ bitreverse (type numérique, numérique)
: Bit à l'envers (Remarque: le nom deviendra @ bitReverse
dans la prochaine version)
--@ ctz (numeric)
: compte les zéros à la fin du bit le moins significatif. S'il s'agit d'une valeur de puissance de 2, c'est la même valeur que «log2 (valeur numérique)».
D'autres incluent «@ clz» (nombre de 0 après le bit le plus significatif), «@ popCount» (nombre de 1), «@ bswap» (à l'envers en octets), et ainsi de suite.
Avec zig, vous ne pouvez développer implicitement ** que si le nombre de bits augmente pour le même type numérique. En dehors de cela, vous devez sélectionner et utiliser chaque ** fonction intégrée pour la diffusion selon la méthode de conversion **.
--@ truncate (type de conversion, valeur)
: Cast qui coupe uniquement les bits inférieurs jusqu'au nombre de bits du type de conversion
Dans l'opération de décalage, il est nécessaire de tronquer à un type numérique qui correspond au nombre de bits du type de la variable à décaler. Puisque 32 vaut 2 à la 5ème puissance, il est essentiel de tronquer le type à «u5» dans la rvaleur de l'opération de décalage de «u32».
Il existe de nombreuses fonctions intégrées de cast, mais les suivantes sont typiques
--@ bitCast (type de transformation, valeur)
: Cast qui enregistre la disposition des bits. Par exemple, lors de la conversion de «f64» en «u64» en tant que champ de bits.
--@ ptrCast (type de pointeur de conversion, valeur de pointeur)
: conversion de type pointeur
--@ intCast (type entier de conversion, valeur entière)
: Conversion de type de valeurs entières avec différents nombres de bits
--@ floatCast (conversion de type nombre flottant, nombre flottant)
: Conversion de type de nombre flottant avec un nombre différent de bits
--@ floatToInt (type entier de conversion, valeur décimale flottante)
: Conversion numérique en entier
--@ intToFloat (type de fraction immobile de conversion, valeur entière)
: Conversion numérique en fraction flottante
--@ ptrToInt (type entier de conversion, valeur du pointeur)
: Conversion de l'adresse du pointeur en entier
--@ intToPtr (type de pointeur de conversion, valeur entière)
: Conversion de la valeur d'adresse du pointeur en pointeur
--@ boolToInt (valeur de validité)
: Conversion de la valeur booléenne en entier (ʻu1`)
Le fragment de code suivant est le code d'implémentation de la version en boucle de 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);
}
}
La variable zig est une zone de mémoire et a un ** type de pointeur ** qui pointe vers son adresse de départ. Le type de pointeur vers le type valeur est un type avec «» au début du type, tel que « u8».
Dans zig, les variables de type pointeur et les variables du type à partir duquel elles sont basées sont accessibles avec la même description **.
Dans zig, les tableaux sont également des types de valeur qui représentent des zones de mémoire continues. Contrairement au langage C, ** les types de tableaux et les types de pointeurs vers les tableaux sont clairement distingués **.
Dans zig-0.4.0, le type de pointeur de tableau est représenté par le «type d'élément [*]». Le type de pointeur de tableau peut également pointer vers des éléments au milieu du tableau, et comme C, le nombre d'index de décalage peut être ajouté ou soustrait.
La variable de type pointeur du tableau peut accéder aux éléments du tableau avec la même description que la variable de type tableau.
Les autres types liés aux pointeurs de tableau sont:
-- * [Nombre d'éléments] Type d'élément
: Un pointeur vers le tableau entier. Le nombre d'éléments (.len
) est fixé au moment de la compilation
-- [] Type d'élément
: Le nombre d'éléments (.len
) est obtenu au moment de l'exécution ** Slice **
Une tranche en zig est une structure de données intégrée en zig qui pointe vers une zone continue de la zone de tableau, et possède un pointeur de tableau (.ptr
) pour l'adresse de départ et le nombre d'éléments ( .len
).
Résumé,
--const a: [12] u8 = [] u8 {1} ** 12;
: Variable de tableau. Le nombre d'éléments est fixé au moment de la compilation
--const b: * const [12] u8 = & a;
: variable de pointeur vers le tableau entier. Le nombre d'éléments est fixé au moment de la compilation
--const c: [] const u8 = & a;
: Type de tranche. Le nombre d'éléments est fixé à l'exécution
--const d: [*] const u8 = & a;
: variable de pointeur vers le tableau. N'a pas le nombre d'éléments
Les trois variables ci-dessus peuvent récupérer le nombre d'éléments avec «a.len», «b.len» et «c.len», mais «d» n'a pas ce moyen.
Voici la syntaxe pour l'attribution à d'autres tranches et pointeurs de tableau.
const c0: []const u8 = a[0..];
const c1: []const u8 = b;
const c2: []const u8 = c[1..];
const c3: []const u8 = d[0..12];
const d1: [*]const u8 = b;
const d2: [*]const u8 = c.ptr;
const d3: [*]const u8 = d + 1;
Pour créer une tranche à partir d'un pointeur de tableau (c3
), vous devez spécifier l'index ** end point **.
Les variables zig ont une portée de bloc, mais contrairement à de nombreux langages, vous ne pouvez pas définir une variable avec le même nom qu'une variable définie en dehors du bloc ** à l'intérieur du bloc.
La variable «i» est utilisée dans la première partie de la fonction «fftc» pour copier dans l'index inversé de bits.
Si vous supprimez le {
}
qui entoure cette partie, vous obtiendrez une erreur de compilation car ʻi` est également utilisé dans la boucle ci-dessous.
zig a une boucle de «for» et «while».
for
: boucle d'itérateur vers tableau, etc. while
: boucle conditionnelleLa boucle while
a les variations suivantes:
--while (expression conditionnelle) {...}
: Equivalent àwhile (expression conditionnelle) {...}
en langage C
--while (expression conditionnelle): (expression post-traitement) {...}
:for (; expression conditionnelle; expression post-traitement) {...}
équivalent en langage C
while (Formule d'exécution) |variable| {...}
:En langage Cwhile ((variable = Formule d'exécution)) {...}
ÉquivalentDans zig, ** les variables de compteur de boucle dans while
doivent être déclarées en dehors de la boucle **.
En zig, for
et while
peuvent recevoir une expression dans la partie ʻelse` qui est traitée après le bouclage.
La boucle peut avoir une valeur de résultat comme expression. La partie ʻelseest requise dans l'expression de boucle, et la valeur transmise à la valeur de
break` est la valeur de l'expression.
const v = while (cond) {break 1;} else 2;
--Expression de boucle minimale 2: const v = while (cond) {break 1;} else ret: {break: ret 2;};
const v = if (cond) 1 else 2;
)Dans ces deux exemples, "v" est "1" si "cond" est "vrai" et "v" est "2" si "faux".
La partie ʻelse` dans l'exemple minimal 2 n'est pas une syntaxe de style bloc, mais une ** expression de bloc **, qui est l'une des syntaxes d'expression de zig. ** La valeur de l'expression de bloc doit être passée dans un «break» étiqueté **, pour lequel vous devez étiqueter l'expression de bloc comme quelque chose.
Vous pouvez également ajouter ʻinline à la
fn de zig, ce qui force tous les appels à être insérés. Cependant, ʻinline
et fn
ont des restrictions d'utilisation. Par exemple, vous ne pourrez pas passer «fn» à une variable ou un argument.
De plus, contrairement au langage C, en zig, ʻinline provoquera une erreur de compilation s'il y a une situation où même un seul ne peut pas être inséré dans
fn`.
En zig, il est possible de spécifier ** force inline ** sur une fonction régulière sur l'appelant au lieu de spécifier tout en ligne dans la définition. C'est le «@ inlineCall» intégré.
L'extrait de code suivant est un code de test pour l'implémentation de la version en boucle de FFT.
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);
}
}
}
test
peut être effectuée dans struct
Dans zig-0.4.0, fn
ne peut pas être décrit au niveau supérieur de la partie test
, mais la fonction peut être décrite dans ** struct
**, et il était possible de l'appeler comme une méthode statique.
En zig, la syntaxe pour obtenir un pointeur à partir d'un type valeur est & variable
.
Contrairement au langage C, les variables de tableau ne peuvent pas être traitées implicitement comme des pointeurs de tableau et doivent être ** explicitement ajoutées avec &
**.
La méthode de référencement d'un élément à partir d'un type pointeur est la même que celle d'un type valeur (ʻa [i] ʻet ʻa.m`).
En tant qu'expression spécifique au pointeur, il existe une ** déréférence de valeur entière *, qui est suivie d'un . *
(V = a. *
Ou ʻa. * = V). Cela équivaut à la déréférence du pointeur
ptr` en langage C.
warn
Vous pouvez utiliser la bibliothèque standard warn
pour avoir les caractères de sortie de la console (vers stderr
) en utilisant une chaîne de format.
Il est également généré en exécutant zig test
.
test zig
L'exécution de zig test fft.zig
sur ce fft.zig
donne les résultats suivants:
$ 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.
$
fft.zig
Remarque: dans zig, le ** processus de démarrage des commandes et des bibliothèques est implémenté ** dans la bibliothèque standard. La description ici est basée sur l'implémentation standard de la bibliothèque dans zig-0.4.0. Les spécifications ici peuvent changer considérablement à l'avenir.
Tout d'abord, nous expliquerons uniquement comment utiliser la fonction main
qui est appelée depuis la bibliothèque standard zig lors de l'exécution d'une commande, puis nous expliquerons le programme zig qui appelle l'implémentation FFT mentionnée ci-dessus par @ import (" fft.zig ")
.
main
de zigVoici un exemple de code de la fonction de point d'entrée main
dans l'exécution de la ligne de commande, qui ne gère que les variables d'environnement, les arguments de commande et le code de sortie:
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);
}
main
et code de sortieLa fonction ** main** sans arguments
pub` est le point d'entrée de votre code utilisateur.
Les types de retour de main
sont noreturn
(quitter sans être exécuté par le nom de famille), ʻu8 (la valeur de retour devient le code de sortie) et
void(le code de sortie est 0). ,
! Void (erreur ou
void`).
Dans le cas de ce code, main
est réglé sur noreturn
car ʻos.exit ()de la bibliothèque standard pour quitter utilisée dans est une fonction
noreturn. Lorsque vous utilisez ʻos.exit ()
, passez le code de sortie comme argument (type ʻu8`).
Si la valeur de retour est ! Void
, le code de sortie sera 1
s'il y a une erreur.
Le type d'ensemble d'erreurs ** défini par la syntaxe ** ʻerror de zig est l'un des types personnalisés tels que
struct et ʻenum
, et est un type d'énumération similaire à ʻenum. Exemple:
const MyError = error {Fail, Dead,};`
La différence avec ʻenum` est que le type d'ensemble d'erreurs gère l'erreur qui est son élément collectivement comme suit.
--Existence de ** type ʻany error ** qui reçoit tous les types de jeux d'erreurs personnalisés -Deux types de jeux d'erreurs**Type de jeu d'erreurs d'ensemble de somme**Peut être fait(
const ErrorsC = ErrosA || ErrorsB;`)
MyError.Dead
is const e: MyError = error.Dead;
Peut être initialisé)
--Il existe un type d'union d'erreur (type avec !
) Et une syntaxe dédiée à l'union d'erreur (opérateur catch
, opérateur try
).Les types avec !
, Tels que! U8
pour les types de retour et MyError! U8
pour les types de variables, sont les ** types d'union d'erreur ** de zig.
Le type d'union d'erreur est une ** variable ou un type de retour qui peut accepter à la fois le type de valeur ** et le type «erreur».
En faisant de ce type d'union d'erreur le type de retour, en zig, ** les erreurs seront également des valeurs return
**.
Le type de retour «! U8» est le type d'ensemble d'erreurs sur la gauche qui est déduit du code d'implémentation de la fonction, et est en fait un type d'union d'erreur tel que «MyError! U8».
Pour passer d'un type d'union d'erreur à une expression de type valeur, zig peut utiliser l'opérateur binaire catch
et l'opérateur de terme unique try
.
catch
Opérateur binaire:Vous pouvez transmettre la valeur de secours en cas d'erreur. Exemple: const v: u8 = errorOrInt() catch |err| 10;
try
: En cas d'erreur, l'expression renvoie
l'erreur. Exemple: const v: u8 = try errorOrInt ();
catch
Section de capture de l'opérateur|err|
Peut être omis si la valeur d'erreur n'est pas utilisée.
La valeur de retour d'une fonction qui utilise l'opérateur try
à l'intérieur doit être de type error union.
Il est également possible de trier l'heure d'erreur comme ʻelse du type d'union d'erreur en utilisant une expression conditionnelle de l'expression ʻif
ou de l'expression switch
.
if
Exemple: const v: u8 = if (errorOrInt()) |n| n else |err| 0;
--switch
Exemple: const v: u8 = switch (errorOrInt ()) {n => n, else => 0,};
Les variables d'environnement peuvent être récupérées avec la bibliothèque standard ʻos.getEnvPosix () `. La valeur de retour de cette fonction est «? [] Const u8».
Le type avec ce «?» Dans la tête est le ** type optionnel ** de zig. Le type facultatif est la ** valeur ou valeur de zig ** null
. (Par conséquent, le type de pointeur ne peut pas être «null» sauf s'il s'agit d'un type facultatif.)
Comme pour le type d'union d'erreur, il peut s'agir d'un type valeur en donnant une valeur de repli avec l'expression ʻif ou l'expression
switch, et lorsque
null, avec ʻelse
.
Il a également la même position que l'opérateur binomial catch
dans l'union d'erreur, avec l'opérateur binomial ʻorelse` pour les types optionnels.
const v: u8 = nullOrInt () orelse 0;
Vous pouvez également le forcer à un type valeur (comme l'opérateur de terme unique try
de type ʻerror) en ajoutant
const v: u8 = nullOrInt ().? ʻAnd.?
.
Cependant, contrairement à «try», aucune erreur ou «null» n'est «return», et il semble que zig-0.4.0 entraînera une terminaison forcée intolérable.
Vous pouvez récupérer des arguments de ligne de commande avec ʻos.args () ʻ dans la bibliothèque standard.
Ceci est un itérateur et peut être récupéré l'un après l'autre sous forme de chaîne optionnelle de type ? [] Const u8
en utilisant la méthode nextPosix ()
.
Pour récupérer séquentiellement à partir de l'itérateur jusqu'à ce qu'il devienne "null", utilisez une boucle while
** qui capture la ** valeur de la condition. S'il devient «nul», il entrera dans la partie «autre».
Lorsqu'il est converti en binaire, le ** 0e argument de commande est le nom de la commande ** et l'argument est le suivant.
Pour zig run
, vous pouvez passer un argument de commande après --
dans zig run example-main.zig --a b c
. Dans zig-0.4.0, dans ce cas, l'argument commence à 0e.
fft.zig
comme @ import
Ce qui suit est un programme qui affiche les résultats FFT / IFFT avec les données utilisées dans le test, puis affiche le temps de référence pour l'exécution de FFT et IFFT de 1 million (2 à la 20e puissance) éléments.
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);
}
}
@ Importer
à partir du fichier zigVous pouvez ** @ import
et utiliser les variables et les fonctions de l'attribut pub
** pour votre propre fichier source zig ainsi que pour la bibliothèque standard.
En fait, la bibliothèque standard existe également en tant que fichier source zig.
Vous pouvez vérifier le code d'implémentation en le développant sous lib / zig / std /
dans le répertoire d'installation.
Dans le langage zig, fondamentalement, les variables utilisent la zone de la mémoire de pile (mémoire statique). Si vous utilisez un grand tableau, ** vous dépasserez la limite de la pile du système d'exploitation au moment de l'exécution **, votre programme doit donc utiliser une zone de mémoire hors pile, telle qu'un tas.
La bibliothèque standard fournit un ** allocateur de mémoire ** qui gère la mémoire du tas. Voici l'implémentation d'allocateur dans zig-0.4.0:
--heap.DirectAllocator
: Allocateurs pour allouer la mémoire du tas (ʻinit,
deinit, ʻalloc
, shrink
, realloc
)
--heap.ArenaAllocator
: Un allocateur qui configure un pool de mémoire en utilisant d'autres allocateurs et libère toute la mémoire allouée jusqu'à présent avec deinit
.
--mem.Allocator
: Interface pour allouer de la mémoire en tapant (create
, destroy
)
Dans zig-0.4.0, l'allocateur heap
a l'interface mem.Allocator
comme membre .allocator
(dans ce code ʻarena.allocator`).
Notez que le type de valeur est passé dans l'argument create
de mem.Allocator
, mais la valeur de retour est un type de pointeur (type union d'erreur) vers ce type de valeur.
--ʻAllocator.create ([1048576] CN) Le type de retour de ʻis! * [1048576] CN
--var f = essayez allocator.create ([1048576] CN);
ʻa type est
* [1048576] CN --
f: * [1048576] CN peut être assigné à la variable
[*] CN(le deuxième argument de
fft ()` dans le code) sans conversion explicite.
defer
L'instruction defer
décrit à l'avance le code qui sera exécuté lorsque le bloc s'échappe.
S'il y a plusieurs différer
s dans le même bloc, ils seront exécutés ** dans l'ordre à partir de l'arrière **.
En écrivant dans un endroit proche à l'aide de «différer», il est possible de décrire le processus d'initialisation et le processus de fin correspondant à l'initialisation en tant que paire.
Un similaire est ʻerrdefer. Le
différer qui est exécuté lorsque le bloc s'échappe ** lorsque l'erreur est
renvoyée. Lors de l'échappement d'un bloc avec l'erreur
return, il est exécuté dans l'ordre de l'arrière dans une séquence mixte de ʻerrdefer
et defer
.
A partir de ce fft-bench-zig.zig
, vous pouvez générer le fichier exécutable fft-bench-zig
avec la ligne de commande suivante.
$ zig build-exe --release-fast fft-bench-zig.zig
Vous pouvez également exécuter votre code avec zig run fft-bench-zig.zig
sans avoir à le construire.
fft.zig
dans un module WebAseembly et utilisez-le en JavaScriptLors de la génération d'un module WebAssembly à partir de code écrit dans un langage de programmation normal tel que Rust, afin d'utiliser le module wasm, il est nécessaire de prendre en compte l'implémentation de la bibliothèque d'exécution du langage de programmation, en particulier les fonctions liées à la mémoire du tas. Dans les importations et les exportations du module wasm, il est nécessaire d'avoir une méthode d'opération de mémoire, etc. dans cette langue, et de l'implémenter et de l'utiliser en fonction de son utilisation.
Cependant, si vous créez un module wasm à partir d'un programme zig, vous générerez un module wasm ** qui n'exposera aucune méthode liée à l'exécution du langage. La plus grande différence est que zig n'utilise fondamentalement pas de mémoire de tas (le langage C est fondamentalement le même, mais je l'écris sans utiliser stdlib.h
qui a du tout une gestion de tas. C'est plus difficile).
Dans le fichier fft.zig
précédemment implémenté, les fonctions`` fft et ʻifft
sont ʻexport`, et il est possible de compiler vers un fichier wasm tel quel.
Vous pouvez générer fft.wasm
avec la ligne de commande suivante.
$ zig build-exe --release-fast -target wasm32-freestanding --name fft.wasm fft.zig
fft.wasm
dans node.jsLe code JavaScript ci-dessous est le programme fft-bench-nodejs.js
qui charge et utilise le fichier wasm généré à partir de ce code zig.
Semblable au précédent fft-bench-zig.zig
, il s'agit d'un benchmark qui affiche la conversion FFT / IFFT avec les données d'exemple, puis affiche le temps d'exécution de FFT / IFFT de 1 million de données d'élément.
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);
Vous pouvez mettre le fft.wasm
généré et ce fft-bench-nodejs.js
dans le même répertoire et l'exécuter avec node fft-bench-node.js
.
Puisqu'il n'y a aucune fonction dans le runtime zig qui doit être ** importéede l'extérieur **, créez-la avec ʻawait WebAssembly.instantiate (buf, {})
.
ʻExports contient
__wasm_call_ctorset
memmory en plus des fonctions ʻexport
dans le code.
__wasm_call_ctors
est une fonction, et si le code zig contient une initialisation, il sera initialisé en appelant ceci **__wasm_call_ctors ()
.
memory
est un objet WebAssembly Memory
initialement vide.
Si vous souhaitez utiliser de la mémoire dans votre code, vous devez appeler la méthode memory.grow ()
pour allouer une zone mémoire.
L'argument est le nombre de pages mémoire à développer, 64 Ko par page.
Memory.buffer
dans ʻexports est un objet Typed Array ʻArrayBuffer
.
Du côté JavaScript, enveloppez ce memory.buffer
avec Float64Array
etc., entrez une valeur et faites-y référence.
Le ** pointeur est la valeur d'adresse **, et la valeur d'adresse mémoire WebAssembly utilise la valeur de décalage d'octet ** memory.buffer
** comme ʻArrayBuffer. Notez que zig a un alignement pour chaque nombre d'octets du type numérique. Pour
f64, la valeur de l'adresse doit être un multiple de 8 et le deuxième argument du constructeur
Float64Array` doit utiliser ce multiple de 8.
zig crée un wasm qui ne nécessite pas que le moteur d'exécution du langage soit fourni par un module externe, et il n'est pas impossible d '«importer» un module externe que vous avez préparé et de l'utiliser dans wasm.
Si vous voulez donner une fonction implémentée en externe à ʻimports et l'utiliser, vous pouvez déclarer un prototype de fonction ** ʻextern
tel que ʻextern fn sin (x: f64) f64; et l'utiliser dans votre code **. Est possible. Dans zig-0.4.0, le nom du module externe dans le fichier wasm est fixé à
" env "et peut être utilisé en le donnant comme
WebAssembly.instantiate (buf, {env: {sin: Math.sin}})`. ..
Il y a plus de restrictions sur les fonctions qui peuvent être «exportées» vers le côté JavaScript en tant que WebAssembly.
Par exemple, dans zig-0.4.0, les ** fonctions ** avec une valeur de retour de type struct
, telle que CN.rect ()
, sont soumises à cette limitation.
Soyez prudent avec ʻexport dans votre code zig, car vous ne pourrez pas convertir en wasm s'il y a même un ʻexport
pour cette limitation.
En guise de contre-mesure, il est possible de diviser le fichier zig du code d'implémentation uniquement pub
dans le fichier zig uniquement @ import
et d'ajouter l''export` approprié pour chaque cible. Je vais.
fft.zig
un fichier objet et utilisez-le à partir du langage CLe code zig peut être converti en un fichier objet disponible depuis C avec la commande zig build-obj
. Un fichier d'en-tête C correspondant au fichier objet est également généré en même temps.
Ici, créez un programme en langage C qui utilise fft.zig
, compilez-le et générez un fichier exécutable.
Générez le fichier objet fft.o
et le fichier d'en-tête fft.h
à partir de fft.zig
avec la ligne de commande suivante:
$ zig build-obj --release-fast fft.zig
Le fichier d'en-tête généré fft.h
a le contenu suivant.
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
Vous pouvez voir que ce fichier d'en-tête fft.h
contient la fonction ʻexport et
struct` dans le code zig.
Ce qui suit est le code C11 fft-bench-cc
de la fonction main
qui exécute et affiche FFT / IFFT avec le même exemple de données utilisé dans le test, puis exécute FFT / IFFT 10 millions d'éléments de données. est.
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;
}
Si vous utilisez des structures ou des fonctions simplement parce qu'elles sont implémentées dans zig, vous n'avez rien à faire de spécial du côté ** C **.
La ligne de commande pour générer le fichier exécutable fft-bench-c
est la suivante (l'implémentation du compilateur C suppose clang ou gcc):
$ cc -Wall -Wextra -std=c11 -pedantic -O3 -o fft-bench-c fft-bench-c.c fft.o
(Cette option spécifie que le code doit être interprété par le standard C11 non étendu lui-même)
Contrairement au cas de fft-bench-c.c
, il est également possible d'importer un fichier objet écrit en langage C avec le programme zig en utilisant le fichier d'en-tête C.
Ici, implémentez FFT avec le code C11 fft-c.c
, et décrivez le fichier d'en-tête C fft-c.h
pour utiliser l'implémentation de zig.
Ensuite, avec fft-bench-cimport.zig
, qui fait la même chose que fft-bench-zig.zig
, lisez et implémentez fft-c.h
.
Ce qui suit est l'implémentation FFT fft-c.c
utilisant le nombre complexe double complexe
dans la spécification C11.
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;
}
L'algorithme est le même que «fft.zig».
De plus, puisque «double complexe» est une valeur dans laquelle la valeur «double» de la partie réelle et de la partie imaginaire sont disposées consécutivement, il a la même disposition de mémoire ** que le «CN» de ** «fft.zig».
Nous fournissons également une fonction constructeur rect
qui crée un nombre complexe à partir de la valeur double
afin que vous n'ayez pas à opérer sur le double complexe
sur zig.
Le fichier d'en-tête C fft-c.h
a un contenu qui correspond à la méthode du fichier d'en-tête écrit par build-obj
de zig.
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
Puisque zig n'a pas de type correspondant au type de nombre complexe, dans le fichier d'en-tête C lu par le programme zig, struct
with double
2 est utilisé à la place.
Semblable au précédent fft-bench-zig.zig
, il s'agit d'un benchmark qui affiche la conversion FFT / IFFT avec les données d'exemple, puis affiche le temps d'exécution de FFT / IFFT de 1 million de données d'élément.
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
intégréEn utilisant ** @ cImport
built-in **, il peut être importé en tant que structure zigou fonction à partir de l'en-tête C. Dans
@ cImport, spécifiez le nom du fichier d'en-tête à interpréter par **
@ cInclude` intégré **.
Le type pointeur de zig est divisé entre le type pointeur du type valeur et le type pointeur vers le tableau, mais le type pointeur de C ne fait pas la distinction entre eux.
Par conséquent, si le type de pointeur est utilisé dans l'argument ou la valeur de retour de la fonction @ cImport
, il est décrit par [* c] type de valeur
qui peut correspondre à l'un ou l'autre type sur zig ** type de pointeur C * Interprété comme *.
Ici, la fonction fft
de l'en-tête C est de type fft (n: i32, f: [* c] CN, F: [* c] CN)
.
Ce type de pointeur C [* c] CN
est attribué à partir de * CN
et de [*] CN
sans conversion (code tardif).
Cependant, dans zig-0.4.0, & f: * [16] CN
, qui est assigné à [*] CN
sans conversion, nécessite un cast explicite par @ ptrCast
(première moitié du code).
-- * [16] CN
= Aucune conversion requise => [*] CN
= Aucune conversion requise => [* c] CN
-- * [16] CN
= Obligatoire @ ptrCast
=> [* c] CN
@ cImport
ʻextern fn`Vous pouvez voir ce que vous faites avec @ cImport
en exécutant la commande zig translate-c fft-c.h
et en regardant le code zig pour cette sortie. Cependant, comme tout le contenu de la bibliothèque standard qui est «# include» est inclus, une grande quantité apparaîtra.
Si vous ne retirez que la partie nécessaire,
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;
Ce sera le même contenu que. Si vous incorporez ce contenu, vous n'avez pas besoin de fft-c.h
ou de l'option zig build`` -isystem .
pour ajouter le chemin d'inclusion.
De plus, si vous intégrez le chemin du fichier objet (. /
Est obligatoire) immédiatement après ʻextern,
--object fft-c.oest également inutile. (Si
. / Est manquant, il essaiera de résoudre en tant que bibliothèque (
" fft-c "ʻest traité comme -lfft-c
).)
En zig, si vous avez un prototype de fonction «externe», il sera déclaré que ** utilise la fonction du même nom dans la bibliothèque ou le fichier objet **.
Vous pouvez le créer avec la ligne de commande suivante.
$ 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
L'option --object fft-c.o
spécifie le fichier objet, et -isystem .
spécifie le répertoire pour lire le fichier d'en-tête avec @ cInclude
.
Même dans ce cas, vous pouvez l'exécuter sans créer de fichier exécutable avec zig run --object fft.o -isystem .fft-bench-cimport.zig
.
build.zig
Il semble que zig vise à activer les builds multiplateformes avec la commande zig build
sans utiliser un système de build externe tel que make
.
Dans build.zig
, qui décrit le contenu de la construction en exécutant zig build
, décrivez la fonction ** pub`` build
**.
En cela, le contenu à exécuter dans la construction est défini comme ** graphe d'étape **. Chaque compilation et exécution de commande est une étape. Pour chaque étape, vous pouvez spécifier les dépendances d'étape qui doivent être effectuées à l'avance.
Il existe également une ** étape nommée **, qui est la cible de construction, à laquelle vous pouvez ajouter tour à tour chaque étape de compilation et d'exécution de commande.
Il y a une ** étape par défaut ** comme système de construction, et elle sert à décrire ce qui est exécuté par zig build
sans spécifier le nom de l'étape. (Certaines des étapes nommées seront ajoutées aux étapes par défaut.)
Cependant, depuis zig-0.4.0, il semble que tout ce qui peut être décrit dans build.zig
n'est pas complètement complet. Par exemple, pour compiler ou supprimer un fichier C, vous devez exécuter la commande dans l'environnement d'exécution directement avec un argument.
(La documentation 0.4.0 a une étape appelée ʻaddCExecutable`, mais elle n'existe pas réellement dans zig-0.4.0)
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);
}
}
En cela, nous faisons 6 étapes nommées. Du haut pour chaque bloc:
-- fft.wasm
: wasm Étapes pour la génération de fichier
--fft-bench-zig
: étapes pour générer un fichier exécutable uniquement à partir du fichier zig
--fft-bench-c
: étapes pour faire d'un fichier zig un fichier objet et créer un fichier exécutable avec le fichier c qui l'utilise comme fichier principal
--fft-bench-cimport
: étapes pour faire d'un fichier c un fichier objet et créer un fichier exécutable avec le fichier zig qui l'utilise comme fichier principal
--clean
et dist-clean
: étapes pour supprimer les fichiers générés par la construction
À l'exception de «clean» et «dist-clean», ils ont été ajoutés aux étapes par défaut.
Vous pouvez voir quelles étapes nommées sont disponibles avec zig build --help
.
Je pense que ce sera un grand changement dans la future mise à niveau de la version, mais cela peut être une référence pour savoir comment le configurer en tant que description de construction dans chacun des quatre cas à construire.
Voici le résultat du benchmark du dernier fichier exécutable généré. C'est le temps nécessaire pour FFT / IFFT de 1 million d'éléments dans la seconde moitié.
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) |
De plus, pour référence dans wasm, le benchmark FFT / IFFT avec le même contenu effectué dans "Circonstances de l'assemblage Web en 2019" a été exécuté dans le même environnement. Les résultats sont également répertoriés.
--Source: https://gist.github.com/bellbind/36b7e71b3203e868c10405a61cdd2d09
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 |
Le fait qu'il ne soit pas nécessaire d'appeler sin
ou cos
par ʻimport` lors de l'incorporation d'une fonction mathématique avec zig peut être important en termes de vitesse d'exécution même avec l'ajout de l'optimisation mathématique intrinsèque v8 ( Dans le wasm basé sur zig, les fonctions sin et cos elles-mêmes étaient incorporées).
Recommended Posts