Tôi có thể học cách viết mã C để tăng tốc các hàm R chậm ở đâu? [đóng cửa]


115

Tài nguyên tốt nhất để học cách viết mã C để sử dụng với R là gì? Tôi biết về hệ thống và phần giao diện ngôn ngữ nước ngoài của phần mở rộng R, nhưng tôi thấy nó khá khó khăn. Tài nguyên tốt (cả trực tuyến và ngoại tuyến) để viết mã C để sử dụng với R là gì?

Để làm rõ, tôi không muốn học cách viết mã C, tôi muốn học cách tích hợp R và C. Ví dụ: làm cách nào để chuyển đổi từ vectơ số nguyên C sang vectơ số nguyên R (hoặc ngược lại) hoặc từ một vô hướng C sang một vectơ R?

Câu trả lời:


71

Cũng có cái cũ tốt Sử dụng nguồn, Luke! --- Bản thân R có rất nhiều mã C (rất hiệu quả) mà người ta có thể nghiên cứu và CRAN có hàng trăm gói, một số từ các tác giả mà bạn tin tưởng. Điều đó cung cấp các ví dụ thực tế, đã được thử nghiệm để nghiên cứu và thích ứng.

Nhưng như Josh nghi ngờ, tôi nghiêng nhiều hơn về C ++ và do đó Rcpp . Nó cũng có rất nhiều ví dụ.

Chỉnh sửa: Có hai cuốn sách tôi thấy hữu ích:

  • Cuốn đầu tiên là " Lập trình S " của Venables và Ripley mặc dù nó đang còn rất lâu (và đã có tin đồn về một ấn bản thứ hai trong nhiều năm). Lúc đó đơn giản là không có gì khác.
  • Phần thứ hai trong " Phần mềm phân tích dữ liệu " của Chambers, gần đây hơn nhiều và có cảm giác tập trung vào R đẹp hơn nhiều - và hai chương về mở rộng R. Cả C và C ++ đều được đề cập. Thêm vào đó, John cắt đứt tôi vì những gì tôi đã làm với tiêu hóa để chỉ riêng điều đó đã đáng giá khi nhập học.

Điều đó nói rằng, John đang ngày càng yêu thích Rcpp (và đóng góp) khi anh ấy thấy sự phù hợp giữa các đối tượng R và các đối tượng C ++ (thông qua Rcpp ) là rất tự nhiên - và ReferenceClasses giúp ích ở đó.

Chỉnh sửa 2: Với câu hỏi tái tập trung của Hadley, tôi rất mong bạn xem xét C ++. Có quá nhiều thứ vô nghĩa bạn phải làm với C --- rất tẻ nhạt và rất nên tránh . Hãy xem họa tiết giới thiệu Rcpp . Một ví dụ đơn giản khác là bài đăng trên blog này , nơi tôi chỉ ra rằng thay vì lo lắng về sự khác biệt 10% (trong một trong các ví dụ của Radford Neal), chúng ta có thể nhận được mức tăng gấp tám mươi lần với C ++ (tất nhiên là một ví dụ có sẵn).

Chỉnh sửa 3: Có một sự phức tạp ở chỗ bạn có thể gặp phải các lỗi C ++, nói một cách nhẹ nhàng là khó tìm hiểu. Nhưng để chỉ sử dụng Rcpp thay vì mở rộng nó, bạn hầu như không cần đến nó. Và trong khi chi phí này là không thể phủ nhận, nó bị lu mờ bởi lợi ích của mã đơn giản hơn, ít bảng soạn sẵn hơn, không có PROTECT / UNPROTECT, không có quản lý bộ nhớ, v.v. Doug Bates mới hôm qua đã tuyên bố rằng ông thấy C ++ và Rcpp giống như viết R hơn viết C ++. YMMV và tất cả những điều đó.


Tôi mong đợi tôi sẽ nhận được câu trả lời "sử dụng Rcpp";) Sẽ thực sự hữu ích nếu bạn có thể nêu ra những nhược điểm của việc sử dụng C ++ thay vì C. Một điểm chính có vẻ là C ++ phức tạp hơn nhiều so với C-does điều này làm cho nó khó sử dụng hơn? (Hoặc trong thực tế, bạn có thể viết mã C ++ tương tự như C không?) Tôi cũng đánh giá cao tài liệu tham khảo hơn hướng đến người dùng mới, những người chưa quen với C api hiện có.
hadley

2
Xem Chỉnh sửa 3có, bạn có thể . Meyers gọi C ++ là ngôn ngữ 'bốn mô hình' và bạn không cần phải sử dụng cả bốn. Sử dụng nó như 'chỉ là một C tốt hơn' và sử dụng Rcpp như keo dán cho R là hoàn toàn tốt. Không ai buộc một phong cách trên bạn - đây không phải là Java ;-)
Dirk Eddelbuettel

@Dirk: thx cho công phu. Nó đã đặt ra câu hỏi trong văn phòng của chúng tôi trước đây, vì C thường được sử dụng ở đây thay vì C ++. Khi nào thì việc sử dụng C thay vì C ++ sẽ có lợi, hay bạn chỉ đơn giản nói "không bao giờ là C, luôn luôn là C ++"?
Joris Meys

Hadley: Tuyệt. Chúng tôi sẽ rất quan tâm đến phản hồi của bạn. Vui lòng tham gia rcpp-devel và không giữ lại. Chúng tôi biết chúng tôi là tài liệu ngắn - nhưng một bộ mắt mới có thể giúp ích rất nhiều.
Dirk Eddelbuettel

6
@hadley điều đó có nghĩa là chúng ta có thể mong đợi một số cải tiến về tốc độ ggplot?
aL3xa

56

Hadley,

Bạn chắc chắn có thể viết mã C ++ tương tự như mã C.

Tôi hiểu những gì bạn nói về việc C ++ phức tạp hơn C. Đây là nếu bạn muốn thành thạo mọi thứ: đối tượng, mẫu, STL, lập trình meta mẫu, v.v. ... hầu hết mọi người không cần những thứ này và chỉ có thể dựa vào người khác với nó. Việc thực hiện Rcpp rất phức tạp, nhưng chỉ vì bạn không biết cách thức hoạt động của tủ lạnh, không có nghĩa là bạn không thể mở cửa và lấy sữa tươi ...

Từ nhiều đóng góp của bạn cho R, điều khiến tôi ngạc nhiên là bạn thấy R hơi tẻ nhạt (thao tác dữ liệu, đồ họa, thao tác chuỗi, v.v.). Hãy chuẩn bị tốt cho nhiều điều bất ngờ nữa với API C nội bộ của R. Điều này rất tẻ nhạt.

Thỉnh thoảng, tôi đọc hướng dẫn sử dụng R-exts hoặc R-ints. Điều này có ích. Nhưng hầu hết thời gian, khi tôi thực sự muốn tìm hiểu về điều gì đó, tôi sẽ vào nguồn R, và cả nguồn của các gói được viết bởi ví dụ Simon (thường có rất nhiều thứ để học ở đó).

Rcpp được thiết kế để làm biến mất những khía cạnh tẻ nhạt này của API.

Bạn có thể tự đánh giá những gì bạn thấy phức tạp hơn, khó hiểu, v.v. dựa trên một vài ví dụ. Hàm này tạo một vectơ ký tự bằng cách sử dụng API C:

SEXP foobar(){
  SEXP ab;
  PROTECT(ab = allocVector(STRSXP, 2));
  SET_STRING_ELT( ab, 0, mkChar("foo") );
  SET_STRING_ELT( ab, 1, mkChar("bar") );
  UNPROTECT(1);
}

Sử dụng Rcpp, bạn có thể viết hàm tương tự như:

SEXP foobar(){
   return Rcpp::CharacterVector::create( "foo", "bar" ) ;
}

hoặc là:

SEXP foobar(){
   Rcpp::CharacterVector res(2) ;
   res[0] = "foo" ;
   res[1] = "bar" ;
   return res ;
}

Như Dirk đã nói, có những ví dụ khác về một số họa tiết. Chúng tôi cũng thường hướng mọi người đến các bài kiểm tra đơn vị của chúng tôi vì mỗi người trong số họ kiểm tra một phần rất cụ thể của mã và có phần tự giải thích.

Rõ ràng tôi có thành kiến ​​ở đây, nhưng tôi khuyên bạn nên làm quen về Rcpp thay vì học C API của R, và sau đó đến danh sách gửi thư nếu có điều gì đó không rõ ràng hoặc có vẻ không khả thi với Rcpp.

Dù sao thì, cuối cùng của sân bán hàng.

Tôi đoán tất cả phụ thuộc vào loại mã cuối cùng bạn muốn viết.

Romain


2
"Rcpp được thiết kế để biến những khía cạnh tẻ nhạt này của API biến mất" = chính xác những gì tôi đang tìm kiếm. Cảm ơn! Điều thực sự hữu ích sẽ là một v. V. Ngắn gọn về C ++ dành cho những người đã quen thuộc với C và muốn sử dụng Rcpp.
hadley

thật tuyệt, ví dụ ngắn gọn về Rcpp đã giúp tôi bán được hàng. Tôi giả định phân bổXX và UNPROTECT (1) được xử lý giống như cách con trỏ thông minh quản lý tài nguyên. tức là RAII. Có bất kỳ hình phạt hiệu suất đáng chú ý nào bằng cách sử dụng Rcpp thay vì vani C api không?
jbremnant

Chúng tôi giải quyết vấn đề đó trong phần giới thiệu Rcpp với một ví dụ điểm chuẩn (cũng nằm trong gói nguồn / đã cài đặt). Tóm lại là không bị phạt gì cả.
Dirk Eddelbuettel

29

@hadley: rất tiếc, tôi không có tài nguyên cụ thể để giúp bạn bắt đầu với C ++. Tôi đã chọn nó từ sách của Scott Meyers (C ++ hiệu quả, C ++ hiệu quả hơn, v.v.) nhưng đây không thực sự là những gì người ta có thể gọi là giới thiệu.

Chúng tôi hầu như chỉ sử dụng giao diện .Call để gọi mã C ++. Quy tắc đủ dễ dàng:

  • Hàm C ++ phải trả về một đối tượng R. Tất cả các đối tượng R đều là SEXP.
  • Hàm C ++ lấy từ 0 đến 65 đối tượng R làm đầu vào (lại là SEXP)
  • nó phải (không thực sự, nhưng chúng ta có thể tiết kiệm này cho sau này) được khai báo với C liên kết, hoặc với extern "C" hoặc RcppExport bí danh mà định nghĩa Rcpp.

Vì vậy, một hàm .Call được khai báo như thế này trong một số tệp tiêu đề:

#include <Rcpp.h>

RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;

và được triển khai như thế này trong tệp .cpp:

SEXP foo( SEXP x1, SEXP x2 ){
   ...
}

Không còn nhiều điều cần biết về R API để sử dụng Rcpp.

Hầu hết mọi người chỉ muốn xử lý các vectơ số trong Rcpp. Bạn làm điều này với lớp NumericVector. Có một số cách để tạo một vectơ số:

Từ một đối tượng hiện có mà bạn chuyển xuống từ R:

 SEXP foo( SEXP x_) {
    Rcpp::NumericVector x( x_ ) ;
    ...
 }

Với các giá trị đã cho bằng cách sử dụng hàm :: create static:

 Rcpp::NumericVector x = Rcpp::NumericVector::create( 1.0, 2.0, 3.0 ) ;
 Rcpp::NumericVector x = Rcpp::NumericVector::create( 
    _["a"] = 1.0, 
    _["b"] = 2.0, 
    _["c"] = 3
 ) ;

Có kích thước nhất định:

 Rcpp::NumericVector x( 10 ) ;      // filled with 0.0
 Rcpp::NumericVector x( 10, 2.0 ) ; // filled with 2.0

Sau đó, khi bạn có một vector, điều hữu ích nhất là trích xuất một phần tử từ nó. Điều này được thực hiện với toán tử [], với lập chỉ mục dựa trên 0, vì vậy, ví dụ tính tổng các giá trị của một vectơ số sẽ giống như sau:

SEXP sum( SEXP x_ ){
   Rcpp::NumericVector x(x_) ;
   double res = 0.0 ;
   for( int i=0; i<x.size(), i++){
      res += x[i] ;
   }
   return Rcpp::wrap( res ) ;
}

Nhưng với đường Rcpp, giờ đây chúng ta có thể làm điều này độc đáo hơn nhiều:

using namespace Rcpp ;
SEXP sum( SEXP x_ ){
   NumericVector x(x_) ;
   double res = sum( x ) ;
   return wrap( res ) ;
}

Như tôi đã nói trước đây, tất cả phụ thuộc vào loại mã bạn muốn viết. Xem xét những gì mọi người làm trong các gói dựa trên Rcpp, kiểm tra các lần tải trang, kiểm tra đơn vị, quay lại với chúng tôi trong danh sách gửi thư. Chúng tôi luôn sẵn lòng giúp đỡ.


20

@jbremnant: Đúng vậy. Các lớp Rcpp triển khai một cái gì đó gần với mẫu RAII. Khi một đối tượng Rcpp được tạo, phương thức khởi tạo sẽ thực hiện các biện pháp thích hợp để đảm bảo đối tượng R bên dưới (SEXP) được bảo vệ khỏi bộ thu gom rác. Bộ hủy hủy bỏ bảo vệ. Điều này được giải thích trong họa tiết Rcpp-intrduction . Việc triển khai cơ bản dựa vào các hàm R API R_PreserveObjectR_ReleaseObject

Thực sự có hình phạt hiệu suất do đóng gói C ++. Chúng tôi cố gắng giữ điều này ở mức tối thiểu với nội tuyến, v.v. Hình phạt là nhỏ và khi bạn tính đến lợi ích về thời gian cần thiết để viết và duy trì mã, thì điều đó không liên quan.

Việc gọi các hàm R từ lớp Rcpp Hàm chậm hơn so với việc gọi trực tiếp eval bằng C api. Điều này là do chúng tôi thực hiện các biện pháp phòng ngừa và gói lời gọi hàm thành một khối tryCatch để chúng tôi nắm bắt các lỗi R và chuyển chúng thành các ngoại lệ C ++ để chúng có thể được xử lý bằng cách sử dụng try / catch tiêu chuẩn trong C ++.

Hầu hết mọi người muốn sử dụng vectơ (đặc biệt là NumericVector), và hình phạt là rất nhỏ với lớp này. Thư mục example / ConvolveBenchmarks chứa một số biến thể của hàm tích chập khét tiếng từ R-exts và họa tiết có kết quả điểm chuẩn. Nó chỉ ra rằng Rcpp làm cho nó nhanh hơn so với mã điểm chuẩn sử dụng R API.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.