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

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

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

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

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



답변

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 에서는 이러한 (트릭을 수행 할 수 없습니다 .


답변

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

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

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

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

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

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

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


답변

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

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

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

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


답변

비교처럼 (너무 많이 읽지 마세요!) : 저는 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;


답변