R에서 루프가 느린 이유는 무엇입니까?


87

루프가 느리고 R대신 벡터화 된 방식으로 작업을 수행해야 한다는 것을 알고 있습니다.

하지만 왜? 루프가 느리고 apply빠른 이유는 무엇 입니까? apply몇 가지 하위 기능을 호출합니다. 빠르지 않은 것 같습니다.

업데이트 : 죄송합니다. 질문이 잘못되었습니다. 벡터화와 apply. 내 질문은,

"벡터화가 더 빠른 이유는 무엇입니까?"


3
나는 R의 "적용이 for 루프보다 훨씬 빠르다"는 것이 약간의 신화 라는 인상을 받았습니다 . 송출 system.time답변에서 전쟁 ... 시작
joran

1
주제에 여기에 좋은 정보의 제비 : stackoverflow.com/questions/2275896/...
체이스

7
기록을 위해 : Apply는 벡터화가 아닙니다. Apply는 다른 (예 : 아니요) 부작용이있는 루프 구조입니다. @Chase 링크 토론을 참조하십시오.
요리스 MEYS

4
S ( S-Plus ?)의 루프 는 전통적으로 느 렸습니다. 이것은 R 의 경우가 아닙니다 . 따라서 귀하의 질문은 실제로 관련이 없습니다. 오늘 S-Plus 의 상황은 무엇인지 모르겠습니다 .
Gavin Simpson

4
이 질문이 왜 무겁게 투표되었는지는 분명하지 않습니다.이 질문은 다른 영역에서 R로 오는 사람들 사이에서 매우 일반적이며 FAQ에 추가되어야합니다.
patrickmdnet

답변:


69

R의 루프는 모든 해석 언어가 느린 것과 같은 이유로 느립니다. 모든 작업에는 많은 추가 수하물이 있습니다.

R_execClosurein을eval.c 보십시오 (이것은 사용자 정의 함수를 호출하기 위해 호출되는 함수입니다). 거의 100 줄에 달하며 실행을위한 환경 생성, 환경에 인수 할당 등 모든 종류의 작업을 수행합니다.

C에서 함수를 호출 할 때 얼마나 덜 발생하는지 생각해보십시오 (인수를 스택, 점프, 팝 인수로 푸시).

그래서 당신은 다음과 같은 타이밍을 얻습니다 (조란이 주석에서 지적했듯이 실제로 apply는 빠르지 않습니다 ; 내부 C 루프 mean 는 빠릅니다. apply단지 일반적인 오래된 R 코드입니다) :

A = matrix(as.numeric(1:100000))

루프 사용 : 0.342 초 :

system.time({
    Sum = 0
    for (i in seq_along(A)) {
        Sum = Sum + A[[i]]
    }
    Sum
})

합계 사용 : 측정 할 수 없을 정도로 작음 :

sum(A)

점근 적으로 루프가 다음과 같이 훌륭하기 때문에 약간 당황 스럽습니다. sum . 느려 야 할 실질적인 이유가 없습니다. 반복 할 때마다 더 많은 추가 작업을 수행하는 것입니다.

따라서 다음을 고려하십시오.

# 0.370 seconds
system.time({
    I = 0
    while (I < 100000) {
        10
        I = I + 1
    }
})

# 0.743 seconds -- double the time just adding parentheses
system.time({
    I = 0
    while (I < 100000) {
        ((((((((((10))))))))))
        I = I + 1
    }
})

(그 예는 Radford Neal에 의해 발견되었습니다 )

때문에 (R은 운영자이며, 실제로 당신이 그것을 사용할 때마다 조회의 이름을 필요로 :

> `(` = function(x) 2
> (3)
[1] 2

또는 일반적으로 해석 된 작업 (모든 언어)에는 더 많은 단계가 있습니다. 물론 이러한 단계는 이점도 제공합니다 . C 에서는 이러한 (트릭을 수행 할 수 없습니다 .


10
그래서 마지막 예의 요점은 무엇입니까? R에서 어리석은 일을하지 않고 빠르게 처리 할 것으로 기대하십니까?
Chase

6
@Chase 나는 그것이 그것을 말하는 한 가지 방법이라고 생각합니다. 예, C와 같은 언어는 중첩 된 괄호로 속도 차이가 없지만 R은 최적화하거나 컴파일하지 않습니다.
Owen

1
또한 () 또는 루프 본문의 {}-이 모든 것에는 이름 조회가 포함됩니다. 또는 일반적으로 R에서 더 많이 쓸 때 인터프리터는 더 많은 작업을 수행합니다.
Owen

1
당신이 for()루프 로 어떤 요점을 만들려고하는지 잘 모르겠어요 ? 그들은 전혀 같은 일을하지 않습니다. for()루프의 각 요소를 반복한다 A그들을 합산. apply()호출은 전체 벡터를 통과 A[,1](당신의 A벡터화 기능을 하나의 열이 있습니다) mean(). 나는 이것이 토론에 어떻게 도움이되는지 보지 못하고 상황을 혼란스럽게 만듭니다.
Gavin Simpson

3
@Owen 나는 귀하의 일반적인 요점에 동의하며 중요한 점입니다. 속도 기록을 깨기 때문에 R을 사용하지 않고 사용하기 쉽고 매우 강력하기 때문에 사용합니다. 그 힘에는 해석의 대가가 따릅니다. for()vs apply()예제 에서 보여 주려는 것이 무엇인지 명확하지 않았습니다 . 합계가 평균을 계산하는 데 큰 부분을 차지하는 동안 해당 예제를 제거해야한다고 생각합니다. 모든 예제가 실제로 보여주는 것은 mean()요소에 대한 C와 같은 반복을 통해 벡터화 된 함수의 속도입니다 .
Gavin Simpson

79

루프가 항상 느리고 apply빠르지는 않습니다. R News 2008 년 5 월호에 이에 대한 좋은 토론이 있습니다 .

Uwe Ligges와 John Fox. R 헬프 데스크 : 어떻게이 루프를 피하거나 더 빠르게 만들 수 있습니까? R News, 8 (1) : 46-50, 2008 년 5 월.

"루프!"섹션에서 (48 페이지부터 시작) 그들은 다음과 같이 말합니다.

R에 대한 많은 의견은 루프를 사용하는 것이 특히 나쁜 생각이라고 말합니다. 이것은 반드시 사실이 아닙니다. 경우에 따라 벡터화 된 코드를 작성하기 어렵거나 벡터화 된 코드가 엄청난 양의 메모리를 소비 할 수 있습니다.

그들은 또한 다음을 제안합니다.

  • 새 객체를 루프 내에서 크기를 늘리는 대신 루프 전에 전체 길이로 초기화합니다.
  • 루프 외부에서 수행 할 수있는 작업을 루프에서 수행하지 마십시오.
  • 단순히 루프를 피하기 위해 루프를 피하지 마십시오.

for루프에 1.3 초가 걸리지 만 apply메모리가 부족한 간단한 예가 있습니다.


35

제기 된 질문에 대한 유일한 답변은 다음과 같습니다. 루프는 아닙니다 느린 경우 당신이해야 할 것이 몇 가지 기능을 수행하는 데이터 세트와 그 기능에 대해 반복을하거나 작업이 벡터화되지 않습니다. for()루프로, 일반적으로 빠른 같이 될 것입니다 apply()가능성이 조금 더 소요보다하지만 lapply()전화. 마지막 포인트는 잘 예를 들면, SO에 덮여 대답 하고, 설정 및 동작에 대한 코드 적용된다 루프 의 전반적인 계산 부담의 상당 부분입니다 루프 .

많은 사람들이 for()루프가 느리다고 생각하는 이유 는 사용자가 잘못된 코드를 작성하기 때문입니다. 일반적으로 (몇 가지 예외가 있지만) 개체를 확장 / 확장해야하는 경우에도 복사가 포함되므로 개체 를 복사 하고 늘리는 오버 헤드가 모두 발생 합니다. 이것은 루프에만 국한된 것이 아니라 루프의 각 반복에서 복사 / 증가하는 경우 많은 복사 / 증가 작업이 발생하기 때문에 루프가 느려질 것입니다.

for()R에서 루프 를 사용하는 일반적인 관용구 는 루프가 시작되기 전에 필요한 스토리지를 할당 한 다음 할당 된 객체를 채우는 것입니다. 이 관용구를 따르면 루프가 느려지지 않습니다. 이것은 apply()당신을 위해 관리하는 것이지만보기에서 숨겨져 있습니다.

물론, for()루프 로 구현하는 연산에 대해 벡터화 된 함수가 존재한다면 그렇게하지 마십시오 . 마찬가지로 벡터화 된 함수가 있으면 etc를 사용 하지 마십시오apply() (예 : apply(foo, 2, mean)를 통해 더 잘 수행됨 colMeans(foo)).


9

비교처럼 (너무 많이 읽지 마세요!) : 저는 R에서 (매우) 간단한 for 루프를 실행했고 Chrome 및 IE 8에서는 JavaScript에서 실행했습니다. Chrome은 네이티브 코드로 컴파일하고 R은 컴파일러로 컴파일합니다. 패키지는 바이트 코드로 컴파일됩니다.

# In R 2.13.1, this took 500 ms
f <- function() { sum<-0.5; for(i in 1:1000000) sum<-sum+i; sum }
system.time( f() )

# And the compiled version took 130 ms
library(compiler)
g <- cmpfun(f)
system.time( g() )

@Gavin Simpson : Btw, S-Plus에서 1162ms가 걸렸습니다 ...

그리고 JavaScript와 "동일한"코드 :

// In IE8, this took 282 ms
// In Chrome 14.0, this took 4 ms
function f() {
    var sum = 0.5;
    for(i=1; i<=1000000; ++i) sum = sum + i;
    return sum;
}

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