programing

R에서 루프 작동 속도 향상

elecom 2023. 6. 12. 21:05
반응형

R에서 루프 작동 속도 향상

저는 R에 큰 성능 문제가 있습니다.나는 반복되는 함수를 작성했습니다.data.frame물건. 열을추하됩니다면만기하 열을 .data.frame그리고 무언가를 축적합니다. (수동 조작)data.frame에는 약 85만 개의 행이 있습니다.내 PC는 아직 작동 중이고(현재 약 10시간) 런타임에 대해 전혀 알지 못합니다.

dayloop2 <- function(temp){
    for (i in 1:nrow(temp)){    
        temp[i,10] <- i
        if (i > 1) {             
            if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) { 
                temp[i,10] <- temp[i,9] + temp[i-1,10]                    
            } else {
                temp[i,10] <- temp[i,9]                                    
            }
        } else {
            temp[i,10] <- temp[i,9]
        }
    }
    names(temp)[names(temp) == "V10"] <- "Kumm."
    return(temp)
}

이 작업 속도를 높일 방법이 있습니까?

큰인 원인은frame을 인데, 이 에서 frame을 을 말합니다.temp[,].
가능한 한 이것을 피하도록 하세요.당신의 기능을 가져갔고, 색인을 변경하고 여기 버전_을 가져갔습니다.A

dayloop2_A <- function(temp){
    res <- numeric(nrow(temp))
    for (i in 1:nrow(temp)){    
        res[i] <- i
        if (i > 1) {             
            if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) { 
                res[i] <- temp[i,9] + res[i-1]                   
            } else {
                res[i] <- temp[i,9]                                    
            }
        } else {
            res[i] <- temp[i,9]
        }
    }
    temp$`Kumm.` <- res
    return(temp)
}

보시다시피 저는 벡터를 만듭니다.res결과를 얻는 것.에 마막에추다니합가에 합니다.data.frame이름을 함부로 대할 필요도 없어요그래서 얼마나 더 나은가요?

는 각 기능을 ▁each에 대해 실행합니다.data.frame와 함께nrow ~ x 1합니다.system.time

X <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
system.time(dayloop2(X))

결과는

성능

당신은 당신의 버전이 기하급수적으로 의존하는 것을 볼 수 있습니다.nrow(X)수정된 버전은 선형 관계를 가지며 단순합니다.lm모델은 850,000개의 행에 대해 계산하는 데 6분 10초가 걸릴 것으로 예측합니다.

벡터화의 힘

Shane과 Calimo가 답변에서 언급했듯이 벡터화는 더 나은 성능을 위한 핵심입니다.코드에서 루프 외부로 이동할 수 있습니다.

  • 컨디셔닝
  • 과의초기즉화결즉((화),temp[i,9])

이것은 이 코드로 이어집니다.

dayloop2_B <- function(temp){
    cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
    res <- temp[,9]
    for (i in 1:nrow(temp)) {
        if (cond[i]) res[i] <- temp[i,9] + res[i-1]
    }
    temp$`Kumm.` <- res
    return(temp)
}

에는 이를 비교해 보세요. 이번에는nrow10,000., 10,000x1,000.

성능

튜닝됨

다른 은 루프 입니다.temp[i,9]res[i](i번째 루프 반복에서 정확히 동일함). 것과 또 입니다.data.frame.
두 모든 을 알 수 .i하지만 조건에 맞는 사람들에게만 해당됩니다.
, 합니다.

dayloop2_D <- function(temp){
    cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
    res <- temp[,9]
    for (i in (1:nrow(temp))[cond]) {
        res[i] <- res[i] + res[i-1]
    }
    temp$`Kumm.` <- res
    return(temp)
}

얻을 수 있는 성능은 데이터 구조에 따라 크게 달라집니다.- 하게확로 - 율비정의 로 -TRUE값을 입력합니다.시뮬레이션 데이터의 경우 1초 미만의 85만 행에 대한 계산 시간이 소요됩니다.

성능

저는 당신이 더 나아갈 수 있기를 바랍니다. 저는 적어도 두 가지가 가능하다고 생각합니다.

  • C조건부 합계를 수행하기 위한 코드
  • 데이터에서 최대 시퀀스가 크지 않다는 것을 알면 루프를 벡터화된 상태로 변경할 수 있습니다.

    while (any(cond)) {
        indx <- c(FALSE, cond[-1] & !cond[-n])
        res[indx] <- res[indx] + res[which(indx)-1]
        cond[indx] <- FALSE
    }
    

시뮬레이션 및 수치에 사용되는 코드는 GitHub에서 사용할 수 있습니다.

R 코드 속도 향상을 위한 일반 전략

먼저, 느린 부분이 실제로 어디에 있는지 알아보세요.느리게 실행되지 않는 코드를 최적화할 필요가 없습니다.적은 양의 코드에 대해서는 단순히 생각해 보는 것만으로도 효과가 있습니다.이 작업이 실패할 경우 RProf 및 유사한 프로파일링 도구가 도움이 될 수 있습니다.

병목 현상을 파악한 후에는 원하는 작업을 수행하기 위한 보다 효율적인 알고리즘을 생각해 보십시오.계산은 가능한 경우 한 번만 실행해야 합니다. 따라서 다음을 수행합니다.

  • 반복적으로 재계산하지 않고 결과를 저장하고 액세스
  • 루프에 종속되지 않는 계산을 루프에서 제거
  • 불필요한 계산을 피합니다(예: 고정 검색과 함께 정규식을 사용하지 않는 것이 좋습니다).

보다 효율적인 기능을 사용하면 중간 또는 큰 속도 향상 효과를 얻을 수 있습니다.예를 들어.paste0약간의 효율성 이득을 생성하지만.colSums()그리고 그것의 친척들은 다소 더 뚜렷한 이득을 만듭니다. mean특히 느립니다.

그러면 특히 일반적인 문제를 피할 수 있습니다.

  • cbind아주 빠르게 속도를 늦출 겁니다
  • 매번 데이터 구조를 확장하는 대신 데이터 구조를 초기화한 다음 입력합니다.
  • 사전 할당을 사용하더라도 가치별 방식이 아닌 기준별 방식으로 전환할 수 있지만, 번거로울 필요는 없을 수 있습니다.
  • R Inferno에서 피할 수 있는 함정이 더 있는지 살펴보십시오.

종종 도움이 될 수는 있지만 항상 도움이 되는 것은 아닌 더 나은 벡터화를 시도합니다.이와 관련하여 본질적으로 벡터화된 명령은 다음과 같습니다.ifelse,diff그리고 비슷한 것들이 더 많은 개선을 제공할 것입니다.apply명령 제품군(잘 작성된 루프에서 속도 향상을 거의 또는 전혀 제공하지 않음).

R 함수에 더 많은 정보를 제공할 수도 있습니다.예를 들어, 텍스트 기반 데이터에서 읽을대신사용하고 지정합니다.속도 향상은 얼마나 많은 추측을 제거하느냐에 따라 달라질 수 있습니다.

다음으로 최적화된 패키지를 고려합니다.이 패키지는 데이터 조작 및 대량의 데이터 읽기에서 사용이 가능한 경우 엄청난 속도 향상을 가져올 수 있습니다.fread).

다음으로, R을 호출하는 보다 효율적인 방법을 통해 속도 향상을 시도합니다.

  • R 스크립트를 컴파일합니다.또는 를 사용합니다.Ra그리고.jit패키지가 적시 컴파일을 위해 함께 제공됩니다( 프레젠테이션에는 Dirk가 예를 들어 설명합니다).
  • 최적화된 BLAS를 사용하고 있는지 확인합니다.이는 전반적인 속도 향상을 제공합니다.솔직히, R이 설치 시 가장 효율적인 라이브러리를 자동으로 사용하지 않는 것은 유감입니다.R 혁명이 그들이 여기서 했던 일을 전체 커뮤니티에 다시 기여하기를 바랍니다.
  • Radford Neal은 여러 가지 최적화 작업을 수행했는데, 그 중 일부는 R Core에 채택되었고, 다른 많은 작업은 pqR로 분할되었습니다.

그리고 마지막으로, 위의 모든 것들이 여전히 여러분이 필요한 만큼 빠르게 여러분을 이끌어내지 못한다면, 여러분은 느린 코드 스니펫을 위해빠른 언어로 옮겨야 할 것입니다.의 조합Rcpp그리고.inline여기서 알고리즘의 가장 느린 부분만 C++ 코드로 쉽게 교체할 수 있습니다.예를 들어, 여기서 처음 시도하는 것은 매우 최적화된 R 솔루션조차도 날려버립니다.

이 모든 것이 끝난 후에도 여전히 문제가 남아 있다면, 더 많은 컴퓨팅 성능이 필요합니다.병렬화(http://cran.r-project.org/web/views/HighPerformanceComputing.html) 또는 GPU 기반 솔루션(gpu-tools).

기타 지침 링크

사중인경우를 .for루프, 당신은 마치 그것이 C나 Java 또는 다른 것인 것처럼 R을 코딩할 가능성이 높습니다.적절하게 벡터화된 R 코드는 매우 빠릅니다.

다음 두 개의 간단한 코드 비트를 예로 들어 10,000개의 정수 목록을 순차적으로 생성합니다.

첫 번째 코드 예제는 전통적인 코딩 패러다임을 사용하여 루프를 코딩하는 방법입니다.완료하는 데 28초가 걸립니다.

system.time({
    a <- NULL
    for(i in 1:1e5)a[i] <- i
})
   user  system elapsed 
  28.36    0.07   28.61 

메모리를 사전 할당하는 간단한 작업으로 거의 100배 향상할 수 있습니다.

system.time({
    a <- rep(1, 1e5)
    for(i in 1:1e5)a[i] <- i
})

   user  system elapsed 
   0.30    0.00    0.29 

연산자를 R은 R 벡터 연산자를 사용하는 것입니다.:순간적입니다.

system.time(a <- 1:1e5)

   user  system elapsed 
      0       0       0 

나 네스트를 사용하여 훨씬 더 빨리 만들 수 있습니다.ifelse()진술들.

idx <- 1:nrow(temp)
temp[,10] <- idx
idx1 <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
temp[idx1,10] <- temp[idx1,9] + temp[which(idx1)-1,10] 
temp[!idx1,10] <- temp[!idx1,9]    
temp[1,10] <- temp[1,9]
names(temp)[names(temp) == "V10"] <- "Kumm."

답변의 했듯이, 아가대마언급에했듯이지막답의리,이▁as▁the듯,Rcpp그리고.inline패키지는 물건을 빠르게 만드는 것을 엄청나게 쉽게 만듭니다.예를 들어, 이것을 시도해 보세요.inline코드(경고: 테스트되지 않음):

body <- 'Rcpp::NumericMatrix nm(temp);
         int nrtemp = Rccp::as<int>(nrt);
         for (int i = 0; i < nrtemp; ++i) {
             temp(i, 9) = i
             if (i > 1) {
                 if ((temp(i, 5) == temp(i - 1, 5) && temp(i, 2) == temp(i - 1, 2) {
                     temp(i, 9) = temp(i, 8) + temp(i - 1, 9)
                 } else {
                     temp(i, 9) = temp(i, 8)
                 }
             } else {
                 temp(i, 9) = temp(i, 8)
             }
         return Rcpp::wrap(nm);
        '

settings <- getPlugin("Rcpp")
# settings$env$PKG_CXXFLAGS <- paste("-I", getwd(), sep="") if you want to inc files in wd
dayloop <- cxxfunction(signature(nrt="numeric", temp="numeric"), body-body,
    plugin="Rcpp", settings=settings, cppargs="-I/usr/include")

dayloop2 <- function(temp) {
    # extract a numeric matrix from temp, put it in tmp
    nc <- ncol(temp)
    nm <- dayloop(nc, temp)
    names(temp)[names(temp) == "V10"] <- "Kumm."
    return(temp)
}

에도 비슷한 #include

inc <- '#include <header.h>

함수, cxx 수함, asinclude=inc이것이 정말 멋진 것은 모든 연결과 컴파일을 해준다는 것입니다. 프로토타이핑이 정말 빠르죠.

고지 사항:저는 tmp의 클래스가 숫자 행렬이나 다른 것이 아니라 숫자여야 한다는 것을 완전히 확신할 수 없습니다.하지만 저는 대부분 확신합니다.

편집: 이 후에도 속도가 더 필요하다면 OpenMP는 병렬화 기능입니다.C++사용해 본 적이 없습니다.inline하지만 효과가 있을 겁니다그 생각은, 만약의 경우에.n 반복 어, 반반복사를 k▁be▁carried행▁by.k % n적합한 소개는 Matloff의 R 프로그래밍 기술에서 찾을 수 있습니다. 여기 16장 C로의 의지에서 확인할 수 있습니다.

나는 코드를 다시 쓰는 것을 싫어합니다...물론 다른 것과 바르는 것이 더 나은 선택이지만 가끔은 그것을 맞추기가 어렵습니다.

.frames를 합니다. 예를 들어 "" "" "" "" data.frames""와 합니다.df$var[i]

다음은 구성된 예입니다.

nrow=function(x){ ##required as I use nrow at times.
  if(class(x)=='list') {
    length(x[[names(x)[1]]])
  }else{
    base::nrow(x)
  }
}

system.time({
  d=data.frame(seq=1:10000,r=rnorm(10000))
  d$foo=d$r
  d$seq=1:5
  mark=NA
  for(i in 1:nrow(d)){
    if(d$seq[i]==1) mark=d$r[i]
    d$foo[i]=mark
  }
})

system.time({
  d=data.frame(seq=1:10000,r=rnorm(10000))
  d$foo=d$r
  d$seq=1:5
  d=as.list(d) #become a list
  mark=NA
  for(i in 1:nrow(d)){
    if(d$seq[i]==1) mark=d$r[i]
    d$foo[i]=mark
  }
  d=as.data.frame(d) #revert back to data.frame
})

data.frame 버전:

   user  system elapsed 
   0.53    0.00    0.53

목록 버전:

   user  system elapsed 
   0.04    0.00    0.03 

벡터 목록을 사용하는 속도가 data.frame보다 17배 빠릅니다.

이와 관련하여 내부적으로 data.frames가 이렇게 느린 이유에 대한 의견이 있으십니까?그들이 리스트처럼 작동한다고 생각할 수도...

훨씬 더 빠른 코드를 위해 이 작업을 수행합니다.class(d)='list'd=as.list(d)그리고.class(d)='data.frame'

system.time({
  d=data.frame(seq=1:10000,r=rnorm(10000))
  d$foo=d$r
  d$seq=1:5
  class(d)='list'
  mark=NA
  for(i in 1:nrow(d)){
    if(d$seq[i]==1) mark=d$r[i]
    d$foo[i]=mark
  }
  class(d)='data.frame'
})
head(d)

여기에 대한 답은 훌륭합니다.다루지 않은 한 가지 사소한 측면은 "PC가 아직 작동 중이며(현재 10시간) 런타임에 대해 전혀 알지 못합니다."라는 질문입니다.저는 개발할 때 항상 다음 코드를 루프에 삽입하여 변경사항이 속도에 미치는 영향을 파악하고 완료하는 데 걸리는 시간을 모니터링합니다.

dayloop2 <- function(temp){
  for (i in 1:nrow(temp)){
    cat(round(i/nrow(temp)*100,2),"%    \r") # prints the percentage complete in realtime.
    # do stuff
  }
  return(blah)
}

래플리와 함께 작동합니다.

dayloop2 <- function(temp){
  temp <- lapply(1:nrow(temp), function(i) {
    cat(round(i/nrow(temp)*100,2),"%    \r")
    #do stuff
  })
  return(temp)
}

루프 내의 기능이 상당히 빠르지만 루프 수가 많은 경우 콘솔에 인쇄하는 것 자체에 오버헤드가 있으므로 자주 인쇄하는 것을 고려해 보십시오.

dayloop2 <- function(temp){
  for (i in 1:nrow(temp)){
    if(i %% 100 == 0) cat(round(i/nrow(temp)*100,2),"%    \r") # prints every 100 times through the loop
    # do stuff
  }
  return(temp)
}

R▁the▁▁using▁r▁speed▁in▁를 사용하여 루프 처리 속도를 높일 수 있습니다.apply 기능 의 경우,, 아도럴입다니마의신당▁probably▁(▁be▁family다것in▁functions▁would니입그▁case▁it(럴,기)일 것입니다.replicate를.plyr진행률 표시줄을 제공하는 패키지입니다.

또 다른 옵션은 루프를 완전히 피하고 벡터화된 산술로 대체하는 것입니다.정확히 무엇을 하고 있는지는 모르겠지만 모든 행에 기능을 한 번에 적용할 수 있습니다.

temp[1:nrow(temp), 10] <- temp[1:nrow(temp), 9] + temp[0:(nrow(temp)-1), 10]

이 작업이 훨씬 빨라지고 조건에 따라 행을 필터링할 수 있습니다.

cond.i <- (temp[i, 6] == temp[i-1, 6]) & (temp[i, 3] == temp[i-1, 3])
temp[cond.i, 10] <- temp[cond.i, 9]

벡터화된 산술은 문제에 대해 더 많은 시간과 생각을 필요로 하지만 실행 시간을 몇 배 줄일 수 있습니다.

▁a▁at▁look시를 보세요.accumulate()에서 합니다.{purrr}:

dayloop_accumulate <- function(temp) {
  temp %>%
    as_tibble() %>%
     mutate(cond = c(FALSE, (V6 == lag(V6) & V3 == lag(V3))[-1])) %>%
    mutate(V10 = V9 %>% 
             purrr::accumulate2(.y = cond[-1], .f = function(.i_1, .i, .y) {
               if(.y) {
                 .i_1 + .i
               } else {
                 .i
               }
             }) %>% unlist()) %>%
    select(-cond)
}

으로 처리합니다.data.table사용 가능한 옵션:

n <- 1000000
df <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
colnames(df) <- paste("col", 1:9, sep = "")

library(data.table)

dayloop2.dt <- function(df) {
  dt <- data.table(df)
  dt[, Kumm. := {
    res <- .I;
    ifelse (res > 1,             
      ifelse ((col6 == shift(col6, fill = 0)) & (col3 == shift(col3, fill = 0)) , 
        res <- col9 + shift(res)                   
      , # else
        res <- col9                                 
      )
     , # else
      res <- col9
    )
  }
  ,]
  res <- data.frame(dt)
  return (res)
}

res <- dayloop2.dt(df)

m <- microbenchmark(dayloop2.dt(df), times = 100)
#Unit: milliseconds
#       expr      min        lq     mean   median       uq      max neval
#dayloop2.dt(df) 436.4467 441.02076 578.7126 503.9874 575.9534 966.1042    10

조건 필터링을 통해 얻을 수 있는 이득을 무시하면 매우 빠릅니다.물론 데이터의 부분 집합에 대해 계산을 수행할 수 있다면 도움이 됩니다.

언급URL : https://stackoverflow.com/questions/2908822/speed-up-the-loop-operation-in-r

반응형