Skip to content

Commit

Permalink
Add experimental C-based period apply
Browse files Browse the repository at this point in the history
I became obsessed with improving the performance of period.apply()
after seeing its abysmal performance in this StackOverflow question:
https://stackoverflow.com/q/50062851/271616.

The C code works for the most part, but tests are needed (obviously).
The loop over the 'INDEX' (endpoints) was written very carefully to
avoid unnecessary allocations. For example, the call remains the same
for each period. The value bound to the crazily-named object changes
each iteration, which avoids creating a new call for each period.
  • Loading branch information
joshuaulrich committed May 12, 2018
1 parent 2b04ada commit 7c29359
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 0 deletions.
8 changes: 8 additions & 0 deletions R/period.apply.R
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,11 @@ function(x,FUN, ...)
ep <- endpoints(x,'years')
period.apply(x,ep,FUN, ...)
}

period_apply <- function(x, INDEX, FUN, ...) {
fun <- substitute(FUN)
e <- new.env()
pl <- .Call("xts_period_apply", x, INDEX, fun, e, PACKAGE = "xts")
# need to copy other attributes to result?
.xts(do.call(rbind, pl), .index(x)[INDEX])
}
1 change: 1 addition & 0 deletions inst/include/xts.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ void copy_xtsCoreAttributes(SEXP x, SEXP y);// internal only

int isXts(SEXP x); // is.xts analogue
int firstNonNA(SEXP x);
SEXP extract_col (SEXP x, SEXP j, SEXP drop, SEXP first_, SEXP last_);
#endif /* _XTS */

#ifdef __cplusplus
Expand Down
69 changes: 69 additions & 0 deletions src/period_apply.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
# xts: eXtensible time-series
#
# Copyright (C) 2008 Jeffrey A. Ryan (FORTRAN implementation)
# Copyright (C) 2018 Joshua M. Ulrich (C implementation)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
*/


#include<Rinternals.h>
#include "xts.h"

SEXP xts_period_apply(SEXP _data, SEXP _index, SEXP _function, SEXP _env)
{
if (!isInteger(_index)) {
error("index must be integer");
}

int i;
R_xlen_t n = xlength(_index);
SEXP _result = PROTECT(allocVector(VECSXP, n));
SEXP _j = PROTECT(allocVector(INTSXP, ncols(_data)));
SEXP _drop = PROTECT(ScalarLogical(0));

int *index = INTEGER(_index);
for (i = 0; i < ncols(_data); i++)
INTEGER(_j)[i] = i + 1;

SEXP _idx0 = PROTECT(ScalarInteger(0));
SEXP _idx1 = PROTECT(ScalarInteger(0));
int *idx0 = INTEGER(_idx0);
int *idx1 = INTEGER(_idx1);

/* reprotect the subset object */
SEXP _xsubset;
PROTECT_INDEX px;
PROTECT_WITH_INDEX(_xsubset = R_NilValue, &px);

/* subset object name */
SEXP _subsym = install("_.*crazy*._.*name*._");
defineVar(_subsym, _xsubset, _env);

/* function call on subset */
SEXP _subcall = PROTECT(lang3(_function, _subsym, R_DotsSymbol));

int N = n - 1;
for (i = 0; i < N; i++) {
idx0[0] = index[i] + 1;
idx1[0] = index[i + 1];
REPROTECT(_xsubset = extract_col(_data, _j, _drop, _idx0, _idx1), px);
defineVar(_subsym, _xsubset, _env);
SET_VECTOR_ELT(_result, i, eval(_subcall, _env));
}

UNPROTECT(7);
return _result;
}

0 comments on commit 7c29359

Please sign in to comment.