본문 바로가기

정리/데이터 분석

[R] R과 C연동 (Rcpp 패키지 사용. R 속도 개선.)

728x90
R과 C연동 (Rcpp 패키지 사용. R 속도 개선.)

1 시작

이번에는 R과 C의 연동을 보여드리려 합니다.

이 과정이 관심이 생겼던 이유는 R의 연산 속도 문제 때문이었고요.

해당 방법으로 얼마나 속도를 개선할 수 있을 지 모르겠지만 우선 소개를 해드립니다.

2 C연동 방법 3 가지. (rcpp 사용 이유)

크게 세가지 방법이 있다고 합니다. (1) .C 함수 [특징] 1. 간단하지만 제한적으로 사용 가능. 2. R 메모리 상에서 연산을 하기 때문에 object 는 포인터 연산자로 다뤄줘야 한다고 합니다.

  1. .Call 함수 [특징]
    1. 헤더파일이 필요합니다.
    2. 모든 객체를 SEXP라는 데이타 타입으로 다룸.
  1. Rcpp package 활용 [특징]
    1. pointer 나 SEXP를 사용하지 않고 데이터 연산 표현 가능. –> 사용만 안해보이는 거지 실제로는 SEXP를 사용합니다.
    2. 64bit 정수 타입에 캐스팅 이슈가 있어 대형 프로젝트에서는 사용하지 않는게 좋을 거 같다고 합니다. (2014년) –> 지금은 해결이 되었을 법도 하고요. http://adv-r.had.co.nz/C-interface.html 에서 보시면 “I do not recommend using C for writing new high-performance code. Instead write C++ with Rcpp. The Rcpp API protects you from many of the historical idiosyncracies of the R API, takes care of memory management for you, and provides many useful helper methods.”

새로운 고성능 코드는 C를 사용하지 말라고 하고요. Rcpp를 가장 추천한다고 하네요.

그래서 저는 3번 Rcpp package를 사용해보려 합니다.

3 Rcpp 사용법

먼저 cpp 파일을 생성해 내가 원하는 cpp코드를 작성합니다. 이때 아래의 내용을 넣어줘야합니다. #include <Rcpp.h> using namespace Rcpp;

// [[Rcpp::export]] 1 위를 적고 1 위치에 내가 원하는 함수 정의하면 됩니다. 예를 들면 #include <Rcpp.h> using namespace Rcpp;

// [[Rcpp::export]] float myFloor(float x){}

이렇게요. 그리고 다음을 수행하면 됩니다. 저는 인터넷에서 floor함수를 cpp로 구현한 내용을 찾아서 컴파일 해봤고요. 생각보다 긴 코드였습니다. 코드는 여기서 받으세요. https://stackoverflow.com/questions/5122993/floor-int-function-implementaton

library(Rcpp)

sourceCpp(paste0(getwd(),"/r_c_interface.cpp"))

sapply(rnorm(n=100,m=10, sd=1),function(x){
 myFloor(x)  
})
##   [1]  9 11  9  9 11 10 11  9  8 12 11 10  9 11 10 11 11  8 11 10  9 10  9
##  [24]  9 11 10  9 12 11  9  8  9 11  9  9 11  9 11  9  8  8 11 10  9 11  9
##  [47]  9 10 10 10  9 10 11 10 10 11 10  8 10  8 12  9 11 10  9  9 10  9  9
##  [70]  8 10  9 10 12 11 11  9 10  9 12  9 10 10  7 10  9 10 10  9  8  9  7
##  [93]  8 10 10 10  9  9 10 11

참 쉽죠?

4 Rcpp활용한 loop 속도 비교

그렇다면 Rcpp를 쓰면 속도가 개선이 될까요?

단순 연산은 크게 차이가 없거나 오히려 R이 빠를 수 있습니다.

왜냐하면 R의 core는 C나 fortran으로 되어있는 것으로 알고 있습니다.

대신 loop문을 확인해보겠습니다.

정수 합계 함수를 Rcpp와 R에서 각각 구현하여 실행 시간을 체크해봤습니다.

sourceCpp(paste0(getwd(),"/r_c_interface.cpp"))

#구현한 합계 함수
mySumInteger<-function(st, ed, df){
  sum<-0
  while(st<ed){
    sum=sum+st
    st<-st+df
  }
  return(sum)
}

system.time(sumInt(1, 100000000, 1));system.time(mySumInteger(1,100000000,1))
##    user  system elapsed 
##   0.034   0.000   0.033
##    user  system elapsed 
##   5.814   0.000   5.813

int sumInt(int start, int end, int diff){ int total=0; for(int i=start;i<end;i=i+diff){ total+=i; } return total;
}

rcpp에서는 위와 같이 함수를 정의했습니다.

rcpp 의 sumInt()는 0.032 초 R에서 구현한 mySumInteger()은 5.463초

약 170배 정도 차이가 납니다. 너무 과하네요.

5 세줄 요약

  1. R의 C Interface 방법에는 세 가지 방법이 있고 Rcpp 가 그나마 안정적인것으로 보인다. (과거엔 아니었지만)
  2. Rcpp 쓰면 루프문 속도 문제가 매우 많이 개선 된다. 쓰기도 편하다.
  3. 모든 언어에서 그렇듯이 무엇이든 시스템에 적용하려면 안정성이 보장되어야 하는데 테스트를 해볼 필요는 있다.

이상으로 이번 포스팅을 마치겠습니다.