快速计算R

时间:2015-08-26 06:40:55

标签: arrays r performance multidimensional-array cumsum

我对R编程比较陌生,到目前为止,这个网站对我很有帮助,但我找不到已经涵盖了我想知道的问题。所以我决定自己发一个问题。

我的问题如下:我想找到有效的方法来计算四维数组的累积和,即我有一个四维数组x中的数据,并希望编写一个计算数组x_sum的函数,使< / p>

x_sum [i,j,k,l] = sum_ {ind1&lt; = i,ind2&lt; = j,ind3&lt; = k,ind4&lt; = l} x [ind1,ind2,ind3,ind4]

我想要使用这个功能数十亿次,这使得它尽可能高效非常重要。虽然我已经提出了几种计算总和的方法(见下文),但我怀疑更有经验的R程序员可能能够找到更有效的解决方案。所以,如果有人能提出更好的方法,我将非常感激。

这是我到目前为止所尝试的内容:

我发现了三种不同的实现(每种实现都带来了速度的提升)(见下面的代码): 一个在R中使用cumsum()函数(cumsum_4R)和两个实现,其中“繁重”在C中完成(使用.C()接口)。 C中的第一个实现仅仅是使用嵌套for循环和指针算法(cumsumC_4_old)来编写和的天真尝试。 在第二个C实现(cumsumC_4)中,我尝试使用以下article中的想法调整我的代码

正如您在下面的源代码中所看到的,适应性相对不平衡:对于某些维度,我能够替换所有嵌套的for循环,但不能替换其他循环。你有关于如何做到这一点的想法吗?

在三个实现上使用microbenchmark,对于大小为40x40x40x40的数组,我得到以下结果:

Unit: milliseconds
             expr       min         lq       mean     median         uq
     cumsum_4R(x) 976.13258 1029.33371 1064.35100 1051.37782 1074.23234
 cumsumC_4_old(x) 174.72868  177.95875  192.75392  184.11121  203.18141
     cumsumC_4(x)  56.87169   57.73512   67.34714   63.20269   68.80326
       max neval
 1381.5832    50
  283.2384    50
  105.7099    50

其他信息: 1)由于这样可以更容易地安装任何所需的软件包,我在Windows下的个人计算机上运行基准测试,但我计划在我的大学的计算机上运行完成的模拟,该计算机在Linux下运行。

编辑:2)四维数据x [i,j,k,l]实际上是外部函数的两个应用的乘积的结果:首先,矩阵与其自身的外积(即外部(mat,mat))然后取另一个矩阵的成对最小值(即外(mat2,mat2,pmin))。然后数据是产品

x =外(垫,垫)*外(mat2,mat2,pmin),

即。
x [i,j,k,l] = mat [i,j] * mat [k,l] * min(mat2 [i,j],mat2 [k,l])

四维数组具有相应的对称性。

3)我首先需要这些累积和的原因是我想运行一个测试的模拟,我需要对索引的“矩形”进行部分求和:我想迭代所有形式的总和< / p>

sum_ {k1&lt; = i1&lt; = m1,k2&lt; = i2&lt; = m2,k1&lt; = i3&lt; = m1,k2&lt; = i4&lt; = m2} x [i1,i2,i3 ,i4],

其中1 <= k1 <= m1 <= n,1 <= k2 <= m2 <= n。为了避免反复计算相同变量的总和,我首先计算所有累积和,然后计算矩形的和作为累积和的函数。你知道更有效的方法吗? 编辑到3):为了包括所有潜在的重要方面:我还想计算表格的总和

sum_ {k1&lt; = i1&lt; = m1,k2&lt; = i2&lt; = m2,1&lt; = i3&lt; = n,1&lt; = i4&lt; = n} x [i1,i2,i3 ,i4]。

(因为我可以使用累积金额轻松获得它们,我之前没有包含此规范。)

这是我使用的C代码(我保存为“cumsumC.c”):

#include<R.h>
#include<math.h>
#include <stdio.h>

int min(int a, int b){
    if(a <= b) return a;
    else return b;
}

void cumsumC_4_old(double* x, int* nv){
    int n = *nv;
    int n2 = n*n;
    int n3 = n*n*n;
    //Dim 1
    for(int i=0; i<n; i++){
        for(int j=0; j<n; j++){
            for(int k=0; k<n; k++){
                for(int l=1; l<n; l++){
                    x[i+j*n+k*n2+l*n3] += x[i + j*n +k*n2+(l-1)*n3];
                }
            }
        }
    }
    //Dim 2
    for(int i=0; i<n; i++){
        for(int j=0; j<n; j++){
            for(int k=1; k<n; k++){
                for(int l=0; l<n; l++){
                    x[i+j*n+k*n2+l*n3] += x[i + j*n +(k-1)*n2+l*n3];
                }
            }
        }
    }
    //Dim 3
    for(int i=0; i<n; i++){
        for(int j=1; j<n; j++){
            for(int k=0; k<n; k++){
                for(int l=0; l<n; l++){
                    x[i+j*n+k*n2+l*n3] += x[i + (j-1)*n +k*n2+l*n3];
                }
            }
        }
    }
    //Dim 4
    for(int i=1; i<n; i++){
        for(int j=0; j<n; j++){
            for(int k=0; k<n; k++){
                for(int l=0; l<n; l++){
                    x[i+j*n+k*n2+l*n3] += x[i-1 + j*n +k*n2+l*n3];
                }
            }
        }
    }
}


void cumsumC_4(double* x, int* nv){
    int n = *nv;
    int n2 = n*n;
    int n3 = n*n*n;
    long ind1, ind2; 
    long index, indexges = n +(n-1)*n+(n-1)*n2+(n-1)*n3, indexend;
    //Dim 1
    index = n3;
    while(index != indexges){
        x[index] += x[index-n3];
        index++;
    }   
    //Dim 2
    long teilind = n+(n-1)*n;
    for(int k=1; k<n; k++){
        ind1 = k*n2;
        ind2 = ind1 - n2;
        for(int l=0; l<n; l++){
            index = l*n3;
            indexend = teilind+index;
            while(index != indexend){
                x[index+ind1] += x[index+ind2];
                index++;
            }
        }
    }
    //Dim 3
    ind1 = n;
    while(ind1 < n+(n-1)*n){
        index = 0;
        indexend = indexges - ind1;
        ind2 = ind1-n;
        while(index < indexend){
            x[ind1+index] += x[ind2+index];
            index += n2;
        }
        ind1++;
    }
    //Dim 4
    index = 0;
    int i;
    long minind;
    while(index < indexges){
        i = 1;
        minind = min(indexges, index+n);
        while(index+i < minind){ 
            x[index+i] += x[index+i-1];
            i++;
        }
        index+=n;
    }
}

这是R函数“cumsum_4R”和用于调用和比较R中累积和函数的代码(在Windows下;对于Linux,需要调整命令dyn.load / dyn.unload;理想情况下,我想要使用50 ^ 4大小数组上的函数,但由于调用microbenchmark需要一段时间,我在这里选择了n = 40):

library("microbenchmark")

# dyn.load("cumsumC.so")
dyn.load("cumsumC.dll")

cumsum_4R <- function(x){
   return(aperm(apply(apply(aperm(apply(apply(x, 2:4,function(a) cumsum(as.numeric(a))), c(1,3,4) , function(a) cumsum(as.numeric(a))), c(2,1,3,4)), c(1,2,4), function(a) cumsum(as.numeric(a))), 1:3, function(a) cumsum(as.numeric(a))), c(3,4,2,1)))
}

cumsumC_4_old <- function(x){
    n <- dim(x)[1]
    arr <- array(.C("cumsumC_4_old", res=as.double(x), as.integer(n))$res, dim=c(n,n,n,n))
    return(arr)
}


cumsumC_4 <- function(x){
    n <- dim(x)[1]
    arr <- array(.C("cumsumC_4", res=as.double(x), as.integer(n))$res, dim=c(n,n,n,n))
    return(arr)
}

set.seed(1234)

n <- 40
x <- array(rnorm(n^4),dim=c(n,n,n,n))
r <- 6 #parameter for rounding results for comparison

res1 <- cumsum_4R(x)
res2 <- cumsumC_4_old(x)
res3 <- cumsumC_4(x)

print(c("Identical R and C1:", identical(round(res1,r),round(res2,r))))
print(c("Identical R and C2:",identical(round(res1,r),round(res3,r))))

times <- microbenchmark(cumsum_4R(x), cumsumC_4_old(x),cumsumC_4(x),times=50)
print(times)

dyn.unload("cumsumC.dll")
# dyn.unload("cumsumC.so")

感谢您的帮助!

1 个答案:

答案 0 :(得分:1)

您确实可以在原始问题中使用第2点和第3点来更有效地解决问题。实际上,这使得问题可以分离。通过可分离,我的意思是等式3中4个和的极限不依赖于你总和的变量。这个以及x是2个矩阵的外积的事实使您能够将公式中的4倍和。 3成两个2倍总和的外积。更好的是:用于定义x的2个矩阵是相同的(由mat表示) - 因此两个2倍的总和给出相同的矩阵,只需要计算一次。 代码如下:

set.seed(1234)
n=40
mat=array(rnorm(n^2),dim=c(n,n))
x=outer(mat,mat)

cumsum_sep=function(x) {
  #calculate matrix corresponding to 2-fold sums
  #actually it's just one matrix because x is an outer product of mat with itself
  tmp=t(apply(apply(x,2,cumsum),1,cumsum))
  #outer product of two-fold sums
  outer(tmp,tmp)
}

y1=cumsum_4R(x)
#note that cumsum_sep operates on the original matrix mat!
y2=cumsum_sep(mat)

检查结果是否相同

all.equal(y1,y2)
[1] TRUE

这给出了基准测试结果

microbenchmark(cumsum_4R(x),cumsum_sep(mat),times=10)

Unit: milliseconds
          expr         min          lq       mean     median         uq       max neval cld
 cumsum_4R(xx) 2084.454155 2135.852305 2226.59692 2251.95928 2270.15198 2402.2724    10   b
 cumsum_sep(x)    6.844939    7.145546   32.75852   14.45762   34.94397  120.0846    10  a 

相当不同! :)