我的问题:我在工作时调试一些代码,逐块运行它,当时我意识到一个小块花了不寻常的时间。我将其杀死并进行了较小(但在逻辑上等效)的调整,它几乎立即运行。我想知道为什么。以下代码在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将日期对象与日期对象进行比较? 让我知道您的想法!我对此很好奇。
答案 0 :(得分:1)
我相信大部分时间的增加是由于创建新列。如您所见,M1
和M3
具有相似的时间。当然,M1
和M3
之间的〜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
关于将difftime
与numeric
进行比较的观点似乎并没有太大区别
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")
)