Un article récent sur l'importance d'utiliser des algorithmes linéaires m'a inspiré pour optimiser la fonction quadratique "chaude", comment je l'ai fait et à quels résultats cela a conduit - je vais vous le dire aujourd'hui. Préparez du Pu Er dans une tasse, asseyez-vous dans votre fauteuil :
▍ — JavaScript
… , , . , , .Au début de cette année
© « »
js(x)
,
ts(x)
et les
flow
fichiers, mais
markdown
,
yaml
,
ignore
,
json
et tous les autres qui implémentent le moteur d'interface.
Le processeur prend le
JavaScript
code des fichiers et recolle le tout, par exemple, dans le processeur Markdown .
Soit convertit
json
,
yaml
et
ignore
dans
JavaScript
pour trouver et corriger les erreurs de plugins .
L'interface du processeur ressemble à ceci :
process(rawSource, {fix}) -> [processedSource, processedPlaces]
- renvoie le code source traité ou les erreurs trouvées conformément au flagfix
;preProcess(processedSource) -> preProcessedList
- se retireJavaScript
deprocessedSource
la vérification et de correction;postProcess(processedSource, preProcessedList) -> processedSource
- colle les corrigésJavaScript
;
Le cœur de Putout se compose de 4 parties (moteurs) :
- L'analyseur traduit une chaîne en
AST
et enAST
arrière dans une chaîne ; - Le chargeur trouve et charge les plugins (y compris pour
Babel
) et les mods de codejscodeshift
- L'interprète reconnaît et traite toutes sortes de plugins (il y en a maintenant 4);
- Le processeur contrôle le fonctionnement de la trinité précédente ;
Le plan de travail peut ressembler à ceci:
▍Nous recherchons des fonctions "chaudes"
Le truc, c'est que nous sommes constamment en train de faire un voyage qui s'est terminé une seconde avant que nous ayons eu le temps de partir.Conformément à la loi de Pareto : 20% d'efforts correctement dirigés nous donneront 80% du résultat, donc la première chose qu'il est toujours logique de trouver les fonctions les plus « chaudes » et de travailler avec elles.
© Victor Pelevin "Flèche Jaune"
Tout d'abord, récupérez le
isolate
fichier à l'
node v14
aide de la clé --prof , exécutez-le à partir de la racine du référentiel :
node --prof packages/putout/bin/putout.mjs --fresh .
Ensuite, nous allons le traiter à l'aide de la clé
--prof-process
:
node --prof-process isolate-0x1049e5000-86428-v8.log > processed.txt
En parcourant les informations relatives à
engine-processor
in,
processed.txt
nous remarquons la ligne :
334 93.6% LazyCompile: *module.exports.runProcessors /Users/coderaiser/putout/packages/engine-processor/lib/processor.js:26:32
Oui, le moteur du processeur réalise 334 cycles d'horloge et 93,6% des appels lorsque la fonction parent est appelée.
Un peu sur Big O
Le passé est la locomotive qui entraîne l'avenir avec lui. Il arrive que ce soit le passé, en plus de celui de quelqu'un d'autre. Vous roulez dos en avant et vous ne voyez que ce qui a déjà disparu. Et pour descendre du train, il faut un ticket. Vous le tenez entre vos mains, mais à qui allez-vous le présenter ?Pour une fonction
© Victor Pelevin "Flèche Jaune"
fn
qui prend en entrée
data
et renvoie
result
:
const result = fn(data);
Big O fournit des informations sur le scénario le plus pessimiste.
Le temps d'exécution
fn
peut être obtenu à l'aide d'une fonction
complexity
, elle prend en entrée
operationsCount
:
const time = complexity(operationsCount);
Le temps est exprimé dans une lettre
n
, c'est plus pratique que d'utiliser des chiffres, car il y en a beaucoup, mais leur signification n'est pas particulièrement importante (on coupe le rasoir d'Occam ) : la technologie change, de nouveaux ordinateurs plus productifs remplacent les ordinateurs faibles . Ce qui compte vraiment, c'est la possibilité de comparer les algorithmes . Et ça existe ! Voici Big O , et voici ses exemples les plus simples :
- O(1) — , , . , , :
time = complexity(2 + n); O(1); const fn = (name) => { const str = `hello ${name}`; return str; }
- O(n) — , , . 10 , 100 :
time = complexity(n * 10); O(n); const fn = (files) => { const results = []; for (const file of files) { results.push(processFile(file)); } return results; }
- O(n^2) — , , . ,
n
.runners
5 ,run
10 , :
time = complexity(5 * 10); O(n * n); function process(runners) { const processed = []; for (const run of runners) { const files = run(); for (const file of files) { processed.push(processFile(file)); } return processed; }
Optimiser
- Souvenez-vous, lorsqu'une personne cesse d'entendre le bruit des roues et accepte d'aller plus loin, elle devient passager.Une version simplifiée du démarrage du Processor Engine contient une boucle dans une boucle, c'est-à-
- Personne ne nous demande si nous sommes d'accord ou non. Nous ne nous souvenons même pas comment nous sommes arrivés ici. Nous roulons juste et c'est tout. Rien ne subsiste.
- La chose la plus difficile dans la vie reste. Montez dans un train et ne soyez pas un passager.
© Victor Pelevin "Flèche Jaune"
dire qu'elle ressemble à ceci :
function process(runners) {
const processed = [];
for (const run of runners) {
const processed = run();
for (const file of files) {
processed.push(processFile(file));
}
return processed;
}
La version simplifiée et révisée contient deux boucles consécutives et
ressemble à ceci :
function process(runners) {
const processed = [];
for (const run of runners) {
files.push(...run());
}
for (const file of files) {
processed.push(processFile(file));
}
return processed;
}
Et idéalement , les fonctions avec des boucles sont extraites et appelées à partir de la fonction principale :
function process(runners) {
const files = getFiles(runners);
const linted = lintFiles(files);
return linted;
}
function getFiles(runners) {
const files = [];
for (const run of runners) {
files.push(...run());
}
return files;
}
function lintFiles(files) {
const linted = [];
for (const file of files) {
linted.push(processFile(file));
}
return linted;
}
Nous prenons des mesures
Tout ce monde est une flèche jaune qui vous touche, un train sur lequel vous vous dirigez vers le pont détruit.Pour prendre les mesures, j'ai utilisé
© Victor Pelevin "Flèche Jaune"
time
. Ce n'est pas la méthode la plus précise, car le temps d'exécution peut varier considérablement sur différents ordinateurs, cependant, au sein d'un même système, le temps + est le même et la différence entre la plupart des exécutions n'est pas très significative. Par exemple, sur un Mac, le temps d'exécution est deux fois moins que sur un Linux distant, conformément aux caractéristiques, votre temps peut différer.
Quand j'écris, j'exécute
putout
souvent des contrôles (déjà plus de) 1800 fichiers, et une minute et demie peut sembler peu, mais si vous le comparez au temps d'exécution de 3000 tests en 15 secondes, cela devient clair : il est de la place pour grandir! Ainsi, nous sélectionnerons une étiquette
v17.5.0
et lancerons une nouvelle peluche à l'aide de redrun:
git checkout v17.5.0 && time redrun fresh:lint
> putout . --fresh
real 1m32.712s
user 1m25.502s
sys 0m6.542s
git checkout master && time redrun fresh:lint
> putout . --fresh
real 1m20.898s
user 1m13.502s
sys 0m6.542s
Nous nous intéressons aux premières lignes : minute 33 secondes, et minute 20 secondes - c'est devenu plus rapide de 13 secondes, cela fait environ 12% à elles, nous ajoutons une mesure après optimisation à une variante idéale et nous obtenons tous 13%.
En répétant la recherche des fonctions "chaudes", on obtient les nombres suivants :
73 54.9% LazyCompile: *module.exports.runProcessors /Users/coderaiser/putout/packages/engine-processor/lib/processor.js:26:32
Le nombre de cycles travaillés est 4 fois moindre, et les pourcentages sont passés de 93% à 54%, ce qui n'est pas une mauvaise chose en soi. Je serais reconnaissant si quelqu'un dans les commentaires développe les informations sur les données que le profileur enregistre dans le fichier.
Des conclusions de grande portée
, , , , , , “Fleetwood Mac”. , , . , , .
© « »
Grâce à l'optimisation, le code est devenu non seulement plus rapide à traiter, mais aussi plus facile à comprendre, et donc plus maintenable et extensible. Par conséquent, je déclare hardiment : l'optimisation des points « chauds » est la racine de toutes les bénédictions !
MISE À JOUR Apparemment, j'ai fait une erreur dans le calcul de la complexité algorithmique et ce que j'appelle l'algorithme linéaire est O (n * m) , merci beaucoup @Yavanosta,moins, nvgordeev ! , , , Big O - . , V8.
▍
, , . , . : , , , . ? . , – ? , ».
© « »