J'ai d'abord lu la réponse à Pourquoi ma fonction WebAssembly est-elle plus lente que l'équivalent JavaScript?
Mais cela a peu éclairé le problème, et j'ai investi beaucoup de temps qui pourrait bien être ce truc jaune contre le mur.
Je n'utilise pas de globaux, je n'utilise pas de mémoire. J'ai deux fonctions simples qui trouvent la longueur d'un segment de ligne et les comparent à la même chose dans le vieux Javascript. J'ai 4 params 3 autres habitants et retourne un flotteur ou double.
Sur Chrome le Javascript est 40 fois plus rapide que le webAssembly et sur firefox le wasm est presque 300 fois plus lent que le Javascript.
J'ai ajouté un cas de test à jsPref WebAssembly V Javascript math
Soit
Veuillez être l'option 1.
J'ai lu le cas d'utilisation de webAssembly
Réutilisez le code existant en ciblant WebAssembly, intégré dans une application JavaScript/HTML plus grande. Cela peut aller de simples bibliothèques d'aide au déchargement de tâches orienté calcul.
J'espérais pouvoir remplacer certaines bibliothèques de géométrie par webAssembly pour obtenir des performances supplémentaires. J'espérais que ce serait génial, comme 10 fois ou plus plus vite. MAIS 300 fois plus lent WTF.
Ce n'est pas un problème d'optimisation JS.
Pour m'assurer que l'optimisation a le moins d'effet possible, j'ai testé en utilisant les méthodes suivantes pour réduire ou éliminer tout biais d'optimisation.
c += length(...
pour s'assurer que tout le code est exécuté.bigCount += c
pour garantir que la fonction entière est exécutée. Pas besoinMath.hypot
pour prouver que le code est en cours d'exécution.// setup and associated functions
const setOf = (count, callback) => {var a = [],i = 0; while (i < count) { a.Push(callback(i ++)) } return a };
const Rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const a = setOf(100009,i=>Rand(-100000,100000));
var bigCount = 0;
function len(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.sqrt(nx * nx + ny * ny);
}
function lenSlow(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.hypot(nx,ny);
}
function lenEmpty(x,y,x1,y1){
return x;
}
// Test functions in same scope as above. None is in global scope
// Each function is copied 4 time and tests are performed randomly.
// c += length(... to ensure all code is executed.
// bigCount += c to ensure whole function is executed.
// 4 lines for each function to reduce a inlining skew
// all values are randomly generated doubles
// each function call returns a different result.
tests : [{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += length(a1,a2,a3,a4);
c += length(a2,a3,a4,a1);
c += length(a3,a4,a1,a2);
c += length(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length64",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lengthF(a1,a2,a3,a4);
c += lengthF(a2,a3,a4,a1);
c += lengthF(a3,a4,a1,a2);
c += lengthF(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length32",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += len(a1,a2,a3,a4);
c += len(a2,a3,a4,a1);
c += len(a3,a4,a1,a2);
c += len(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length JS",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lenSlow(a1,a2,a3,a4);
c += lenSlow(a2,a3,a4,a1);
c += lenSlow(a3,a4,a1,a2);
c += lenSlow(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "Length JS Slow",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lenEmpty(a1,a2,a3,a4);
c += lenEmpty(a2,a3,a4,a1);
c += lenEmpty(a3,a4,a1,a2);
c += lenEmpty(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "Empty",
}
],
Parce qu'il y a beaucoup plus de frais généraux dans le test, les résultats sont plus proches, mais le code JS est toujours plus rapide de deux ordres de grandeur.
Notez la lenteur de la fonction Math.hypo
t est. Si l'optimisation était en vigueur, cette fonction serait proche de la fonction len
plus rapide.
/*
=======================================
Performance test. : WebAssm V Javascript
Use strict....... : true
Data view........ : false
Duplicates....... : 4
Cycles........... : 147
Samples per cycle : 100
Tests per Sample. : undefined
---------------------------------------------
Test : 'length64'
Mean : 12736µs ±69µs (*) 3013 samples
---------------------------------------------
Test : 'length32'
Mean : 13389µs ±94µs (*) 2914 samples
---------------------------------------------
Test : 'length JS'
Mean : 728µs ±6µs (*) 2906 samples
---------------------------------------------
Test : 'Length JS Slow'
Mean : 23374µs ±191µs (*) 2939 samples << This function use Math.hypot
rather than Math.sqrt
---------------------------------------------
Test : 'Empty'
Mean : 79µs ±2µs (*) 2928 samples
-All ----------------------------------------
Mean : 10.097ms Totals time : 148431.200ms 14700 samples
(*) Error rate approximation does not represent the variance.
*/
Quel est l'intérêt de WebAssambly s'il n'optimise pas
Fin de la mise à jour
Trouvez la longueur d'une ligne.
Source originale dans un langage personnalisé
// declare func the < indicates export name, the param with types and return type
func <lengthF(float x, float y, float x1, float y1) float {
float nx, ny, dist; // declare locals float is f32
nx = x1 - x;
ny = y1 - y;
dist = sqrt(ny * ny + nx * nx);
return dist;
}
// and as double
func <length(double x, double y, double x1, double y1) double {
double nx, ny, dist;
nx = x1 - x;
ny = y1 - y;
dist = sqrt(ny * ny + nx * nx);
return dist;
}
Le code se compile en Wat pour la relecture
(module
(func
(export "lengthF")
(param f32 f32 f32 f32)
(result f32)
(local f32 f32 f32)
get_local 2
get_local 0
f32.sub
set_local 4
get_local 3
get_local 1
f32.sub
tee_local 5
get_local 5
f32.mul
get_local 4
get_local 4
f32.mul
f32.add
f32.sqrt
)
(func
(export "length")
(param f64 f64 f64 f64)
(result f64)
(local f64 f64 f64)
get_local 2
get_local 0
f64.sub
set_local 4
get_local 3
get_local 1
f64.sub
tee_local 5
get_local 5
f64.mul
get_local 4
get_local 4
f64.mul
f64.add
f64.sqrt
)
)
Comme wasm compilé dans une chaîne hexadécimale (la note n'inclut pas la section de nom) et chargé à l'aide de WebAssembly.compile Les fonctions exportées s'exécutent ensuite avec la fonction Javascript len (dans l'extrait ci-dessous)
// hex of above without the name section
const asm = `0061736d0100000001110260047d7d7d7d017d60047c7c7c7c017c0303020001071402076c656e677468460000066c656e67746800010a3b021c01037d2002200093210420032001932205200594200420049492910b1c01037c20022000a1210420032001a122052005a220042004a2a09f0b`
const bin = new Uint8Array(asm.length >> 1);
for(var i = 0; i < asm.length; i+= 2){ bin[i>>1] = parseInt(asm.substr(i,2),16) }
var length,lengthF;
WebAssembly.compile(bin).then(module => {
const wasmInstance = new WebAssembly.Instance(module, {});
lengthF = wasmInstance.exports.lengthF;
length = wasmInstance.exports.length;
});
// test values are const (same result if from array or literals)
const a1 = Rand(-100000,100000);
const a2 = Rand(-100000,100000);
const a3 = Rand(-100000,100000);
const a4 = Rand(-100000,100000);
// javascript version of function
function len(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.sqrt(nx * nx + ny * ny);
}
Et le code de test est le même pour les 3 fonctions et s'exécute en mode strict.
tests : [{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
length(a1,a2,a3,a4);
}
},
name : "length64",
},{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
lengthF(a1,a2,a3,a4);
}
},
name : "length32",
},{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
len(a1,a2,a3,a4);
}
},
name : "lengthNative",
}
]
Les résultats des tests sur FireFox sont
/*
=======================================
Performance test. : WebAssm V Javascript
Use strict....... : true
Data view........ : false
Duplicates....... : 4
Cycles........... : 34
Samples per cycle : 100
Tests per Sample. : undefined
---------------------------------------------
Test : 'length64'
Mean : 26359µs ±128µs (*) 1128 samples
---------------------------------------------
Test : 'length32'
Mean : 27456µs ±109µs (*) 1144 samples
---------------------------------------------
Test : 'lengthNative'
Mean : 106µs ±2µs (*) 1128 samples
-All ----------------------------------------
Mean : 18.018ms Totals time : 61262.240ms 3400 samples
(*) Error rate approximation does not represent the variance.
*/
Andreas décrit un certain nombre de bonnes raisons pour lesquelles l'implémentation JavaScript a été initialement observée être 300 fois plus rapide . Cependant, il existe un certain nombre d'autres problèmes avec votre code.
Pour une réponse plus définitive, consultez le document conjoint de l'équipe WebAssembly, qui décrit un gain de performances d'exécution attendu d'environ 30%
Enfin, pour répondre à votre argument:
Quel est l'intérêt de WebAssembly s'il n'optimise pas
Je pense que vous avez des idées fausses sur ce que WebAssembly fera pour vous. Sur la base du document ci-dessus, les optimisations des performances d'exécution sont assez modestes. Cependant, il existe encore un certain nombre d'avantages en termes de performances:
Il existe également un certain nombre d'avantages non liés aux performances.
Pour une mesure des performances plus réaliste, consultez:
Les deux sont des bases de code de production pratiques.
Le moteur JS peut appliquer de nombreuses optimisations dynamiques à cet exemple:
Effectuez tous les calculs avec des nombres entiers et convertissez uniquement en double pour le dernier appel à Math.sqrt.
Inline l'appel à la fonction len
.
Hissez le calcul hors de la boucle, car il calcule toujours la même chose.
Reconnaissez que la boucle est laissée vide et éliminez-la entièrement.
Reconnaissez que le résultat n'est jamais renvoyé par la fonction de test et supprimez donc tout le corps de la fonction de test.
Tous sauf (4) s'appliquent même si vous ajoutez le résultat de chaque appel. Avec (5) le résultat final est une fonction vide de toute façon.
Avec Wasm, un moteur ne peut pas effectuer la plupart de ces étapes, car il ne peut pas s'aligner au-delà des frontières linguistiques (du moins aucun moteur ne le fait aujourd'hui, AFAICT). De plus, pour Wasm, on suppose que le compilateur producteur (hors ligne) a déjà effectué des optimisations pertinentes, donc un Wasm JIT a tendance à être moins agressif que celui pour JavaScript, où l'optimisation statique est impossible.