On a tangent from the main topic of this thread: sometimes (especially to non-experts) it's not obvious whether a variable is protected or not.

I don't think there's any easy way to determine that, but perhaps there should be. Would it be possible to add a run-time test you could call in C code (e.g. is_protected(x)) that would do the same search the garbage collector does in order to determine if a particular pointer is protected?

This would be an expensive operation, similar in cost to actually doing a garbage collection. You wouldn't want to do it routinely, but it would be really helpful in debugging.

Duncan Murdoch

On 2025-04-11 6:05 a.m., Suharto Anggono Suharto Anggono via R-devel wrote:
  On second thought, I wonder if the caching in my changed 'StringFromLogical' 
in my previous message is safe. While 'ans' in the C function 'coerceToString' 
is protected, its element is also protected. If the object corresponding to 
'ans' is then no longer protected, is it possible for the cached object 
'TrueCh' or 'FalseCh' in 'StringFromLogical' to be garbage collected? If it is, 
I think of clearing the cache for each first filling. For example, by abusing 
'warn' argument, the following is added to my changed 'StringFromLogical'.

  if (*warn) TrueCh = FalseCh = NULL;

Correspondingly, in 'coerceToString',

  warn = i == 0;

is inserted before

  SET_STRING_ELT(ans, i, StringFromLogical(LOGICAL_ELT(v, i), &warn));

for LGLSXP case.

---------------------
On Thursday, 10 April 2025 at 10:54:03 pm GMT+7, Martin Maechler 
<maech...@stat.math.ethz.ch> wrote:


Suharto Anggono Suharto Anggono via R-devel
     on Thu, 10 Apr 2025 07:53:04 +0000 (UTC) writes:

     > Chain of calls of C functions in coerce.c for as.character(<logical>) in 
R:

     > do_asatomic
     > ascommon
     > coerceVector
     > coerceToString
     > StringFromLogical (for each element)

     > The definition of 'StringFromLogical' in coerce.c :

     > Chain of calls of C functions in coerce.c for as.character(<logical>) in 
R:
     >
     > do_asatomic
     > ascommon
     > coerceVector
     > coerceToString
     > StringFromLogical (for each element)
     >
     > The definition of 'StringFromLogical' in coerce.c :
     >
     > attribute_hidden SEXP StringFromLogical(int x, int *warn)
     > {
     >    int w;
     >    formatLogical(&x, 1, &w);
     >    if (x == NA_LOGICAL) return NA_STRING;
     >    else return mkChar(EncodeLogical(x, w));
     > }
     >
     > The definition of 'EncodeLogical' in printutils.c :
     >
     > const char *EncodeLogical(int x, int w)
     > {
     >    static char buff[NB];
     >    if(x == NA_LOGICAL) snprintf(buff, NB, "%*s", min(w, (NB-1)), 
CHAR(R_print.na_string));
     >    else if(x) snprintf(buff, NB, "%*s", min(w, (NB-1)), "TRUE");
     >    else snprintf(buff, NB, "%*s", min(w, (NB-1)), "FALSE");
     >    buff[NB-1] = '\0';
     >    return buff;
     > }
     >
     > > L <- sample(c(TRUE, FALSE), 10^7, replace = TRUE)
     > > system.time(as.character(L))
     >    user  system elapsed
     >    2.69    0.02    2.73
     > > system.time(c("FALSE", "TRUE")[L+1])
     >    user  system elapsed
     >    0.15    0.04    0.20
     > > system.time(c("FALSE", "TRUE")[L+1L])
     >    user  system elapsed
     >    0.08    0.05    0.13
     > > L <- rep(NA, 10^7)
     > > system.time(as.character(L))
     >    user  system elapsed
     >    0.11    0.00    0.11
     > > system.time(c("FALSE", "TRUE")[L+1])
     >    user  system elapsed
     >    0.16    0.06    0.22
     > > system.time(c("FALSE", "TRUE")[L+1L])
     >    user  system elapsed
     >    0.09    0.03    0.12
     >
     > `as.character` of a logical vector that is all NA is fast enough.
     > It appears that the call to 'formatLogical' inside > the C function
     > 'StringFromLogical' does not introduce much    > slowdown.


     > I found that using string literal inside the C function 
'StringFromLogical', by replacing
     > EncodeLogical(x, w)
     > with
     > x ? "TRUE" : "FALSE"
     > (and the call to 'formatLogical' is not needed anymore), make it faster.

indeed! ... and we also notice that the 'w' argument is neither
needed anymore, and that makes sense: At this point when you
know you have a an R logical value there are only three
possibilities and no reason ever to warn about the conversion.

     > Alternatively,
or in addition !


     > "fast path" could be introduced in 'EncodeLogical', potentially also 
benefits format() in R.
     > For example, without replacing existing code, the following fragment 
could be inserted.
     >
     >    if(x == NA_LOGICAL) {if(w == R_print.na_width) return 
CHAR(R_print.na_string);}
     >    else if(x) {if(w == 4) return "TRUE";}
     >    else {if(w == 5) return "FALSE";}
     >
     > However, with either of them, c("FALSE", "TRUE")[L+1L] is still faster 
than as.character(L) .
     >
     > Precomputing or caching possible results of the C function 'StringFromLogical' allows 
as.character(L) to be as fast as c("FALSE", "TRUE")[L+1L] in R. For example, 
'StringFromLogical' could be changed to
     >
     > attribute_hidden SEXP StringFromLogical(int x, int *warn)
     > {
     >    static SEXP TrueCh, FalseCh;
     >    if (x == NA_LOGICAL) return NA_STRING;
     >    else if (x) return TrueCh ? TrueCh : (TrueCh = mkChar("TRUE"));
     >    else return FalseCh ? FalseCh : (FalseCh = mkChar("FALSE"));

     > }

Indeed, and something along this line (storing the other two constant strings) 
was also
my thought when seeing the
   mkChar(x ? "TRUE" : "FALSE)
you implicitly proposed above.

I'm looking into applying both speedups;
thank you very much, Suharto!

Martin


--
Martin Maechler
ETH Zurich  and  R Core team
[[alternative HTML version deleted]]

______________________________________________
R-devel@r-project.org mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel

______________________________________________
R-devel@r-project.org mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel

Reply via email to