我有一个函数,我会调用很多(在一个优化中,每次迭代大约10 ^ 11次,对于一些不同的实验)。我已经实现了快速版本,但我很难看到如何提高性能。 “系统”时间很短,用户时间很长。
这是代码,它接受一个整数并返回一个向量,表示该整数是一个不同的基本计数系统(例如,base = 2给出二进制,base = 10给出标准结果)。参数k给出了要返回的向量的长度,因此前面可能有很多零。
正如您将看到的,这些函数需要5或7秒才能运行,但这些都不是系统时间。我想了解原因,以及是否有办法加快速度。我对其他函数也存在同样的问题(99%的时间是在一个循环中花费一个函数,但速度提高了200倍,运行时间只减半),但为了清晰起见,我显示了这个。
library(Rcpp)
library(microbenchmark)
# Rcpp version of the function
cppFunction('
NumericVector convert10tobase_c(double number_b10, int k, int base = 4){
if(number_b10 >= pow(base, k)){
stop("k is not large enough to contain the number in this base");
}
NumericVector ret(k);
if(k == 1){
return number_b10;
}
for (int i = 1 ;i < k; i++){
double entry = floor(number_b10 / pow(base, (k - i)));
ret[i-1] = entry;
number_b10 = number_b10 - entry * pow(base, (k - i));
}
ret[k-1] = number_b10;
return ret;
}')
# R version of the function
convert10tobase <- function(number_b10, k, base = 5){
if(number_b10 >= base ^ k){
stop("k is not large enough to contain the number in this base")
}
ret <- rep(0, k)
if(k == 1){
return(number_b10)
}
for (i in 1:(k - 1)){
entry <- floor(number_b10 / base^(k-i))
ret[i] <- entry
number_b10 <- number_b10 - entry * (base ^ (k - i))
}
ret[k] <- number_b10
return(ret)
}
# generate test data one hundred, thousand and million integers
set.seed(1)
base <- 4
k <- 8
ints_short <- floor(runif(1e2) * (base^k))
ints_long <- floor(runif(1e4) * (base^k))
ints_v_long <- floor(runif(1e6) * (base^k))
# benchmark the Rcpp version
microbenchmark(
one = convert10tobase_c(ints_short[1], k, base),
hundred = sapply(1:length(ints_short), function(i) convert10tobase_c(ints_short[i], k, base)),
ten_thous = sapply(1:length(ints_long), function(i) convert10tobase_c(ints_long[i], k, base)),
times = 100)
# test R and Rcpp times
r_start <- proc.time()
t <- sapply(1:length(ints_v_long), function(i) convert10tobase(ints_v_long[i], k, base))
r_stop <- proc.time()
c_start <- proc.time()
t <- sapply(1:length(ints_v_long), function(i) convert10tobase_c(ints_v_long[i], k, base))
c_stop <- proc.time()
# results - little time in 'system'
r_stop - r_start
c_stop - c_start
顺便说一下,我把这个函数调用了一次,一百次和十万次。一百个电话的时间比一个电话慢300倍,而一万个电话的速度比一百个电话慢三十倍。我想理解为什么,并希望能够解释它的任何资源。
谢谢!
答案 0 :(得分:5)
R非常擅长有效地对多个类似事物做同样的事情。因此,如果在执行某些操作之前将相似的事物组合在一起,则代码会更有效。这可能有点棘手,特别是当你从其他编码背景到达时。
这是一个解决方案,你的函数在R中被向量化(不确定这与C ++循环有什么关系,可能是内部的东西)。它可能会进一步优化,但它比每个单独的数字使用sapply
快100倍。它返回一个矩阵,每个数字输入一行,每个条目一列。当数字大于base ^k
时,返回一行NA。在进一步处理输出时,可以轻松识别该行。
convert10tobase_v <- function(number_b10, k, base = 5){
orig_b10 <- number_b10 #save original for check after
if(k == 1){
return(number_b10)
}
#initialize matrix to store results
ret <- matrix(0, ncol=k, nrow=length(number_b10))
#tiny-forloop, won't influenc performance and makes
#storing results/modifying number_b10 easier
for (i in 1:(k - 1)){
entry <- floor(number_b10 / base^(k-i))
ret[,i] <- entry
number_b10 <- number_b10 - entry * (base ^ (k - i))
}
ret[,k] <- number_b10
ret[orig_b10 >= base ^ k,] <- NA #set 'too large' numbers to missing
return(ret)
}
微基准:
Unit: microseconds
expr min lq mean median uq max neval cld
one_single 20.216 25.1910 31.94323 29.079 37.6310 58.469 100 a
hundred_singles 2217.461 2317.9145 2499.23338 2386.336 2498.4525 4436.476 100 b
ten_thous_singles 240467.874 246613.1635 253205.12598 249890.060 252432.2090 307050.155 100 c
one_v 22.703 26.5910 33.09706 30.323 36.3875 62.823 100 a
hundred_v 53.181 56.9135 68.05703 61.889 75.5740 129.066 100 a
ten_thous_v 2641.359 2707.2920 2806.83843 2744.613 2827.9620 4645.160 100 b