@@ -3,8 +3,7 @@ title: Passing user-supplied C++ functions with RcppXPtrUtils
3
3
author : Iñaki Ucar
4
4
license : GPL (>= 2)
5
5
tags : function
6
- summary : Demonstrates how to build and check user-supplied C++ functions
7
- with the RcppXPtrUtils package
6
+ summary : Demonstrates how to build and check user-supplied C++ functions with the RcppXPtrUtils package
8
7
layout : post
9
8
src : 2017-08-04-passing-cpp-function-pointers-rcppxptrutils.Rmd
10
9
---
@@ -25,7 +24,7 @@ couple of issues though:
25
24
complies with the internal signature supported by the C++ backend,
26
25
which may lead to weird runtime errors.
27
26
28
- ## Better ` XPtr ` handling with RcppXPtrUtils
27
+ ### Better ` XPtr ` handling with RcppXPtrUtils
29
28
30
29
In a nutshell, RcppXPtrUtils provides functions for dealing with these
31
30
two issues: namely, ` cppXPtr ` and ` checkXPtr ` . As a package author,
@@ -41,10 +40,154 @@ returned object is an R's `externalptr` wrapped into a class called
41
40
42
41
43
42
43
+ {% highlight r %}
44
+ library(RcppXPtrUtils)
44
45
46
+ ptr <- cppXPtr("double foo(int a, double b) { return a + b; }")
47
+ class(ptr)
48
+ {% endhighlight %}
45
49
46
50
47
51
52
+ <pre class =" output " >
53
+ [1] " ; XPtr" ;
54
+ </pre >
48
55
49
56
50
57
58
+ {% highlight r %}
59
+ ptr
60
+ {% endhighlight %}
61
+
62
+
63
+
64
+ <pre class =" output " >
65
+ 'double foo(int a, double b)' < ; pointer: 0x564011b984e0> ;
66
+ </pre >
67
+
68
+ The ` checkXptr ` function checks the object against a given
69
+ signature. If the verification fails, it throws an informative error:
70
+
71
+
72
+
73
+ {% highlight r %}
74
+ checkXPtr(ptr, type="double", args=c("int", "double")) # returns silently
75
+ checkXPtr(ptr, "int", c("int", "double"))
76
+ {% endhighlight %}
77
+
78
+
79
+
80
+ <pre class =" output " >
81
+ Error in checkXPtr(ptr, " ; int" ; , c(" ; int" ; , " ; double" ; )): Bad XPtr signature:
82
+ Wrong return type 'int', should be 'double'.
83
+ </pre >
84
+
85
+
86
+
87
+ {% highlight r %}
88
+ checkXPtr(ptr, "int", c("int"))
89
+ {% endhighlight %}
90
+
91
+
92
+
93
+ <pre class =" output " >
94
+ Error in checkXPtr(ptr, " ; int" ; , c(" ; int" ; )): Bad XPtr signature:
95
+ Wrong return type 'int', should be 'double'.
96
+ Wrong number of arguments, should be 2'.
97
+ </pre >
98
+
99
+
100
+
101
+ {% highlight r %}
102
+ checkXPtr(ptr, "int", c("double", "std::string"))
103
+ {% endhighlight %}
104
+
105
+
106
+
107
+ <pre class =" output " >
108
+ Error in checkXPtr(ptr, " ; int" ; , c(" ; double" ; , " ; std::string" ; )): Bad XPtr signature:
109
+ Wrong return type 'int', should be 'double'.
110
+ Wrong argument type 'double', should be 'int'.
111
+ Wrong argument type 'std::string', should be 'double'.
112
+ </pre >
113
+
114
+ ### Complete use case
115
+
116
+ First, let us define a templated C++ backend that performs some
117
+ processing with a user-supplied function and a couple of adapters:
118
+
119
+
120
+ {% highlight cpp %}
121
+ #include <Rcpp.h>
122
+ using namespace Rcpp;
123
+
124
+ template <typename T >
125
+ NumericVector core_processing(T func, double l) {
126
+ double accum = 0;
127
+ for (int i=0; i<1e3; i++)
128
+ accum += sum(as<NumericVector >(func(3, l)));
129
+ return NumericVector(1, accum);
130
+ }
131
+
132
+ // [[ Rcpp::export]]
133
+ NumericVector execute_r(Function func, double l) {
134
+ return core_processing<Function >(func, l);
135
+ }
136
+
137
+ typedef SEXP (* funcPtr)(int, double);
138
+
139
+ // [[ Rcpp::export]]
140
+ NumericVector execute_cpp(SEXP func_ , double l) {
141
+ funcPtr func = * XPtr<funcPtr >(func_ );
142
+ return core_processing<funcPtr >(func, l);
143
+ }
144
+ {% endhighlight %}
145
+
146
+ Note that the user-supplied function takes two arguments: one is also
147
+ user-provided and the other is provided by the backend itself. This
148
+ core is exposed through the following R function:
149
+
150
+
151
+ {% highlight r %}
152
+ execute <- function(func, l) {
153
+ stopifnot(is.numeric(l))
154
+ if (is.function(func))
155
+ execute_r(func, l)
156
+ else {
157
+ checkXPtr(func, "SEXP", c("int", "double"))
158
+ execute_cpp(func, l)
159
+ }
160
+ }
161
+ {% endhighlight %}
162
+
163
+ Finally, we can compare the ` XPtr ` approach with a pure R-based one,
164
+ and with a compiled function wrapped in R, as returned by
165
+ ` Rcpp::cppFunction ` :
166
+
167
+
168
+ {% highlight r %}
169
+ func_r <- function(n, l) rexp(n, l)
170
+ cpp <- "SEXP foo(int n, double l) { return rexp(n, l); }"
171
+ func_r_cpp <- Rcpp::cppFunction(cpp)
172
+ func_cpp <- cppXPtr(cpp)
173
+
174
+ microbenchmark::microbenchmark(
175
+ execute(func_r, 1.5),
176
+ execute(func_r_cpp, 1.5),
177
+ execute(func_cpp, 1.5)
178
+ )
179
+ {% endhighlight %}
180
+
181
+
182
+
183
+ <pre class =" output " >
184
+ Unit: microseconds
185
+ expr min lq mean median uq
186
+ execute(func_r, 1.5) 9010.765 9434.233 9985.919 9739.334 10469.518
187
+ execute(func_r_cpp, 1.5) 8751.593 9218.137 9776.670 9430.297 10257.962
188
+ execute(func_cpp, 1.5) 170.708 193.201 248.939 229.528 300.495
189
+ max neval cld
190
+ 14538.013 100 b
191
+ 13712.093 100 b
192
+ 416.451 100 a
193
+ </pre >
0 commit comments