| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## master #5269 +/- ##
=======================================
Coverage 98.59% 98.60%
=======================================
Files 79 79
Lines 14685 14691 +6
=======================================
+ Hits 14479 14486 +7
+ Misses 206 205 -1 ☔ View full report in Codecov by Sentry.
|
Sorry, something went wrong.
|
Looks good in that this solution works and it's a good issue to fix. Just wondering about any performance decrease, particularly when updating existing columns by name inside a loop. When set() was enhanced to allow adding new columns, that was done at C level inside Cassign, iirc. I realize the over-allocation might be tricky to achieve at C level though. |
Sorry, something went wrong.
Benchmark - Updating existing columnsSetupI used two different installations of R but both with version 4.1.1. And put everything into a script I call twice. First line is always the benchmark with an integer set, second one with a character set. Scriptlibrary(data.table)
# ensure that if branch hits
options(datatable.alloccol=1024L)
set.seed(373)
DT = as.data.table(matrix(rnorm(1e4*1e4), ncol=1e4))
cols = lapply(1:100, function(i) sample(ncol(DT), 2e3))
colsns = lapply(1:100, function(i) colnames(DT)[sample(ncol(DT), 2e3)])
R.home()
microbenchmark::microbenchmark(times=10,
for (i in cols) {set(DT, j=i, value=DT$V1)},
for (i in colsns) {set(DT, j=i, value=DT$V1)}
)100 set / 2000 cols eachCurrent (update.dev.pkg())[1] "/opt/R-4.1.1"
Unit: seconds
expr min lq mean median uq max neval
for (i in cols) { set(DT, j = i, value = DT$V1) } 1.276253 1.279709 1.285705 1.282165 1.285725 1.306588 10
for (i in colsns) { set(DT, j = i, value = DT$V1) } 1.301202 1.302665 1.336406 1.305066 1.310549 1.617736 10Suggested Version[1] "/usr/lib/R"
Unit: seconds
expr min lq mean median uq max neval
for (i in cols) { set(DT, j = i, value = DT$V1) } 1.356582 1.362484 1.368112 1.365384 1.368992 1.392386 10
for (i in colsns) { set(DT, j = i, value = DT$V1) } 1.377221 1.380865 1.415789 1.384860 1.390200 1.700783 1010000 set / 10 cols eachoptions(datatable.alloccol=1L) so we run into edgy case Current (update.dev.pkg())[1] "/opt/R-4.1.1"
Unit: milliseconds
expr min lq mean median uq max neval
for (i in cols) { set(DT, j = i, value = DT$V1) } 633.9781 638.1574 660.568 643.1093 676.7702 723.2221 10
for (i in colsns) { set(DT, j = i, value = DT$V1) } 1673.4164 1691.8153 1746.374 1711.6982 1779.9412 1979.1221 10Suggested Version[1] "/usr/lib/R"
Unit: milliseconds
expr min lq mean median uq max neval
for (i in cols) { set(DT, j = i, value = DT$V1) } 667.0488 669.4836 681.223 676.7265 697.8435 700.7359 10
for (i in colsns) { set(DT, j = i, value = DT$V1) } 1590.1081 1614.8530 1644.618 1621.1307 1624.6613 1893.9807 10Adding 10k columns to empty data.tablelibrary(data.table)
options(datatable.alloccol=1e4)
set.seed(373)
cols = paste0("V", 1:1e4)
R.home()
microbenchmark::microbenchmark(times=10,
{ DT = data.table(); for (i in cols) {set(DT, j=i, value=i)} }
)Current (update.dev.pkg())[1] "/opt/R-4.1.1"
Unit: milliseconds
expr min lq mean median uq max neval
{ DT = data.table() for (i in cols) { set(DT, j = i, value = i) } } 152.2915 154.1718 157.0469 154.8296 158.0875 167.0684 10Suggested Version[1] "/usr/lib/R"
Unit: milliseconds
expr min lq mean median uq max neval
{ DT = data.table() for (i in cols) { set(DT, j = i, value = i) } } 186.834 189.5498 192.6968 192.3136 194.6215 201.9198 10Naturally we loose some ms for the check of is.character(j) and due to the substitute, assign procedure. |
Sorry, something went wrong.
|
@mattdowle I tried to move everything down to the C level, hence, we save the 2nd chmatch call and added some benchmarks. We also simplify the Cassign logic here and remove some edgy cases as e.g. with readRDS(), load() or structure() What is still open is the question of whether we can also save the substitute/assign environment assignment at the R level or move it down to C. In case this is possible, we should change it also at :=, setDT, etc. |
Sorry, something went wrong.
| name = substitute(x) | ||
| x = .Call(Cassign,x,i,j,NULL,value) | ||
| if (is.name(name)) | ||
| assign(as.character(name),x,parent.frame(),inherits=TRUE) |
There was a problem hiding this comment.
This assign() looks pretty complicated to reason about, I would like to see more tests of robustness here (setDT() within a function, places where inherits=TRUE is important, etc.).
At a glance I can't tell why this is needed though -- it definitely warrants a comment explaining why we need to branch.
Sorry, something went wrong.
There was a problem hiding this comment.
I simplified now the whole code. I also added a comment why we need the assign.
Ultimately it would be nice to let set work also in the cases where it needs to overallocate and is inside a function, howeover, for making this to work we would need to find the right environment where to assign (might not be the first where x is present) and it is not clear whether it would be the last due to scoping
Sorry, something went wrong.
|
Generated via commit 4c5b1a2 Download link for the artifact containing the test results: ↓ atime-results.zip
|
Sorry, something went wrong.
| { | ||
| .Call(Cassign,x,i,j,NULL,value) | ||
| set = function(x, i=NULL, j, value) { | ||
| name = as.character(substitute(x)) |
Sorry, something went wrong.
There was a problem hiding this comment.
set(x) expects that x is a data.table.
We already use a similar approach inside := to assign the name.
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Closes #1831
Closes #4100 (what's left is the duplicate of #496)
Towards #496 establishing consistent behavior between set and :=
Towards #678 closing oldtncol = TRUELENGTH(dt); // TO DO: oldtncol can be just called tl now, as we won't realloc here any more.