2016-02-26 101 views
2

我有,我会大量调用一个函数(每次迭代大约10^11倍的优化,一些不同的实验)。我已经实现了一个快速版本,但我很努力地看到我可以如何提高性能。 '系统'时间很短,用户时间很长。加快代码:减少“用户”时间

下面的代码,它需要在一个整数,并返回表示该整数是一个不同的基计数系统的载体(例如碱= 2给出了二进制,碱= 10给出了标准结果背面)。参数k给出要返回的向量的长度,所以前面可能有很多零。

正如你看到的,功能采取5或7秒的运行,但它没有一个是系统时间。我想了解为什么,以及是否有办法加快速度。我有其他功能(的99%的时间是花费在一个环路一个功能,但加速它200fold只有一半的运行时间)这个同样的问题,但我出这一个清晰度。

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倍,而一个电话的时间比一百个电话慢三十倍。我想了解为什么,并希望能够解释它的任何资源。

谢谢!

+2

你可以从你的矢量化功能,而不是单独调用它要检查每个整数受益。 – Heroka

+0

重复使用“pow”值而不是两次计算它有一个小的潜在好处。另外请注意,可以通过注意'i'和'i + 1'之间的“除以基数”关系来减少指数的数量。 – mvkorpel

+1

大部分时间都花在调用函数的开销上。如果用C++循环替换'sapply',即可以通过向@Heroka指出的矢量化函数,可以显着提高性能。 – Roland

回答

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 
+1

矢量化是一个C级'for'循环。 – Roland

+0

感谢您的完整示例,这加上您和@ Roland的评论是非常有帮助的。 – Tom

+0

不客气,谢谢您提供的数据,代码和明确的问题陈述。 – Heroka