首页 > 解决方案 > 从 Rcpp 中的列表中提取元素似乎有点慢

问题描述

我刚刚写了一个 Rcpp 函数,它具有三个相同大小的输入向量,x(numeric) y(numeric) 和category(character)。然后我想返回一个列表,列表大小等于唯一类别值的长度。此列表中的每个元素都是基于xy具有相应类别的相同大小的矩阵(相等的行和列)。

但是,我发现当大小很大时我的代码不够快n。我认为原因是我需要从列表中提取一些内容,进行一些计算并将其每次插入回来。有没有人有关于如何加快这个过程的建议。

rcpp 代码

#include <Rcpp.h>
using namespace Rcpp;

//[[Rcpp::export]]
List myList(NumericVector x, NumericVector y, CharacterVector category) {

  int n = x.size();
  CharacterVector levels = unique(category);
  int levels_size = levels.size();
  List L(levels_size);

  int plot_width = 600;
  int plot_height = 600;

  // Each element in the list L has the same size Matrix
  for(int j = 0; j < levels_size; j++) {
    NumericMatrix R(plot_height, plot_width);
    L[j] = R;
  }
  int id = 0;

  double xmax = max(x);
  double ymax = max(y);
  double xmin = min(x);
  double ymin = min(y);

  for(int i=0; i < n; i++) {

    for(int j = 0; j < levels_size; j++) {
      if(category[i] == levels[j]) {
        id = j;
        break;
      }
    }

    int id_x = floor((x[i] - xmin)/(xmax - xmin) * (plot_width - 1));
    int id_y = floor((y[i] - ymin)/(ymax - ymin) * (plot_height - 1));

    NumericMatrix M = L[id];
    // some computation in M
    M(id_y, id_x) += 1;
    L[id] = M;
  }
  return(L);
}

R代码

n <- 1e8
class <- 20

x <- rnorm(n)
y <- rnorm(n)
category <- sample(as.factor(1:class), size = n, replace = TRUE)

start_time <- Sys.time()
L <- myList(x = x, y = y, category = category)
end_time <- Sys.time()
end_time - start_time
# Time difference of 35.3367 secs

标签: rrcpp

解决方案


我怀疑有关性能的两个主要问题:

  • 大量字符串比较(按顺序1e9
  • 矩阵有很多缓存未命中,因为通常两个连续的 xy 对不会来自同一类别,因此需要不同的矩阵

两者都指向同一个方向:不要尝试实现自己的 GROUP BY 操作。数据库引擎和包之类data.table的更清楚如何做到这一点。例如,在使用时,data.table我们需要一个更简单的函数,它期望 x 和 y为一个类别并输出单个矩阵:

#include <Rcpp.h>
using namespace Rcpp;

//[[Rcpp::export]]
NumericMatrix getMat(NumericVector x, NumericVector y,
                     double xmin, double xmax, double ymin, double ymax,
                     int plot_width = 600, int plot_height = 600) {
    int n = x.size();
    NumericMatrix M(plot_height, plot_width);

    for(int i=0; i < n; i++) {
        int id_x = floor((x[i] - xmin)/(xmax - xmin) * (plot_width - 1));
        int id_y = floor((y[i] - ymin)/(ymax - ymin) * (plot_height - 1));
        M(id_y, id_x) += 1;
    }
    return M;
}

/***R
n <- 1e8
class <- 20

library("data.table")
foo <- data.table(x = rnorm(n),
                  y = rnorm(n),
                  category = sample(as.factor(1:class), size = n, replace = TRUE))

xmin <- min(foo$x)
xmax <- max(foo$x)
ymin <- min(foo$y)
ymax <- max(foo$y)

system.time(bar <- foo[,
                       list(baz = list(getMat(x, y, xmin, xmax, ymin, ymax))),
                       by = category])
*/

笔记:

  • 在我的系统上,聚合时间不到 6 秒。
  • 如果setkey(foo, category)在聚合之前执行 a 会更快。不过,这会在物理上改变行的顺序。小心使用!
  • data.table语法有点简洁,但是习惯了...
  • 输出的结构不同,但可以根据需要进行转换。

推荐阅读