výkon - Jaký je nejrychlejší způsob, jak projít pole v JavaScriptu?
Z knih jsem se naučil, že byste měli psát cyklus for takto:
for(var i=0, len=arr.length; i < len; i++){
// blah blah
}
takže arr.length nebude počítáno pokaždé.
Jiní říkají, že překladač provede nějakou optimalizaci, takže můžete jednoduše napsat:
for(var i=0; i < arr.length; i++){
// blah blah
}
Jen chci vědět, která je v praxi nejlepší cesta?
Odpověď č. 1
Po provedení tohoto testu s většinou moderních prohlížečů: https://jsben.ch/wY5fo
V současnosti nejrychlejší forma smyčky (a podle mého názoru také nejzřetelnější z hlediska syntaxe).
Standardní for-cyklus s ukládáním délky
var i = 0, len = myArray.length;
while (i < len) {
// your code
i++
}
Řekl bych, že to je rozhodně případ, kdy tleskám vývojářům JavaScriptového enginu. Runtime by měl být optimalizován pro jasnost, ne důvtip.
Odpověď č. 2
Absolutně nejrychlejší způsob, jak projít polem v JavaScriptu, je:
var len = arr.length;
while (len--) {
// blah blah
}
Podívejte se na tento příspěvek pro úplné srovnání
Odpověď č. 3
Ke červnu 2016, provádění některých testů v nejnovějším Chrome (71 % podíl na trhu prohlížečů v květnu 2016 a stále rostoucí):
- Nejrychlejší smyčka je smyčka for, a to jak s uložením délky do proměnné, tak bez něj, přičemž výkon je opravdu hodně podobný. (Smyčka for s uloženou délkou někdy poskytla lepší výsledky než ta bez ukládání, ale rozdíl je téměř zanedbatelný, což znamená, že engine může být už optimalizovaný tak, aby preferoval standardní a pravděpodobně nejjednodušší smyčku for bez ukládání).
- Smyčka while s dekrementy byla přibližně 1,5krát pomalejší než smyčka for.
- Smyčka používající zpětné volání (například standardní forEach) byla přibližně 10krát pomalejší než smyčka for.
Věřím, že toto vlákno je příliš staré a uvádí programátory v omyl, aby si mysleli, že potřebují ukládat délku do mezipaměti, nebo používat zpětné průchody pomocí while s dekrementy, aby dosáhli lepšího výkonu, čímž vzniká kód, který je méně srozumitelný a náchylnější k chybám než jednoduchá přímočará smyčka for. Proto doporučuji:
Pokud vaše aplikace prochází velké množství položek nebo je váš kód smyčky uvnitř funkce, která je často používána, jednoduchý for cyklus je řešení:
for (var i = 0; i < arr.length; i++) { // Do stuff with arr[i] or i }Pokud vaše aplikace opravdu neprochází mnoho položek, nebo pokud potřebujete provádět jen malé iterace sem a tam, použití standardního zpětného volání forEach nebo jakékoli podobné funkce z vámi zvolené JS knihovny může být snáze pochopitelné a méně náchylné k chybám, protože rozsah proměnné index je uzavřený a není potřeba používat závorky, přičemž můžete přímo přistupovat k hodnotě pole:
arr.forEach(function(value, index) { // Do stuff with value or index });Pokud opravdu potřebujete ušetřit pár milisekund při iteraci přes miliardy řádků a délka vašeho pole se během procesu nemění, můžete zvážit uložení délky do mezipaměti ve vašem cyklu for. I když si myslím, že to dnes opravdu není nutné:
for (var i = 0, len = arr.length; i < len; i++) { // Do stuff with arr[i] }
Odpověď č. 4
Je teprve rok 2018, takže by byla fajn aktualizace...
A opravdu musím nesouhlasit s přijatou odpovědí. Liší se to v různých prohlížečích. Některé provádějí forEach rychleji, jiné for-loop a některé while. Zde je benchmark všech metod https://jsben.ch/mW36e
arr.forEach( a => {
// ...
}
a protože můžete vidět hodně for-cyklusů jako for(a=0; ... ), stojí za zmínku, že bez 'var' budou proměnné definovány globálně a to může dramaticky ovlivnit rychlost, takže to zpomalí.
Duffovo zařízení běží rychleji na Operě, ale ne ve Firefoxu
var arr = arr = new Array(11111111).fill(255);
var benches =
[ [ "empty", () => {
for(var a = 0, l = arr.length; a < l; a++);
}]
, ["for-loop", () => {
for(var a = 0, l = arr.length; a < l; ++a)
var b = arr[a] + 1;
}]
, ["for-loop++", () => {
for(var a = 0, l = arr.length; a < l; a++)
var b = arr[a] + 1;
}]
, ["for-loop - arr.length", () => {
for(var a = 0; a < arr.length; ++a )
var b = arr[a] + 1;
}]
, ["reverse for-loop", () => {
for(var a = arr.length - 1; a >= 0; --a )
var b = arr[a] + 1;
}]
,["while-loop", () => {
var a = 0, l = arr.length;
while( a < l ) {
var b = arr[a] + 1;
++a;
}
}]
, ["reverse-do-while-loop", () => {
var a = arr.length - 1; // CAREFUL
do {
var b = arr[a] + 1;
} while(a--);
}]
, ["forEach", () => {
arr.forEach( a => {
var b = a + 1;
});
}]
, ["for const..in (only 3.3%)", () => {
var ar = arr.slice(0,arr.length/33);
for( const a in ar ) {
var b = a + 1;
}
}]
, ["for let..in (only 3.3%)", () => {
var ar = arr.slice(0,arr.length/33);
for( let a in ar ) {
var b = a + 1;
}
}]
, ["for var..in (only 3.3%)", () => {
var ar = arr.slice(0,arr.length/33);
for( var a in ar ) {
var b = a + 1;
}
}]
, ["Duff's device", () => {
var len = arr.length;
var i, n = len % 8 - 1;
if (n > 0) {
do {
var b = arr[len-n] + 1;
} while (--n); // n must be greater than 0 here
}
n = (len * 0.125) ^ 0;
if (n > 0) {
do {
i = --n <<3;
var b = arr[i] + 1;
var c = arr[i+1] + 1;
var d = arr[i+2] + 1;
var e = arr[i+3] + 1;
var f = arr[i+4] + 1;
var g = arr[i+5] + 1;
var h = arr[i+6] + 1;
var k = arr[i+7] + 1;
}
while (n); // n must be greater than 0 here also
}
}]];
function bench(title, f) {
var t0 = performance.now();
var res = f();
return performance.now() - t0; // console.log(`${title} took ${t1-t0} msec`);
}
var globalVarTime = bench( "for-loop without 'var'", () => {
// Here if you forget to put 'var' so variables'll be global
for(a = 0, l = arr.length; a < l; ++a)
var b = arr[a] + 1;
});
var times = benches.map( function(a) {
arr = new Array(11111111).fill(255);
return [a[0], bench(...a)]
}).sort( (a,b) => a[1]-b[1] );
var max = times[times.length-1][1];
times = times.map( a => {a[2] = (a[1]/max)*100; return a; } );
var template = (title, time, n) =>
`<div>` +
`<span>${title} </span>` +
`<span style="width:${3+n/2}%"> ${Number(time.toFixed(3))}msec</span>` +
`</div>`;
var strRes = times.map( t => template(...t) ).join("\n") +
`<br><br>for-loop without 'var' ${globalVarTime} msec.`;
var $container = document.getElementById("container");
$container.innerHTML = strRes;body { color:#fff; background:#333; font-family:helvetica; }
body > div > div { clear:both }
body > div > div > span {
float:left;
width:43%;
margin:3px 0;
text-align:right;
}
body > div > div > span:nth-child(2) {
text-align:left;
background:darkorange;
animation:showup .37s .111s;
-webkit-animation:showup .37s .111s;
}
@keyframes showup { from { width:0; } }
@-webkit-keyframes showup { from { width:0; } }<div id="container"> </div>Odpověď č. 5
Pokud na pořadí nezáleží, preferuji tento styl:
for(var i = array.length; i--; )
Ukládá délku do mezipaměti a je mnohem kratší k napsání. Ale bude procházet pole v opačném pořadí.
Odpověď č. 6
2014 While je zpět
Jen mysli logicky.
Podívej se na to
for( var index = 0 , length = array.length ; index < length ; index++ ) {
//do stuff
}
- Je potřeba vytvořit alespoň 2 proměnné (index, délka)
- Je potřeba zkontrolovat, zda je index menší než délka
- Potřebujeme zvýšit index
- smyčka
formá 3 parametry
Teď mi řekni, proč by to mělo být rychlejší než:
var length = array.length;
while( --length ) { //or length--
//do stuff
}
- Jedna proměnná
- Žádné kontroly
- index je snížen (stroje to preferují)
whilemá pouze jeden parametr
Byl jsem úplně zmatený, když Chrome 28 ukázal, že for smyčka je rychlejší než while. To muselo být nějakého druhu
„Uh, všichni používají cyklus for, pojďme se na to zaměřit při vývoji pro Chrome.“
Ale nyní, v roce 2014, se smyčka while vrací do Chrome. Je dvakrát rychlejší, na ostatních/starších prohlížečích byla vždy rychlejší.
Nedávno jsem provedl několik nových testů. Teď v reálném světě tyto krátké kódy nestojí za nic a jsperf vlastně nemůže správně vykonat while smyčku, protože potřebuje znovu vytvořit array.length, což také zabere čas.
na jsperf NEMŮŽETE zjistit skutečnou rychlost cyklu while.
musíte vytvořit vlastní vlastní funkci a zkontrolovat ji pomocí window.performance.now()
A jo... neexistuje žádný způsob, že by cyklus while byl jednoduše rychlejší.
Skutečný problém je vlastně manipulace s DOM/doba vykreslování/doba kreslení nebo jak tomu chcete říkat.
Například mám scénu na plátně, kde potřebuji vypočítat souřadnice a kolize... to se provádí mezi 10-200 mikrosekundami (ne milisekundami). Ve skutečnosti trvá několik milisekund, než se vše vykreslí. Stejně jako v DOM.
ALE
Existuje další super výkonný způsob, jak v některých případech použít cyklus for... například k kopírování/klonování pole
for(
var i = array.length ;
i > 0 ;
arrayCopy[ --i ] = array[ i ] // doing stuff
);
Všimněte si nastavení parametrů:
- Stejně jako ve smyčce while používám jen jednu proměnnou
- Je potřeba zkontrolovat, zda je index větší než 0;
- Jak vidíte, tento přístup je odlišný oproti běžnému for cyklu, který všichni používají, protože uvnitř třetího parametru dělám různé věci a také snižuju hodnoty přímo uvnitř pole.
Říkaje to, toto potvrzuje, že stroje jako --
psaní, že jsem přemýšlel, že to trochu zkrátím a odstraním nějaké zbytečné věci, a napsal jsem toto pomocí stejného stylu:
for(
var i = array.length ;
i-- ;
arrayCopy[ i ] = array[ i ] // doing stuff
);
I když je kratší, vypadá to, že použití i ještě jednou zpomaluje všechno. Je to o 1/5 pomalejší než předchozí smyčka for a smyčka while.
Poznámka: ; je velmi důležitá po cyklu for bez {}
I kdybych ti právě řekl, že jsperf není nejlepší způsob, jak testovat skripty.. přidal jsem sem tyto 2 smyčky
http://jsperf.com/caching-array-length/40
A zde je další odpověď ohledně výkonu v JavaScriptu
https://stackoverflow.com/a/21353032/2450730
Tato odpověď ukazuje efektivní způsoby psaní javascriptu. Takže pokud tomu nerozumíte, zeptejte se a dostanete odpověď, nebo si přečtěte knihu o javascriptu http://www.ecma-international.org/ecma-262/5.1/
Odpověď č. 7
http://jsperf.com/caching-array-length/60
Nejnovější revize testu, kterou jsem připravil (použitím starší verze), ukazuje jednu věc.
Délka cache není tak důležitá, ale neuškodí.
Každé první spuštění testu uvedeného výše (na čerstvě otevřeném panelu) dává nejlepší výsledky pro poslední 4 ukázky (3., 5., 7. a 10. v grafech) v Chrome, Opera a Firefoxu na mém Debian Squeeze 64-bit (můj desktop hardware). Následující spuštění dávají docela odlišný výsledek.
Z hlediska výkonnosti jsou závěry jednoduché:
- Použijte smyčku for (dopředu) a otestujte pomocí
!==místo<. - Pokud nemusíte pole později znovu použít, pak je while smyčka na dekrementované délce a destruktivní
shift()pole také efektivní.
shrnutí
V současnosti (2011.10) se zdá, že níže uvedený vzor je nejrychlejší.
for (var i = 0, len = arr.length; i !== len; i++) {
...
}
Mějte na paměti, že ukládání arr.length do mezipaměti zde není zásadní, takže můžete jen testovat i !== arr.length a výkon se nesníží, ale získáte kratší kód.
PS: Vím, že ve snippetu s shift() by bylo možné použít jeho výsledek místo přístupu k 0. prvku, ale nějak jsem to přehlédl po opětovném použití předchozí revize (která měla špatné while smyčky), a později jsem nechtěl ztratit již získané výsledky.
Odpověď č. 8
„Nejlepší“ ve smyslu čistého výkonu? Nebo výkon A čitelnost?
Čistý výkon „nejlepší“ je tento, který používá cache a prefixový operátor ++ (moje data: http://jsperf.com/caching-array-length/189)
for (var i = 0, len = myArray.length; i < len; ++i) {
// blah blah
}
Tvrdil bych, že for-cyklus bez cache je nejlepším kompromisem mezi dobou vykonávání a časem, který programátor potřebuje na čtení. Každý programátor, který začínal s C/C++/Java, neztratí ani milisekundu tím, že by musel projít tímto cyklem.
for(var i=0; i < arr.length; i++){
// blah blah
}
