为什么方法1比方法2快?

时间:2019-08-20 22:10:30

标签: r performance

我的问题:我在工作时调试一些代码,逐块运行它,当时我意识到一个小块花了不寻常的时间。我将其杀死并进行了较小(但在逻辑上等效)的调整,它几乎立即运行。我想知道为什么。以下代码在R中,但是,我想答案可能并不特定于R,并且可能适用于类似范式或“编译方法”的大多数编程语言?

代码和信息:
使用R版本3.6.1
已加载的库:dplyr,knitr,DataExplorer,胶水,动物园

old_df是5653380 obs的数据帧。共91个变量。
field1是类别为“ character”的策略编号的列。不是唯一的,每次都发生很多次。
date_col1和date_col2是具有“日期”类的列。

方法1:

    new_df <- old_df %>%
      group_by(field1) %>%
      mutate(checkfield = date_col1 - date_col2) %>%
      filter(checkfield < 0) %>%
      filter(row_number() == 1)
    old_df$filter <- ifelse(old_df$field1 %in% new_df$field1,1,0)

方法2:

    new_df <- old_df %>%
      group_by(field1) %>%
      filter(date_col1 < date_col2) %>%
      filter(row_number() == 1)
    old_df$filter <- ifelse(old_df$field1 %in% new_df$field1,1, 0)

您可能会看到,两种方法的预期输出都是在“过滤器”列中为策略编号添加标记“ 1”,其中date_col1

我的想法:显然,方法2可能会更快一些,因为它避免将列设置为“ checkfield”,但是我不认为这是问题所在,因为我逐行运行了方法1,并且它看起来像是'filter(checkfield <0)'行,那里的东西出了问题。为了进行测试,我定义了两个日期x,y并检查了返回“ difftime”的类(x-y)。因此,在此过滤器调用中,我们将“ difftime”与“数字”进行比较。也许这需要某种类型的杂耍来进行比较,方法2将日期对象与日期对象进行比较?

让我知道您的想法!我对此很好奇。

2 个答案:

答案 0 :(得分:1)

我相信大部分时间的增加是由于创建新列。如您所见,M1M3具有相似的时间。当然,M1M3之间的〜2毫秒之间的差异将根据数据大小成倍增加

library(tidyverse)
library(microbenchmark)

set.seed(42)
n = 1e5
d = seq.Date(Sys.Date() - 10000, Sys.Date(), 1)
x = sample(d, n, TRUE)
y = sample(d, n, TRUE)
df1 = data.frame(x, y, id = sample(LETTERS, n, TRUE))

microbenchmark(M1 = {
    df1 %>%
        group_by(id) %>%
        mutate(chk = x - y) %>%
        filter(chk < 0) %>%
        filter(row_number() == 1)
},
M2 = {
    df1 %>%
        group_by(id) %>%
        filter(x < y) %>%
        filter(row_number() == 1)
},
M3 = {
    df1 %>%
        group_by(id) %>%
        mutate(chk = x - y) %>%
        filter(x < y) %>%
        filter(row_number() == 1)
})
#Unit: milliseconds
# expr       min        lq      mean    median       uq       max neval
#   M1 13.130673 13.405151 15.088266 14.096772 15.56080 22.636533   100
#   M2  5.931192  6.208457  6.564363  6.402879  6.71973  9.354252   100
#   M3 11.360640 11.607993 12.449220 12.001383 12.57732 18.260131   100

关于将difftimenumeric进行比较的观点似乎并没有太大区别

library(microbenchmark)

set.seed(42)
n = 1e7
x = sample(d, n, TRUE)
y = sample(d, n, TRUE)
df1 = data.frame(x, y)
df1$difference = df1$x - df1$y

class(df1$difference)
#[1] "difftime"

microbenchmark(date_vs_date = {
    df1 %>% filter(x < y)
},
date_vs_numeric ={
    df1 %>% filter(difference < 0)
})
#Unit: milliseconds
#            expr      min       lq     mean   median       uq      max neval
#    date_vs_date 177.1789 222.4112 243.6617 233.7221 244.2765 430.4273   100
# date_vs_numeric 181.6222 217.1121 251.6127 232.7213 249.8218 455.8285   100

答案 1 :(得分:1)

到目前为止,我的探索工作以一个简化的示例和一个稍小的数据集(只有一百万行和最少的列子集)进行了单独的测试(test_cf,用于过滤checkfield变量,test_lt用于过滤日期比较)大约需要相同的时间,而两者都与创建检查字段列所需的时间相同。一次执行两个操作(comb,创建并比较)需要花费2.5倍的时间,不确定原因。

也许您可以以此为二分法/基准测试来寻找罪魁祸首的起点。

     test elapsed relative
2    comb   5.557    2.860
1 make_cf   1.943    1.000
4 test_cf   2.122    1.092
3 test_lt   2.109    1.085

我使用rbenchmark::benchmark()是因为我更喜欢输出格式:microbenchmark::microbenchmark()可能更准确(但是如果它产生了很大的变化,我会感到惊讶)。

代码

library(dplyr)
n <- 1e6 ## 5653380 in orig; reduce size for laziness
set.seed(101)
## sample random dates, following
##  https://stackoverflow.com/questions/21502332/generating-random-dates
f <- function(n)
    sample(seq(as.Date('1999/01/01'), as.Date('2000/01/01'), by="day"),
           replace=TRUE,
           size=n)
dd <- tibble(
    date_col1=f(n),
    date_col2=f(n)
 ## set up checkfield so we can use it without creating it
) %>% mutate(cf=date_col1-date_col2)

基准:

library(rbenchmark)
benchmark(
    make_cf=dd %>% mutate(checkfield=date_col1-date_col2),
    comb=dd %>% mutate(checkfield=date_col1-date_col2) %>% filter(checkfield<0),
    test_lt=dd %>% filter(date_col1<date_col2),
    test_cf=dd %>% filter(cf<0),
    columns=c("test","elapsed","relative")
)