-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
reducer assignment issues #76
Comments
I am looking into this... seems an awkward bug! But I can reproduce it. |
I dove a bit more into it and I did not have the same problems when using I also see |
Hey, I think I found it. The way we're assigning from xexpressions is currently broken. Will fix ASAP. Sorry for these painful bugs and thanks for the awesome bug reports! |
I think I did try Hopefully you can fix it with the xexpression assignment! Not a problem about it being buggy, I'm more than happy to hunt these down for you. |
I've investigated this quite a bit now and am still not sure what's going on exactly. With pure-C++ functors it seems to work fine. I also call gc from Rcpp using
which noticeably slows the function call but doesn't lead to a segfault. Things like Maybe we can simplify this even further and ask on the Rcpp mailing list for help? Btw calling back to R in the reducer will be a lot slower than pure C++ :) |
I'll see if I can simplify further. I'm aware of the painful speed loss from calling back to R from cpp, but think that the flexibility might be nice for small scale things, even if its slower. |
maybe we can make an example for only Numeric / double stuff and see if the guys over at Rcpp have ideas. |
You mean like this? // [[Rcpp::depends(xtensorrr)]]
// [[Rcpp::plugins(cpp14)]]
#include <xtensor-r/rarray.hpp>
#include <xtensor/xreducer.hpp>
#include <xtensor/xio.hpp>
#include <xtensor/xarray.hpp>
#include <Rcpp.h>
using namespace Rcpp;
// Assume we take in doubles and return doubles
// [[Rcpp::export]]
SEXP custom_reducer_impl(SEXP x, Rcpp::Function f, std::vector<std::size_t> axes) {
// Create an rarray of doubles
const xt::rarray<double>& x_rray = xt::rarray<double>(x);
// This lambda calls out to the R function at each iteration of the reduce call
// The return value of f is a SEXP, we assume it holds a double under the hood
auto r_functor_lambda = [f](const auto& left, const auto& right) {
SEXP f_res = f(left, right);
double res = REAL(f_res)[0];
return(res);
};
// Make an xtensor xreducer from our lambda
auto xr_functor = xt::make_xreducer_functor(r_functor_lambda);
// THIS IS THE PROBLEM LINE!
xt::xarray<double> res = xt::reduce(xr_functor, x_rray, axes);
xt::rarray<double> res2 = res;
std::cout << "xarray: " << res << std::endl;
std::cout << "rarray: " << res2 << std::endl;
return(res2);
}
// [[Rcpp::export]]
SEXP custom_reducer_impl_bad(SEXP x, Rcpp::Function f, std::vector<std::size_t> axes) {
// Create an rarray of doubles
const xt::rarray<double>& x_rray = xt::rarray<double>(x);
// This lambda calls out to the R function at each iteration of the reduce call
// The return value of f is a SEXP, we assume it holds a double under the hood
auto r_functor_lambda = [f](const auto& left, const auto& right) {
SEXP f_res = f(left, right);
double res = REAL(f_res)[0];
return(res);
};
// Make an xtensor xreducer from our lambda
auto xr_functor = xt::make_xreducer_functor(r_functor_lambda);
// THIS IS THE PROBLEM LINE!
xt::rarray<double> res = xt::reduce(xr_functor, x_rray, axes);
std::cout << "rarray: " << res << std::endl;
return(res);
} # change to your own path.
Rcpp::sourceCpp("~/Desktop/simple-example.cpp")
small <- matrix(as.double(1:6), ncol = 2)
large <- matrix(as.double(1:5000), ncol = 2)
fn <- function(.x, .y) {.x + .y}
custom_reducer_impl(small, fn, 0L)
#> [1] 6 15
custom_reducer_impl_bad(small, fn, 0L)
#> [1] 6 15
custom_reducer_impl(large, fn, 0L)
#> [1] 3126250 9376250
# should crash
# custom_reducer_impl_bad(large, fn, 0L) Update) Updated the comments to be more relevant |
I've used a lambda instead of the separate functor idea, but I think the same idea is still holding |
Cool! I'll get back to work on this tomorrow. Btw. there is also the immediate_evaluation mode, which is not lazy, but faster (and assigns to a intermediate xarray). It could be interesting for you! |
There was one other idea I wanted to try. By-passing I noticed a few things here:
Regarding the instability, you do get different results than before when we used Below, the cpp function These results seem to tell me either: a) There is still something wrong with the way xexpressions are assigned to // [[Rcpp::depends(xtensorrr)]]
// [[Rcpp::plugins(cpp14)]]
#include <xtensor-r/rarray.hpp>
#include <xtensor/xreducer.hpp>
#include <xtensor/xio.hpp>
#include <xtensor/xarray.hpp>
#include <Rcpp.h>
using namespace Rcpp;
// Assume we take in doubles and return doubles
// [[Rcpp::export]]
SEXP custom_reducer_impl(SEXP x, Rcpp::Function f, std::vector<std::size_t> axes) {
// Create an rarray of doubles
const xt::rarray<double>& x_rray = xt::rarray<double>(x);
// This lambda calls out to the R function at each iteration of the reduce call
// The return value of f is a SEXP, we assume it holds a double under the hood
auto r_functor_lambda = [f](const auto& left, const auto& right) {
SEXP f_res = f(left, right);
double res = REAL(f_res)[0];
return(res);
};
// Make an xtensor xreducer from our lambda
auto xr_functor = xt::make_xreducer_functor(r_functor_lambda);
// THIS IS THE PROBLEM LINE!
xt::xarray<double> res = xt::reduce(xr_functor, x_rray, axes);
xt::rarray<double> res2 = res;
return(res2);
}
// [[Rcpp::export]]
SEXP custom_reducer_impl_bad(SEXP x, Rcpp::Function f, std::vector<std::size_t> axes) {
// Create an rarray of doubles
const xt::rarray<double>& x_rray = xt::rarray<double>(x);
// This lambda calls out to the R function at each iteration of the reduce call
// The return value of f is a SEXP, we assume it holds a double under the hood
auto r_functor_lambda = [f](const auto& left, const auto& right) {
SEXP f_res = f(left, right);
double res = REAL(f_res)[0];
return(res);
};
// Make an xtensor xreducer from our lambda
auto xr_functor = xt::make_xreducer_functor(r_functor_lambda);
// THIS IS THE PROBLEM LINE!
xt::rarray<double> res = xt::reduce(xr_functor, x_rray, axes);
return(res);
}
// [[Rcpp::export]]
SEXP custom_reducer_bare_call(SEXP x, SEXP f_name_, std::vector<std::size_t> axes, SEXP env) {
// get the name of the function object in the env
// install it as f
const char* f_name = CHAR(Rf_asChar(f_name_));
SEXP f = Rf_install(f_name);
// We also need left_ and right_ installed
// to shove them in the call before the reduce loop
SEXP left_ = Rf_install("left_");
SEXP right_ = Rf_install("right_");
// Create the call like f(left_, right_)
// Ignoring dots for now
SEXP f_call = PROTECT(Rf_lang3(f, left_, right_));
// Create an rarray of doubles
const xt::rarray<double>& x_rray = xt::rarray<double>(x);
// This lambda calls out to the R function at each iteration of the reduce call
// The return value of f is a SEXP, we assume it holds a double under the hood
auto r_functor_lambda = [f_call, env](const auto& left, const auto& right) {
// left and right should be length 1
// Assume they are REAL for now
SEXP left_val = PROTECT(Rf_allocVector(REALSXP, 1));
SEXP right_val = PROTECT(Rf_allocVector(REALSXP, 1));
// Shove in the actual values
REAL(left_val)[0] = left;
REAL(right_val)[0] = right;
// Define left_ again (for the existing call to find)
// And assign its value to be the SEXP left_val
SEXP left_ = Rf_install("left_");
Rf_defineVar(left_, left_val, env);
// Same for right_
SEXP right_ = Rf_install("right_");
Rf_defineVar(right_, right_val, env);
// Eval the call in the right env
SEXP f_res = PROTECT(Rf_eval(f_call, env));
//SEXP f_res = PROTECT(R_forceAndCall(f_call, 2, env));
// Pull out the result
double res = REAL(f_res)[0];
UNPROTECT(3);
return(res);
};
// Make an xtensor xreducer from our lambda
auto xr_functor = xt::make_xreducer_functor(r_functor_lambda);
// THIS IS THE PROBLEM LINE!
xt::xarray<double> res2 = xt::reduce(xr_functor, x_rray, axes);
xt::rarray<double> res = res2;
//xt::rarray<double> res = xt::reduce(xr_functor, x_rray, axes);
UNPROTECT(1);
return(res);
} # change to your own path.
Rcpp::sourceCpp("~/Desktop/simple-example.cpp")
small <- matrix(as.double(1:6), ncol = 2)
large <- matrix(as.double(1:5000), ncol = 2)
reallarge <- matrix(as.double(1:50000), ncol = 2)
fn <- function(.x, .y) {.x + .y}
fn2 <- function(.x, .y) {.x}
# //////////////////////////////////////////////////////////////////////////////
# sum-reduce
custom_reducer_impl(small, fn, 0L)
#> [1] 6 15
custom_reducer_impl_bad(small, fn, 0L) # somehow this works
#> [1] 6 15
custom_reducer_impl(large, fn, 0L)
#> [1] 3126250 9376250
# should crash
# custom_reducer_impl_bad(large, fn, 0L)
# //////////////////////////////////////////////////////////////////////////////
# reduce but just return x back
# All good here
custom_reducer_impl(small, fn2, 0L)
#> [1] 1 4
custom_reducer_impl_bad(small, fn2, 0L)
#> [1] 1 4
custom_reducer_impl(large, fn2, 0L)
#> [1] 1 2501
# Crashes
# custom_reducer_impl_bad(large, fn2, 0L)
# //////////////////////////////////////////////////////////////////////////////
# Bare bones version
custom_reducer_impl_bare <- function(.x, .f, ...) {
.f <- rlang::as_function(.f)
custom_reducer_bare_call(.x, ".f", 0L, env = environment())
}
custom_reducer_impl_bare(small, fn)
#> [1] 6 15
# its alive!
custom_reducer_impl_bare(large, fn)
#> [1] 3126250 9376250
# this works too!
custom_reducer_impl_bare(reallarge, fn)
#> [1] 312512500 937512500
# //////////////////////////////////////////////////////////////////////////////
# Benchmarks
# Really large
bench::mark(
as.array(colSums(reallarge)),
rray:::rray_reducer_cpp("sum", reallarge, 0L),
custom_reducer_impl(reallarge, fn, 0L), # <- functor + Rcpp::Function
custom_reducer_impl_bare(reallarge, fn) # <- bare bones
)
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
#> # A tibble: 4 x 10
#> expression min mean median max `itr/sec` mem_alloc n_gc
#> <chr> <bch:t> <bch:t> <bch:t> <bch:tm> <dbl> <bch:byt> <dbl>
#> 1 as.array(… 37.6µs 40.4µs 39µs 1.48ms 24734. 7.39KB 2
#> 2 "rray:::r… 98.1µs 276.9µs 255.7µs 9.9ms 3612. 1.5MB 13
#> 3 custom_re… 472ms 479.2ms 479.2ms 486.46ms 2.09 2.49KB 37
#> 4 custom_re… 18.4ms 22.1ms 20.4ms 55.75ms 45.2 2.49KB 23
#> # … with 2 more variables: n_itr <int>, total_time <bch:tm> Created on 2018-12-11 by the reprex package (v0.2.1.9000) |
I'll check that out! Thanks! |
Debugging this a little more just to try and narrow in on the issue. Here are a few simpler variations:
2 fails randomly on large matrices when you try and print the result out, and I have no idea why. I'm generally getting the error message The interesting thing here is that this happens even though I am not accessing // [[Rcpp::depends(xtensorrr)]]
// [[Rcpp::plugins(cpp14)]]
#include <xtensor-r/rarray.hpp>
#include <xtensor/xreducer.hpp>
#include <xtensor/xio.hpp>
#include <xtensor/xarray.hpp>
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
SEXP custom_reducer_impl_simple(SEXP x, std::vector<std::size_t> axes) {
const xt::rarray<double>& x_rray = xt::rarray<double>(x);
// Just return the double value of 1
auto r_functor_lambda = [](const auto& left, const auto& right) {
double res = 1;
return(res);
};
auto xr_functor = xt::make_xreducer_functor(r_functor_lambda);
xt::rarray<double> res = xt::reduce(xr_functor, x_rray, axes);
return(res);
}
// [[Rcpp::export]]
SEXP custom_reducer_impl_simple_error(SEXP x, std::vector<std::size_t> axes) {
const xt::rarray<double>& x_rray = xt::rarray<double>(x);
// Create a NumericVector in there
auto r_functor_lambda = [](const auto& left, const auto& right) {
NumericVector l(1, 1.0);
double res = 1;
return(res);
};
auto xr_functor = xt::make_xreducer_functor(r_functor_lambda);
xt::rarray<double> res = xt::reduce(xr_functor, x_rray, axes);
return(res);
}
// [[Rcpp::export]]
SEXP custom_reducer_impl_simple_works(SEXP x, std::vector<std::size_t> axes) {
const xt::rarray<double>& x_rray = xt::rarray<double>(x);
// Create a NumericVector in there
auto r_functor_lambda = [](const auto& left, const auto& right) {
NumericVector l(1, 1.0);
double res = 1;
return(res);
};
auto xr_functor = xt::make_xreducer_functor(r_functor_lambda);
xt::xarray<double> res2 = xt::reduce(xr_functor, x_rray, axes);
xt::rarray<double> res = res2;
return(res);
}
# change to your own path.
Rcpp::sourceCpp("~/Desktop/example.cpp")
real_large <- matrix(as.double(1:50000), ncol = 2)
# Fine
for(i in 1:10000) {
x <- custom_reducer_impl_simple(real_large, 0L)
}
# This should error if you try it
# If you comment out the print(x) line, it probably wont error
for(i in 1:10000) {
x <- custom_reducer_impl_simple_error(real_large, 0L)
print(x)
}
# Fine
for(i in 1:10000) {
x <- custom_reducer_impl_simple_works(real_large, 0L)
print(x)
}
|
just FYI I just found a bug in the reducers for column_major (which is always the case in R). |
ah, forget what I just said, it's just a bug with the new keep_dim feature :/ |
Should this issue be closed? |
I'm still having the error, but custom reducers are low on my priority list right now, and probably won't make it into the first version of rray. That said, if you throw out the rest of the issue and focus on this comment: #76 (comment), then that is the simplest way to showcase the error. |
Hey @DavisVaughan FYI, we have worked quite a bit on Xtensor reducers recently (thanks to the hard work of @adriendelsalle) in core Xtensor, and that issue should now be fixed! |
So, I've been debugging an issue all morning, and finally think I have a grasp of what's happening. It's not a fun one, so be warned.
The short version is:
I think when you assign the result of
xt::reduce()
back to anxt::rarray
, something goes wrong, but if you assign it back toxt::xarray
then coerce that toxt::rarray
everything is fine. This only seems to show up when reducing over large-ish (>2500 elements) matrices.The long version
I'm trying (and successfully!) to expose the "custom reducer" idea to the R user, so they can do something like
rray_reducer(x, function(.x, .y) {.x + .y})
to get a reduced sum over a specified axis. It would be great because you can then use any R function as the reducer function (probably with some performance loss because you have to call back to R repeatedly from cpp, but im okay with that for now).It works for small matrices (or maybe im getting lucky) but when I up the size to ~2500 elements or more, things fall apart.
The only way I can think to give you a reproducible example is to make a standalone cpp file that you can use
Rcpp::sourceFile()
on...so...I'm sorry about that.This version goes to
xarray
first, then torarray
. It seems to work fine. In therray_custom_reducer_impl
function, with the comment// THIS IS THE PROBLEM LINE!
is the section that is causing the issue. It works fine in this example because of the conversion first toxarray
.Correct results:
standard output and standard error
Created on 2018-12-09 by the reprex package (v0.2.1.9000)
This is the not working version, even though I think it should work. It's more...unstable...than actually not working as the
rarray
result has variable output. The only thing I changed is that problem line, and now I just return 1 because I don't trust the output. But a key thing to look at here is the std output, where I've printed resultingrarray
objects from the two calls to the same function. I had to try and run it multiple times because sometimes it just crashes...Sometimes this will crash on you, sometimes it will just give odd results...
standard output and standard error
Created on 2018-12-09 by the reprex package (v0.2.1.9000)
The text was updated successfully, but these errors were encountered: