성능 : 재귀 대 자바 스크립트의 반복


24

Javascript의 기능적 측면과 Scheme과 Javascript의 관계에 대한 최근 기사 (예 : http://dailyjs.com/2012/09/14/functional-programming/ )를 읽었습니다 (후자는 첫 번째 영향을 받았습니다. OO 기능은 프로토 타입 기반 언어 인 Self에서 상속되는 반면 기능적 언어입니다.

그러나 내 질문은 더 구체적입니다. Javascript의 재귀 성능 대 반복 성능에 대한 메트릭이 있는지 궁금합니다.

인터프리터 / 컴파일러가 재귀를 반복으로 변환하기 때문에 일부 언어에서는 (설계 반복이 더 나은 성능을 발휘하는) 차이가 최소화된다는 것을 알고 있습니다. 그러나 Javascript가 적어도 부분적으로는 기능적이므로 아마도 이것이 아닐 것입니다 언어.


3
나만의 테스트를하고 jsperf.com
TehShrike

현상금과 TCO를 언급 한 답변이 있습니다. ES6이 TCO를 지정하는 것으로 보이지만 지금까지 kangax.github.io/compat-table/es6 을 믿는다면 아무도 그것을 구현하지 않는 것 같습니다.
Matthias Kauer

답변:


28

JavaScript 는 꼬리 재귀 최적화를 수행하지 않으므로 재귀가 너무 심하면 호출 스택 오버플로가 발생할 수 있습니다. 반복에는 그러한 문제가 없습니다. 너무 많은 재귀가 발생한다고 생각하고 실제로 재귀가 필요한 경우 (예 : 플러드 채우기) 재귀를 자신의 스택으로 교체하십시오.

함수 호출 및 반환에는 상태 보존 및 복원이 필요하지만 반복은 단순히 함수의 다른 지점으로 이동하기 때문에 재귀 성능은 반복 성능보다 나쁠 수 있습니다.


그냥 궁금해 ... 나는 빈 배열이 만들어지고 재귀 함수 사이트가 배열의 위치에 할당 된 다음 배열에 저장된 값이 반환되는 코드를 보았습니다. "재귀를 자신의 스택으로 교체"한다는 의미입니까? 예 : var stack = []; var factorial = function(n) { if(n === 0) { return 1 } else { stack[n-1] = n * factorial(n - 1); return stack[n-1]; } }
mastazi

@mastazi : 아니오, 이것은 내부 호출 스택과 함께 쓸모없는 호출 스택을 만듭니다. 나는 Wikipedia대기열 기반 홍수 채우기 와 같은 것을 의미했습니다 .
Triang3l

언어는 TCO를 수행하지 않지만 구현은 가능하다는 점에 주목할 가치가 있습니다. 아마도 TCO 몇 구현에 게재 될 수 있습니다 사람들이 JS 수단을 최적화하는 방법
다니엘 Gratzer

1
@mastazi은 교체 else와 그 기능에 else if (stack[n-1]) { return stack[n-1]; } else그리고 당신은 메모이 제이션을 . 팩토리얼 코드를 작성한 사람은 불완전한 구현을했을 것입니다 (아마도 stack[n]어디서나 사용해야했습니다 stack[n-1]).
Izkata

@ Izkata에게 감사드립니다. 저는 종종 그런 종류의 최적화를 수행하지만 오늘까지는 그 이름을 알지 못했습니다. 나는 ;-) 대신 IT의 CS를 연구해야
mastazi

20

업데이트 : ES2015 이후 JavaScript에는 TCO 가 있으므로 아래 인수의 일부가 더 이상 유효하지 않습니다.


Javascript에는 꼬리 호출 최적화 기능이 없지만 재귀가 가장 좋은 방법입니다. 그리고 진지한 경우를 제외하고는 호출 스택 오버플로가 발생하지 않습니다.

성능은 염두에 두어야하지만 조기 최적화도 고려해야합니다. 재귀가 반복보다 더 우아하다고 생각되면 계속하십시오. 이것이 병목 현상이 아닌 것으로 판명되면 (아마도 아닐 수도 있음) 추악한 반복으로 대체 할 수 있습니다. 그러나 대부분의 병목 현상은 코드 자체가 아니라 DOM 조작 또는보다 일반적으로 I / O에 있습니다.

재귀는 항상 더 우아합니다 1 .

1 : 개인적인 의견.


3
나는 재귀가 더 우아하고 우아함이 가독성뿐만 아니라 유지 보수성이므로 중요하다는 데 동의합니다 (이것은 주관적이지만 내 의견으로는 재귀는 읽기 쉽고 유지 보수가 가능합니다). 그러나 때때로 성능이 중요합니다. 성능 측면에서도 재귀가 최선의 방법이라는 주장을지지 할 수 있습니까?
mastazi

3
내 대답에서 말한 것처럼 @mastazi는 재귀가 병목이 될지 의심합니다. 대부분의 경우 DOM 조작 또는보다 일반적으로 I / O입니다. 그리고 조기 최적화를 잊지 마세요 모든 악의 뿌리입니다)
플로리안 Margaine

DOM 조작이 병목 현상을 일으키는 경우 +1! 이에 대해 Yehuda Katz (Ember.js)와의 매우 흥미로운 인터뷰를 기억합니다.
mastazi

1
@mike "미숙아"정의는 " 적절한 시간 전에 성숙하거나 익히다 " 입니다. 재귀 적으로 무언가를 수행하면 스택 오버 플로우가 발생한다는 것을 알고 있다면 조기에 그렇지 않습니다. 그러나 실제 데이터가없는 변덕을 가정하면 조기입니다.
Zirak

2
Javascript를 사용하면 프로그램에서 사용할 수있는 스택 양이 없습니다. IE6에는 작은 스택이 있거나 FireFox에는 큰 스택이있을 수 있습니다. Scheme 스타일의 재귀 루프를 수행하지 않는 한 재귀 알고리즘의 깊이는 거의 없습니다. 비 루프 기반 재귀가 조기 최적화를 피하는 것처럼 보이지는 않습니다.
mike30

7

나는 자바 스크립트 에서도이 성능에 대해 매우 궁금했다. 그래서 몇 가지 실험을했다 (이전 버전의 노드에도 불구하고). 나는 반복 계산 대 반복적으로 팩토리얼 계산기를 작성하고 로컬로 몇 번 실행했습니다. 결과는 세금을 갖는 재귀 (예상)로 크게 왜곡 된 것처럼 보였다.

코드 : https://github.com/j03m/trickyQuestions/blob/master/factorial.js

Result:
j03m-MacBook-Air:trickyQuestions j03m$ node factorial.js 
Time:557
Time:126
j03m-MacBook-Air:trickyQuestions j03m$ node factorial.js 
Time:519
Time:120
j03m-MacBook-Air:trickyQuestions j03m$ node factorial.js 
Time:541
Time:123
j03m-MacBook-Air:trickyQuestions j03m$ node --version
v0.8.22

이것을 사용 해보고 "use strict";차이가 있는지 확인할 수 있습니다. ( jump표준 호출 순서 대신 s를 생성 합니다)
Burdock

1
최신 버전의 노드 (6.9.1)에서 나는 매우 비슷한 결과를 얻었습니다. 재귀에 약간의 세금이 있지만 1,000,000 루프의 경우 400ms의 차이는 루프 당 .0025ms입니다. 1,000,000 루프를 수행하는 경우 명심해야합니다.
Kelz

6

에 따라 영업의 요청에 나는 (희망, 자신의 바보를하지 않고 : P)의 칩 것이다

우리는 재귀가 더 우아한 코딩 방법이라는 데 모두 동의했다고 생각합니다. 잘 수행하면 유지 관리가 용이 ​​한 코드를 만들 수 있습니다. IMHO는 0.0001ms를 줄이는 것만 큼 중요합니다.

JS가 테일 콜 최적화를 수행하지 않는다는 주장에 관한 한 ECMA5의 엄격한 모드를 사용하면 TCO가 더 이상 사실이 아닙니다 . 그것은 내가 한참 전에 너무 행복하지 않은 것이었지만, 적어도 나는 arguments.callee엄격 모드에서 왜 오류가 발생 하는지 알고 있습니다. 위 링크가 버그 보고서에 연결되어 있음을 알고 있지만 버그는 WONTFIX로 설정되어 있습니다. 또한 표준 TCO는 ECMA6 (2013 년 12 월)입니다.

본능적으로 JS의 기능적 특성을 고수하면서 재귀는 99.99 %의 시간보다 더 효율적인 코딩 스타일이라고합니다. 그러나 Florian Margaine은 병목 현상이 다른 곳에서 발견 될 가능성이 있다고 지적합니다. DOM을 조작하는 경우 가능한 유지 관리 가능한 코드 작성에 초점을 맞추는 것이 가장 좋습니다. DOM API는 느리다.

어느 것이 더 빠른 옵션인지에 대한 명확한 대답을 제공하는 것은 불가능하다고 생각합니다. 최근에 많은 jspref가 Chrome의 V8 엔진이 일부 작업에서 엄청나게 빠르다 는 것을 보여주었습니다 .FF의 SpiderMonkey에서 4 배 느리게 실행되고 그 반대도 마찬가지입니다. 최신 JS 엔진에는 코드를 최적화하기 위해 모든 종류의 트릭이 있습니다. 나는 전문가는 아니지만 V8은 클로저 (및 재귀)에 대해 고도로 최적화 된 반면 MS의 JScript 엔진은 그렇지 않다고 생각합니다. DOM과 관련하여 SpiderMonkey는 종종 더 나은 성능을 발휘합니다 ...

요컨대, JS에서 항상 그렇듯이 어떤 기술이 더 성능이 좋을지는 예측 불가능에 가깝습니다.


3

엄격한 모드가 없으면 반복 성능이 보통 재귀보다 약간 빠릅니다 ( JIT가 더 많은 작업을 수행함 ). 테일 재귀 최적화는 본질적으로 전체 통화 시퀀스를 점프로 전환하기 때문에 눈에 띄는 차이를 제거합니다.

예 : Jsperf

재귀와 반복 중에서 선택할 때 코드 명확성과 단순성에 대해 훨씬 더 걱정할 것을 제안합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.