c++语言get:_用C++给R语言加速:Rcpp简单用法
作者:黃天元,復旦大學博士在讀,熱愛數據科學與開源工具(R),致力于利用數據科學迅速積累行業經驗優勢和科學知識發現,涉獵內容包括但不限于信息計量、機器學習、數據可視化、應用統計建模、知識圖譜等,著有《R語言高效數據處理指南》(《R語言數據高效處理指南》(黃天元)【摘要 書評 試讀】- 京東圖書)。知乎專欄:R語言數據挖掘。郵箱:huang.tian-yuan@qq.com.歡迎合作交流。
最近要做一個小任務,它的描述非常簡單,就是識別向量的變化。比如一個整數序列是“4,4,4,5,5,6,6,5,5,5,4,4”,那么我們要根據數字的連續關系來分組,輸出應該是“1,1,1,2,2,3,3,4,4,4,5,5”。這個函數用R寫起來非常簡單,稍加思考草擬如下:
get_id = function(x){z = vector()y = NULLfor (i in seq_along(x)) {if(i == 1) y = 1else if(x[i] != x[i-1]) y = y + 1z = c(z,y)}z }不妨做個小測試:
get_id = function(x){z = vector()y = NULLfor (i in seq_along(x)) {if(i == 1) y = 1else if(x[i] != x[i-1]) y = y + 1z = c(z,y)}z } c(rep(33L,3),rep(44L,4),rep(33L,3)) #> [1] 33 33 33 44 44 44 44 33 33 33 get_id(c(rep(33L,3),rep(44L,4),rep(33L,3))) #> [1] 1 1 1 2 2 2 2 3 3 3得到的結果絕對是準確的,而且按照這些代碼,基本可以識別不同的數據類型,只要這些數據能夠用“==”來判斷是否相同(可能用setequal函數的健壯性更好)。
但是當數據量很大的時候,這樣寫是否足夠快,就很重要了。這意味著看你要等一小時、一天還是一個月。想起自己小時候還學過C++,就希望嘗試用Rcpp來加速,擬了代碼如下:
library(Rcpp)# 函數名稱為get_id_c cppFunction('IntegerVector get_id_c(IntegerVector x){int n = x.size();IntegerVector out(n);for (int i = 0; i < n; i++) {if(i == 1) out[i] = 1;else if(x[i] == x[i-1]) out[i] = out[i-1];else out[i] = out[i-1] + 1;}return out; }')需要聲明的是,C++需要定義數據類型,因為任務是正整數,所以函數就接受一個整數向量,輸出一個整數向量。多年不用C++,寫這么一段代碼居然調試過程就出了3次錯,慚愧。但是對性能的提升效果非常顯著,我們先做一些簡單嘗試。先嘗試1萬個整數:
library(pacman) p_load(tidyfst) sys_time_print({res1 = get_id(c(rep(33L,1e4),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e4),rep(44L,4),rep(33L,3))) })setequal(res1,res2)0.14s和0.00s的區別,可能體會不夠深。那么來10萬個整數試試:
sys_time_print({res1 = get_id(c(rep(33L,1e5),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e5),rep(44L,4),rep(33L,3))) })setequal(res1,res2)13s vs 0s,有點不能忍了。那么,我們來100萬個整數再來試試:
# 不要嘗試這個 sys_time_print({res1 = get_id(c(rep(33L,1e6),rep(44L,4),rep(33L,3))) })# 可以嘗試這個 sys_time_print({res2 = get_id_c(c(rep(33L,1e6),rep(44L,4),rep(33L,3))) })setequal(res1,res2)好的,關于這段代碼:
sys_time_print({res1 = get_id(c(rep(33L,1e6),rep(44L,4),rep(33L,3))) })可以不要嘗試了,因為直接卡死了。但是如果用Rcpp構造的函數,那么就放心試吧,我們還遠遠沒有探知其算力上限。可以觀察一下結果:
我們可以看到,1億個整數,也就是0.69秒;10億是7.15秒。雖然想嘗試百億,但是我的計算機內存已經不夠了。
總結一下,永遠不敢說R的速度不夠快,只是自己代碼寫得爛而已(盡管完成了實現,其實get_id這個函數優化的空間是很多的),方法總比問題多。不多說了,去溫習C++,學習Rcpp去了。后面如果有閑暇,來做一個Rcpp的學習系列。放一個核心資料鏈接:
Seamless R and C++ Integration?rcpp.org根據評論區提示,重新寫了R的代碼再來比較。代碼如下:
library(pacman) p_load(Rcpp,tidyfst)get_id = function(x){z = vector()for (i in seq_along(x)) {if(i == 1) z[i] = 1else if(x[i] != x[i-1]) z[i] = z[i-1] + 1else z[i] = z[i-1]}z }cppFunction('IntegerVector get_id_c(IntegerVector x){int n = x.size();IntegerVector out(n);for (int i = 0; i < n; i++) {if(i == 1) out[i] = 1;else if(x[i] == x[i-1]) out[i] = out[i-1];else out[i] = out[i-1] + 1;}return out; }')sys_time_print({res1 = get_id(c(rep(33L,1e4),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e4),rep(44L,4),rep(33L,3))) })setequal(res1,res2)sys_time_print({res1 = get_id(c(rep(33L,1e5),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e5),rep(44L,4),rep(33L,3))) })setequal(res1,res2)sys_time_print({res1 = get_id(c(rep(33L,1e6),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e6),rep(44L,4),rep(33L,3))) })setequal(res1,res2)sys_time_print({res1 = get_id(c(rep(33L,1e7),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e7),rep(44L,4),rep(33L,3))) })setequal(res1,res2)sys_time_print({res1 = get_id(c(rep(33L,1e8),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e8),rep(44L,4),rep(33L,3))) })setequal(res1,res2)sys_time_print({res1 = get_id(c(rep(33L,1e9),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e9),rep(44L,4),rep(33L,3))) })setequal(res1,res2)1萬:
10萬:
100萬:
1000萬:
1億:
結論:還是Rcpp香。
三更:R代碼提前設置向量長度,再比較。
library(pacman) p_load(Rcpp,tidyfst)get_id = function(x){z = integer(length(x))for (i in seq_along(x)) {if(i == 1) z[i] = 1else if(x[i] != x[i-1]) z[i] = z[i-1] + 1else z[i] = z[i-1]}z }cppFunction('IntegerVector get_id_c(IntegerVector x){int n = x.size();IntegerVector out(n);for (int i = 0; i < n; i++) {if(i == 1) out[i] = 1;else if(x[i] == x[i-1]) out[i] = out[i-1];else out[i] = out[i-1] + 1;}return out; }')sys_time_print({res1 = get_id(c(rep(33L,1e4),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e4),rep(44L,4),rep(33L,3))) })setequal(res1,res2)sys_time_print({res1 = get_id(c(rep(33L,1e5),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e5),rep(44L,4),rep(33L,3))) })setequal(res1,res2)sys_time_print({res1 = get_id(c(rep(33L,1e6),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e6),rep(44L,4),rep(33L,3))) })setequal(res1,res2)sys_time_print({res1 = get_id(c(rep(33L,1e7),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e7),rep(44L,4),rep(33L,3))) })setequal(res1,res2)sys_time_print({res1 = get_id(c(rep(33L,1e8),rep(44L,4),rep(33L,3))) })sys_time_print({res2 = get_id_c(c(rep(33L,1e8),rep(44L,4),rep(33L,3))) })setequal(res1,res2)四更:對于這個任務來講,data.table的rleid函數是最快的。R語言中的終極魔咒,能找到現成的千萬不要自己寫,總有巨佬在前頭。不過直到10億個整數才有難以忍受的差距。
1億10億總結
以上是生活随笔為你收集整理的c++语言get:_用C++给R语言加速:Rcpp简单用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 索尼更新 PS VR 2 FAQ 页面:
- 下一篇: java调用c的sdk_如何使用java