[Rd] R_CheckUserInterrupt() can be a performance bottleneck within GUIs
Martin Becker
m@rt|n@becker @end|ng |rom mx@un|-@@@r|@nd@de
Tue Dec 17 11:46:54 CET 2024
tl;dr: R_CheckUserInterrupt() can be a performance bottleneck
within GUIs. This also affects functions in the 'stats'
package, which could be improved by changing the position
of calls to R_CheckUserInterrupt().
Dear all,
Recently I was puzzled because some code in a package under development,
which consisted almost entirely of a .Call() to a function written in C,
was running much slower within RStudio compared to R in a terminal. It
took me some time to identify the cause, so I thought I would share my
findings; perhaps they will be helpful to others.
The performance drop was caused by R_CheckUserInterrupt(), which I call
(perhaps too often) in my C code. While calling R_CheckUserInterrupt()
seems to be quite cheap when running R or Rscript in a terminal, it is
more expensive when running R within a GUI, especially within RStudio,
as I noticed (but also, e.g., within R.app on MacOS). In fact, using a
GUI (especially RStudio) can change the cost of (frequent) calls to
R_CheckUserInterrupt() from negligible to critical (in real-world
applications). Significant performance drops are also visible for
functions in the 'stats' package, e.g., pwilcox().
The following MWE (using Rcpp) illustrates the problem. Consider the
following code:
---
library(Rcpp)
cppFunction('double nonsense(const int n, const int m, const int check) {
int i, j;
double result;
for (i=0;i<n;i++) {
if (check) R_CheckUserInterrupt();
result = 1.;
for (j=1;j<=m;j++) if (j%2) result *= j; else result /=j;
}
return(result);
}')
tmp1 <- system.time(nonsense(1e8,10,0))[1]
tmp2 <- system.time(nonsense(1e8,10,1))[1]
cat("w/o check:",tmp1,"sec., with check:",tmp2,"sec.,
diff.:",tmp2-tmp1,"sec.\n")
tmp3 <- system.time(pwilcox(rwilcox(1e5,40,60),40,60))[1]
cat("wilcox example:",tmp3,"sec.\n")
---
Running this code when R (4.4.2) is started in a terminal window
produces the following measurements/output (Apple M1, MacOS 15.1.1):
w/o check: 0.525 sec., with check: 0.752 sec., diff.: 0.227 sec.
wilcox example: 1.028 sec.
Running the same code when R is used within R.app (1.81 (8462)
aarch64-apple-darwin20) on the same machine results in:
w/o check: 0.525 sec., with check: 1.683 sec., diff.: 1.158 sec.
wilcox example: 2.13 sec.
Running the same code when R is used within RStudio Desktop (2024.12.0
Build 467) on the same machine results in:
w/o check: 0.507 sec., with check: 22.905 sec., diff.: 22.398 sec.
wilcox example: 29.686 sec.
So, the performance drop is already remarkable for R.app, but really
huge for RStudio.
Presumably, checking for user interrupts within a GUI is more involved
than within a terminal window, so there may not be much room for
improvement in R.app or RStudio (and I know that this list is not the
right place to suggest improvements for RStudio or to report unwanted
behaviour). However, it might be worth considering
1. an addition to the documentation in WRE (explaining that too many
calls to R_CheckUserInterrupt() can cause a performance bottleneck,
especially when the code is running within a GUI),
2. check (and possibly change) the position of R_CheckUserInterrupt() in
some base R functions. For example, moving R_CheckUserInterrupt() from
cwilcox() to pwilcox() and qwilcox() in src/nmath/wilcox.c may lead to a
significant improvement (while still being feasible in terms of response
time).
Best,
Martin
--
apl. Prof. Dr. Martin Becker, Akad. Oberrat
Lehrstab Statistik
Quantitative Methoden
Fakultät für Empirische Humanwissenschaften und Wirtschaftswissenschaft
Universität des Saarlandes
Campus C3 1, Raum 2.17
66123 Saarbrücken
Deutschland
More information about the R-devel
mailing list