使用Rcpp提高性能之入门篇

2024-06-23 20:18

本文主要是介绍使用Rcpp提高性能之入门篇,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

C++能解决的瓶颈问题有:

  • 由于迭代依赖于之前结果,循环难以简便的向量化运算
  • 递归函数,或者是需要对同一个函数运算成千上万次
  • R语言缺少一些高级数据结构和算法

我们只需要在代码中写一部分C++代码来就可以处理上面这些问题。后续操作在Windows下进行,你需要安装Rtools,用install.packages("Rcpp")安装新版的Rcpp,最重要一点,你需要保证你R语言时不能是C:/Program Files/R/R-3.5.1/这种形式,否则会报错。

后续操作会用到microbenchmark包来评估R代码和RCPP的效率差异,用install.packages('microbenchmark)安装

RCPP入门

先从一个简单的add函数开始,学习如何用cppFunction在R里面写C++代码

library(Rcpp)cppFunction('int add(int x, int y, int z) {int sum = x + y + z;return sum;
}')
add
# function (x, y) 
# .Call(<pointer: 0x0000000063c015a0>, x, y)

Rcpp将会编译C++代码, 然后构建能够连接到C++函数的R函数。后续将会介绍如何将一些R代码改写成C++代码。

  • 标量输入,标量输出
  • 向量输入,标量输出
  • 向量输入,向量输出
  • 矩阵输入,向量输出

没有输入,标量输出

最简单的函数就是不提供任何输出,返回一个输出,比如说

one <- function() 1L

等价的C++代码是

int one(){return 1;
}

那么将这段C++代码在R用cppFunction中改写就是如下

cppFunction('int one(){return 1;
}')

上面这段函数就展示了R和C++之间一些重要区别:

  • C++写代码不是函数名 <- function(参数){} 而是 函数名(函数参数){}
  • C++中必须声明返回类型,ini就是标量整数。C++对应R语言常用向量的类是: NumericVector,IntegerVector, CharacterVectorLogicalVector.
  • R语言没有标量,全是向量。而C++有向量和标量之分,标量的数据类型是double, int, Stringbool
  • C++你必须要用到return声明要返回的数据
  • 每段代码后要跟着;

标量输入,标量输出

我们可以写一个函数,sign,他的功能就是把一个负数转成正数,正数不变

signR <- function(x){if (x > 0){x} else if (x == 0 ){0} else{-x}
}cppFunction('int signC(int x){if( x >0 ){return x;} else if (x == 0){return 0;} else {return -x;}
}')

这个例子中要注意两件事情

  • C++中,你需要声明输入的数据类型
  • C++和R的条件语句长得一样。

向量输入,标量输出

R和C++一大区别就是R的循环效率很低。因此在R语言要尽量避免使用显示的循环语句,尽量向量化运算函数。而C++的循环花销特别小,所以可以放心大胆的用。

让我们用R代码写一个求和函数sum 以及 C++的求和函数,然后比较下效率

sumR <- function(x){total <- 0for (i in seq_along(x)){total <- total + x[i]}total
}cppFunction('int sumC(NumericVector x ){ int n = x.size();double total = 0;for(int i = 0; i < n; ++i){total += x[i];  }return total;}')

C++版本和R版本的逻辑相同,但是有如下不同

  • .size()确认向量的长度
  • for的写法为for(初始值; 判断语句; 递增)
  • 记住: C++的向量索引从0开始,R是从1开始
  • 向量赋值是=而不是<-
  • total += x[i]等价于total = total + x[i], 类似的符号还有-=, *=, /=

最后用microbenchmark比较下,R自带求和函数和我们自己写的两个版本的差异

x <- runif(1000)
microbenchmark(sum(x),sumC(x),sumR(x)
)

最快的是高度优化过的内置函数,最差的就是sumR(), 速度会比sumC()慢10倍以上。

向量输入,向量输出

R中比较常见的操作就是向量间运算,尤其R还会自动补齐。自动补齐某些时候会造成一些问题,但是C++不存在这个问题。我们可以写一个RCPP的+函数

cppFunction('NumericVector addC(NumericVector x, NumericVector y){int xn = x.size();int yn = y.size();if (xn != yn){stop("input should be same length");}NumericVector out(xn);for(int i=0; i< xn; ++i){out[i] = x[i] + y[i];}return out;
}')x <- runif(1e6)
y <- runif(1e6)
microbenchmark(addC(x,y),x+y)

矩阵输入,向量输出

每个向量类型都有矩阵等价类,NumericMatrix, IntegerMatirx, CharacterMatirx, LogicalMatirx. 让我们尝试写一个rowSums()函数

cppFunction('NumericVector rowSumsC(NumericMatrix x){int nrow = x.nrow(), ncol = x.ncol();NumericVector out(nrow);for(int i = 0; i < nrow; i++){double total =0;for(int j =0; j< ncol; j++){total += x(i,j);}out[i] = total;}return out;
}')
set.seed(1024)
x <- matrix(sample(100), nrow = 10)
rowSumsC(x)

这里注意有两点不同,在C++中,你用()对矩阵取值,而不是[]

尽管看起来C++的代码运行起来比R语言快多了,比如说R要一分钟,RCPP只要一秒,但是如果算上我们写代码的时间和调试代码的时间,刚开始不熟练估计要10分钟,那么总体来看,还是直接上手写R代码比较合适。

但是如果有一些代码要不断复用,那么写C++代码还是很划算。这个时候就建议将代码写到专门的文本中,用sourceCpp()加载,而不是cppFunction()函数

在Rsutdio中可以创建一个C++模板文件,代码写完之后还可以进行debug。

创建模板

比如说在里面写上面的rowSumsC函数,分为如下几个部分

导入头文件,加载Rcpp到命名空间中,类似于library()

#include <Rcpp.h>
using namespace Rcpp;

使用// [[Rcpp::export]]说明这里的函数会被R使用

// [[Rcpp::export]]
NumericVector rowSumsC(NumericMatrix x){int ncol = x.ncol(), nrow = x.nrow();NumericVector out(nrow);for (int i =0; i < nrow; i++ ){double total = 0;for (int j =0 ;j < ncol; j++){total += x(i,j);}out[i] = total;}return out;
}

下面部分会在sourceCpp()加载后自动运行

/*** R
library(microbenchmark)
set.seed(1014)
x <- matrix(sample(100), 10)
microbenchmark(rowSumsC(x),Matrix::rowSums(x)
)
*/

将文件保存成rowSumsC.cpp, 之后在R里用sourceCpp(file = "rowSumsC.cpp")

这篇关于使用Rcpp提高性能之入门篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1088167

相关文章

Java使用ANTLR4对Lua脚本语法校验详解

《Java使用ANTLR4对Lua脚本语法校验详解》ANTLR是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件,下面就跟随小编一起看看Java如何使用ANTLR4对Lua脚本... 目录什么是ANTLR?第一个例子ANTLR4 的工作流程Lua脚本语法校验准备一个Lua Gramm

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

Qt中QUndoView控件的具体使用

《Qt中QUndoView控件的具体使用》QUndoView是Qt框架中用于可视化显示QUndoStack内容的控件,本文主要介绍了Qt中QUndoView控件的具体使用,具有一定的参考价值,感兴趣的... 目录引言一、QUndoView 的用途二、工作原理三、 如何与 QUnDOStack 配合使用四、自

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

使用Python构建一个Hexo博客发布工具

《使用Python构建一个Hexo博客发布工具》虽然Hexo的命令行工具非常强大,但对于日常的博客撰写和发布过程,我总觉得缺少一个直观的图形界面来简化操作,下面我们就来看看如何使用Python构建一个... 目录引言Hexo博客系统简介设计需求技术选择代码实现主框架界面设计核心功能实现1. 发布文章2. 加

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

Python虚拟环境终极(含PyCharm的使用教程)

《Python虚拟环境终极(含PyCharm的使用教程)》:本文主要介绍Python虚拟环境终极(含PyCharm的使用教程),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录一、为什么需要虚拟环境?二、虚拟环境创建方式对比三、命令行创建虚拟环境(venv)3.1 基础命令3

Python Transformer 库安装配置及使用方法

《PythonTransformer库安装配置及使用方法》HuggingFaceTransformers是自然语言处理(NLP)领域最流行的开源库之一,支持基于Transformer架构的预训练模... 目录python 中的 Transformer 库及使用方法一、库的概述二、安装与配置三、基础使用:Pi