JavaScript 루프 성능-증가하는 것보다 빠르게 반복자를 0으로 감소시키는 이유
그의 저서 Even Faster Web Sites에서 Steve Sounders는 루프의 성능을 향상시키는 간단한 방법은 전체 길이로 증가하는 대신 반복자를 0으로 줄이는 것입니다 ( 실제로이 장은 Nicholas C. Zakas가 썼습니다 ). 이 변경으로 각 반복의 복잡성에 따라 원래 실행 시간을 최대 50 %까지 절약 할 수 있습니다. 예를 들면 :
var values = [1,2,3,4,5];
var length = values.length;
for (var i=length; i--;) {
process(values[i]);
}
이것은 for
루프, do-while
루프 및 루프에 대해 거의 동일 while
합니다.
그 이유는 무엇입니까? 반복자를 훨씬 빠르게 줄이는 이유는 무엇입니까? (저는이 주장을 입증하는 벤치 마크가 아니라 이것의 기술적 배경에 관심이 있습니다.)
편집 : 첫눈에 여기에 사용 된 루프 구문이 잘못 보입니다. 아무 없다 length-1
거나 i>=0
그래서 (내가 너무 혼란스러워했다)의 명확히하자.
다음은 일반적인 for 루프 구문입니다.
for ([initial-expression]; [condition]; [final-expression])
statement
초기 표현 -
var i=length
이 변수 선언이 먼저 평가됩니다.
조건 -
i--
이 표현식은 각 루프 반복 전에 평가됩니다. 루프를 처음 통과하기 전에 변수를 감소시킵니다. 이 표현식이
false
루프로 평가되면 종료됩니다. 자바 스크립트에있는0 == false
경우, 그래서i
결국 동일0
이로 해석됩니다false
루프 종료됩니다.최종 표현
이 표현식은 각 루프 반복이 끝날 때 ( condition 의 다음 평가 전에 ) 평가 됩니다. 여기에 필요하지 않으며 비어 있습니다. 세 가지 식은 모두 for 루프에서 선택 사항입니다.
for 루프 구문은 질문의 일부가 아니지만 약간 드물기 때문에 명확하게하는 것이 흥미 롭다고 생각합니다. 그리고 더 빠른 이유 중 하나는 더 적은 표현을 사용하기 때문입니다 ( 0 == false
"트릭").
Javascript에 대해 잘 모르겠습니다. 최신 컴파일러에서는 문제가되지 않지만 "오래된 날"에는이 코드가 있습니다.
for (i = 0; i < n; i++){
.. body..
}
생성 할 것이다
move register, 0
L1:
compare register, n
jump-if-greater-or-equal L2
-- body ..
increment register
jump L1
L2:
역방향 계산 코드는
for (i = n; --i>=0;){
.. body ..
}
생성 할 것이다
move register, n
L1:
decrement-and-jump-if-negative register, L2
.. body ..
jump L1
L2:
따라서 루프 내부에서는 4 개가 아닌 2 개의 추가 명령 만 수행합니다.
그 이유는 루프 끝점을 0과 비교하고 있기 때문에 다시 비교하는 것보다 빠릅니다 < length
(또는 다른 JS 변수).
서수 연산자 <, <=, >, >=
는 다형성이기 때문에 이러한 연산자는 어떤 비교 동작을 사용해야하는지 결정하기 위해 연산자의 왼쪽과 오른쪽 모두에 대한 유형 검사가 필요하기 때문입니다.
여기에 매우 좋은 벤치 마크가 있습니다.
JavaScript에서 루프를 코딩하는 가장 빠른 방법은 무엇입니까?
반복이 더 적은 명령을 가질 수 있다고 쉽게 말할 수 있습니다. 이 두 가지를 비교해 보겠습니다.
for (var i=0; i<length; i++) {
}
for (var i=length; i--;) {
}
각 변수 액세스와 각 연산자를 하나의 명령어로 계산할 때 전자 for
루프는 5 개의 명령어 (read i
, read length
, evaluation i<length
, test (i<length) == true
, increment i
)를 사용하고 후자는 3 개의 명령어 (read i
, test i == true
, decrement i
) 만 사용합니다. 그것은 5 : 3의 비율입니다.
리버스 while 루프를 사용하는 것은 어떻습니까?
var values = [1,2,3,4,5];
var i = values.length;
/* i is 1st evaluated and then decremented, when i is 1 the code inside the loop
is then processed for the last time with i = 0. */
while(i--)
{
//1st time in here i is (length - 1) so it's ok!
process(values[i]);
}
IMO 이것은 적어도 더 읽기 쉬운 코드입니다 for(i=length; i--;)
나는 루프 속도도 탐구 해 왔으며 감소하는 것이 증가하는 것보다 빠르다는 것에 대한이 정보를 찾는 데 관심이있었습니다. 그러나 나는 이것을 증명하는 테스트를 아직 찾지 못했습니다. jsperf에는 많은 루프 벤치 마크가 있습니다. 다음은 감소를 테스트하는 것입니다.
http://jsperf.com/array-length-vs-cached/6
Caching your array length, however (also recommended Steve Souders' book) does seem to be a winning optimization.
for
increment vs. decrement in 2017
In modern JS engines incrementing in for
loops is generally faster than decrementing (based on personal Benchmark.js tests), also more conventional:
for (let i = 0; i < array.length; i++) { ... }
It depends on the platform and array length if length = array.length
has any considerable positive effect, but usually it doesn't:
for (let i = 0, length = array.length; i < length; i++) { ... }
Recent V8 versions (Chrome, Node) have optimizations for array.length
, so length = array.length
can be efficiently omitted there in any case.
There is an even more "performant" version of this. Since each argument is optional in for loops you can skip even the first one.
var array = [...];
var i = array.length;
for(;i--;) {
do_teh_magic();
}
With this you skip even the check on the [initial-expression]
. So you end up with just one operation left.
in modern JS engines, the difference between forward and reverse loops is almost non-existent anymore. But the performance difference comes down to 2 things:
a) extra lookup every of length property every cycle
//example:
for(var i = 0; src.length > i; i++)
//vs
for(var i = 0, len = src.length; len > i; i++)
this is the biggest performance gain of a reverse loop, and can obviously be applied to forward loops.
b) extra variable assignment.
the smaller gain of a reverse loop is that it only requires one variable assignment instead of 2
//example:
var i = src.length; while(i--)
I've conducted a benchmark on C# and C++ (similar syntax). There, actually, the performance differs essentially in for
loops, as compared to do while
or while
. In C++, performance is greater when incrementing. It may also depend on the compiler.
In Javascript, I reckon, it all depends on the browser (Javascript engine), but this behavior is to be expected. Javascript is optimized for working with DOM. So imagine you loop through a collection of DOM elements you get at each iteration, and you increment a counter, when you have to remove them. You remove the 0
element, then 1
element, but then you skip the one that takes 0
's place. When looping backwards that problem disappears. I know that the example given isn't just the right one, but I did encounter situations where I had to delete items from an ever-changing object collection.
Because backward looping is more often inevitable than forward looping, I am guessing that the JS engine is optimized just for that.
Have you timed it yourself? Mr. Sounders might be wrong with regards to modern interpreters. This is precisely the sort of optimization in which a good compiler writer can make a big difference.
I am not sure if it's faster but one reason i see is that when you iterate over an array of large elements using increment you tend to write:
for(var i = 0; i < array.length; i++) {
...
}
You are essentially accessing the length property of the array N (number of elements) times. Whereas when you decrement, you access it only once. That could be a reason.
But you can also write incrementing loop as follows:
for(var i = 0, len = array.length; i < len; i++) {
...
}
'Nice programing' 카테고리의 다른 글
tic-tac-toe 게임에 어떤 알고리즘을 사용하여 AI에 대한 "최고의 움직임"을 결정할 수 있습니까? (0) | 2020.11.30 |
---|---|
GDB에서 이전 줄로 이동하는 방법은 무엇입니까? (0) | 2020.11.30 |
Rails 3에서 Rails 3.1로 업그레이드 (0) | 2020.11.30 |
선택기 [class ^ =“span”]은 무엇을합니까? (0) | 2020.11.30 |
Python 구성 파일 : 파일 형식 권장 사항이 있습니까? (0) | 2020.11.30 |