使用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

相关文章

Conda与Python venv虚拟环境的区别与使用方法详解

《Conda与Pythonvenv虚拟环境的区别与使用方法详解》随着Python社区的成长,虚拟环境的概念和技术也在不断发展,:本文主要介绍Conda与Pythonvenv虚拟环境的区别与使用... 目录前言一、Conda 与 python venv 的核心区别1. Conda 的特点2. Python v

Spring Boot中WebSocket常用使用方法详解

《SpringBoot中WebSocket常用使用方法详解》本文从WebSocket的基础概念出发,详细介绍了SpringBoot集成WebSocket的步骤,并重点讲解了常用的使用方法,包括简单消... 目录一、WebSocket基础概念1.1 什么是WebSocket1.2 WebSocket与HTTP

C#中Guid类使用小结

《C#中Guid类使用小结》本文主要介绍了C#中Guid类用于生成和操作128位的唯一标识符,用于数据库主键及分布式系统,支持通过NewGuid、Parse等方法生成,感兴趣的可以了解一下... 目录前言一、什么是 Guid二、生成 Guid1. 使用 Guid.NewGuid() 方法2. 从字符串创建

Python使用python-can实现合并BLF文件

《Python使用python-can实现合并BLF文件》python-can库是Python生态中专注于CAN总线通信与数据处理的强大工具,本文将使用python-can为BLF文件合并提供高效灵活... 目录一、python-can 库:CAN 数据处理的利器二、BLF 文件合并核心代码解析1. 基础合

Python使用OpenCV实现获取视频时长的小工具

《Python使用OpenCV实现获取视频时长的小工具》在处理视频数据时,获取视频的时长是一项常见且基础的需求,本文将详细介绍如何使用Python和OpenCV获取视频时长,并对每一行代码进行深入解析... 目录一、代码实现二、代码解析1. 导入 OpenCV 库2. 定义获取视频时长的函数3. 打开视频文

Spring IoC 容器的使用详解(最新整理)

《SpringIoC容器的使用详解(最新整理)》文章介绍了Spring框架中的应用分层思想与IoC容器原理,通过分层解耦业务逻辑、数据访问等模块,IoC容器利用@Component注解管理Bean... 目录1. 应用分层2. IoC 的介绍3. IoC 容器的使用3.1. bean 的存储3.2. 方法注

Python内置函数之classmethod函数使用详解

《Python内置函数之classmethod函数使用详解》:本文主要介绍Python内置函数之classmethod函数使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 类方法定义与基本语法2. 类方法 vs 实例方法 vs 静态方法3. 核心特性与用法(1编程客

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

Python中注释使用方法举例详解

《Python中注释使用方法举例详解》在Python编程语言中注释是必不可少的一部分,它有助于提高代码的可读性和维护性,:本文主要介绍Python中注释使用方法的相关资料,需要的朋友可以参考下... 目录一、前言二、什么是注释?示例:三、单行注释语法:以 China编程# 开头,后面的内容为注释内容示例:示例:四