Rev 75129 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
---title: "ALTREP: TODOs and Topics For Discussion"author: Gabe Beckeroutput:html_document:toc: truetoc_float:collapsed: falsesmooth_scroll: false---<!-- render withRscript -e 'rmarkdown::render(input="ALTREP_discussion_todo.md", output_file="ALTREP_discussion_todo.html")'--># Direct use of metadata## arithmetic summary functions (`sum`, `mean`, `prod`)We can use `NO_NA` information to move NA checking outside of tightloops. Initial testing suggests about a 1.7x speedup for integersumming.### Caveats and gotchas:We can get the tight loop to be completely empty but need to ensureupdated (ie return value) is correct in all cases. if we take it outof the loop. This also, perhaps expectedly(?), seems to make a muchbigger difference in the integer case than the double case.## `is.na`, `anyNA`, `na.omit`We already use `NO_NA` to fastpass these, but in cases where we knowsortedness but don't know NA status (rare but technically possible) weshould be checking only the ends of the vector for `anyNA` andleveraging sortedness for is.na. This can be either a binary searchfor the first (na at end, or last (na at start) NA value, or a linearwalk that starts at the relevant end and walks until it sees a non-NA.## `min`, `max`, `which.min`, `which.max``min`, `max` already dispatch to altrep methods, but core behavior shoulduse sortedness if present, after method dispatch (if the methodreturns NULL)`which.min`, `which.max` should use sortedness whether or not theyultimately get methods as well.## `match` (and thus `%in%`)Test using sortedness in core match data. Binary search. rewritemacros in `ALTREP` branch in more maintainable form and move out ofaltrep methods.# User-level R functions/operations that can always return ALTREPs with metadata## is.na* Guaranteed no NAs* Sorted if x is sorted+ Will need logic to determine sortedness direction## sample.intGuaranteed No NAs# `R` functions/ops which can sometimes return ALTREPs with metadata"Propogation" refers here to infering guaranteed metadata about thereturn value based on the metadata of one or more arguments passed in("parents")## Single propogation (one parent)### Including sortedness* `lag`* `head` (if not covered more generally by bracket)* `tail` (if not covered more generally by bracket)### Excluding sortedness (only `No_NA`)* `cumsum`, `cumprod` (contingent on overflow check)* `rep`* `sample`## Double propogation (two parents)### Including sortedness* `+ - * /` (contingent on overflow checking)+ at least one scalar operand* `* /`+ one operand scalar+direction will reverse if scalar is < 0* `< <= > >=`+ at least one operand scalar### Excluding sortedness (only NO_NA)* `+ - * / ^` (contingent on overflow checking)+ both operands length > 1* `< > >= <= ==`+ both operands length > 1* `& |`* `[` (on a vector/1d array x, atomic vector indices i)+ for logical indices `length(i) <= length(x)`+ for numeric indices `max(i) <= length(x)`* `rnorm` (mean, sd are two parents)* `runif` (min, max are two parents)## propogation mechanismsSome propogation situations will be essentially one-off and requirecustom logic, but I envision there being C level functions (or macrosif desired) representing the common metadata propagationrelationships, which can be used to wrap return values passed back upto R from core C code.Should only be called if the relevant "parent" objects are allintact ALTREPs (ie non-expanded, with valid metadata to derive from)```C/* parent is an ALTREP with valid metadata, ie non-expanded */SEXP derive_nainfo1(SEXP ans, SEXP parent) {SEXP ret;switch(TYPEOF(parent)) {case INTSXP:ret = int_derive_nainfo1(ans, parent);break;case REALSXP:ret = real_derive_nainfo1(ans, parent);break;/*etc*/default:/*need to be careful about protecting this in caller if we do itthis way we could also always return a wrapper even if the allmetadata is unknown/uninformative but that would be addingoverhead for no reason...On the other hand, we shouldn't even be calling this function unlessparent is an intact ALTREP so that shouldn't happen much. ~GB*/ret = ans;break;}return ret;}SEXP int_derive_nainfo1(ans, parent) {SEXP ret;int nona = INTEGER_NO_NA(parent);if(nona == 1) {SEXP mdata = PROTECT(allocVector(VECSXP, 2));VECTOR_SET_ELT(mdata, 0, ScalarInteger(nona));VECTOR_SET_ELT(mdata, 1, ScalarInteger(UNKNOWN_SORTEDNESS));ret = make_wrapper(ans, mdata);UNPROTECT(1);} else {ret = ans;}return ret}```# `ALTREP` Classes## Internal### RlesInternal because we (may) want, e.g., scalar comparison against sortedvectors to return logical RLE. E.g., when x is sorted, is.na, andscalar comparisons of x can return rles.## External### HashTabled vectorsVectors which carry around their hash-table once it is created, thusmaking matching really fast.### Indexed vectorsVectors which carry around their order, making them fast to sort,match against, etc.### Rcpp IntegrationIntegration, probably in both directions, with the Rcpp Vector c++ classes.Boths ways, here, meaning an ALTREP that operates on those classes and using the ALTREP API in the definition of those classes to implement the various things that the Vector classes do.### Arrow IntegrationALTREP vectors backed by the Apache Arrow interoperability framework.# Other work## formatting and printingCurrently operates directly on dataptr. Need version that operates on SEXP via `*_GET_REGION` and doesn't expand altreps.This can be mostly simple wrapper functions around what's there now I think. A first pass that seems to work is in the branch.## Handling attrbitues in more cases when wrapping# For Discussion## Changing No_NA metadata (possibly with name change)from 0/1 indicating only knowledge of NA absence to* `-1/0/1` or `-1/NA/1`+ indicating known NA presence, unknown, known NA absence, or* The known number of NAs present (NA or 0+ count)+ Note `*_NO_NA` could still be supported here if desired## What to do about scalarsA lot of functions return scalars where we can infer and attachNo_NA-ness metadata. Not sure we want to do this, though, because ofoverhead.### Possible solutionChange `*_NO_NA(x)` to just do the check if x is length 1.upsides: no wrapping scalar SEXPs, saves on unneeded overheaddownsides: Breaks the assumption that *_NO_NA always returns 0 for non`ALTREP` SEXPs. I'm not sure this is bad, but it is different.Note: this won't be faster in the fast-passes case, but it will make the propogation logic much more unified, I think.## Can we retain metadata as a side-effect in some cases?e.g., is.na(x) doesn't find any NAs, it would be nice if x retainedthat info even though it's not the return value. Same for is.unsorted(x)Note: This is only relevant when NOT talking about return value, forwhich creating a new wrapper `ALTREP` should be sufficient, especiallyonce wrappers of wrappers will collapse themselves at creation timewhere appropriate.Note: My initial conception included soemthing along the lines of`Set_No_NA(x, val)` and `Set_Is_Sorted(x, val)` in the `ALTREP` methodstable as part of the API, but current thinking is to not exposesomething like that, even to the R internals, much less package-space.### Possible Solution 1: method for each op which should annotate metadata inplacee.g., `Is_NA` method present in current `ALTREP` branch.Upsides: Doesn't allow non-`ALTREP` code to penetrate theabstraction. Doubles as a way to have custom mechanisms for the chosenops (e.g. is.na on RLEs)Downsides: Each time we add fucntionality to do something like thiswe'll have to add a whole new method to the `ALTREP` table.### Possible Solution 2: Revisit metadata setter methodsUnlikely, here largely for completeness.### Possible Solution3: Don't support that or heavily restrict what ops its supported inWe can just say yeah, that would be cool, but we don't allow it. onlyreturn values can be annotated by any operation that isn't already amethod in the tableUpsides: Easiest.Downsides: Feels like leaving something on the table.## User-code level introspection of ALTREPsSome package developers may want to. e.g., treat rle ALTREPs differentlyCurrently getting info about what type of `ALTREP` x is is restricted tothe semi-user-facing `.Internal(inspect(x))`, which can't beprogrammed against, and some undocumented, non-API macros deep inaltrep.c## methods/ops exclusive to specific ALTREP classesIs there any reasonable way to support this? Do we want to?### Example: a "windowing" ALTREP`ALTREP` class which provides a no-copy window into another vector, codewill want to move the window around. For example when operating onchunks/groups of data in the parent vector.But a `Set_Window` (or whatever we call it) method doesn't make anysense for any of the other vector types.### Possible solution 1: Allow developers to create new ALTREP "types"Which inherit from one of the basetypes. E.g. `ALTWINDOWEDINTEGER` which would have the `Set_Window` method. along with all the `ALTINTEGER` methodsUpsides: empowers developers to do some pretty powerful stuff within the `ALTREP` framework.Downsides: Dramatically increases the complexity of the `ALTREP` machinery and, effectively, the C API## Possible new methods for consideration for Integer/RealWe may not ultimately want to implement all of these, need to weighcomplexity/size of API vs expected benefits. This is intentionally ananti-conserative list of possibilities for discussion.### Sort_checkImplements custom logic to actually perform a sort-check on the data (as opposed to the metadata-based fastpass)Example: only checking the "runs" vector for an RLE### SortImplements custom sorting logicExample: asking a columnstore to create a temporary column with the sorted data.### OrderImplements custom logic for order.### Is_NAImplements custom is.na logic for the `ALTREP` data representationExample: Arrow uses a bitmask to represent missingness (they call itnull-ness). is.na needs to compute on that### Which, which.min, which.maxExample: `ALTREP` pointing to Indexed columnstore memory where we wantthe columnstore to do the min computation.### Set_eltThe current decision is not to include this, but it would be useful,particularly in non-read-only interoperability settings, to see if itmakes sense to includeGotchas/downsies: very easy to accidentally create a vector that hasreference mechanics, which I think we really want to avoid.### Match### Unique### Duplicated