div - Depuración(línea por línea) de la DLL generada por Rcpp en Windows
sidebarpanel shiny (1)
Recientemente, he estado experimentando con Rcpp (en línea) para generar DLL que realizan varias tareas en las entradas R proporcionadas. Me gustaría poder depurar el código en estas DLL línea por línea, dado un conjunto específico de R entradas. (Estoy trabajando bajo Windows).
Para ilustrar, consideremos un ejemplo específico que cualquiera debería poder ejecutar ...
El siguiente código es una función cxx realmente simple que simplemente duplica el vector de entrada. Sin embargo, myvar
cuenta que hay una variable adicional myvar
que cambia de valor varias veces pero no afecta la salida; esto se agregó para que podamos ver cuándo el proceso de depuración se está ejecutando correctamente.
library(inline)
library(Rcpp)
f0 <- cxxfunction(signature(a="numeric"), plugin="Rcpp", body=''
Rcpp::NumericVector xa(a);
int myvar = 19;
int na = xa.size();
myvar = 27;
Rcpp::NumericVector out1(na);
for(int i=0; i < na; i++) {
out1[i] = 2*xa[i];
myvar++;
}
myvar = 101;
return(Rcpp::List::create( _["out1"] = out1));
'')
Después de ejecutar lo anterior, escribiendo el comando
getLoadedDLLs()
abre una lista de archivos DLL en la sesión R. El último en la lista debería ser el DLL creado por el proceso anterior, tiene un nombre temporal aleatorio, que en mi caso es
file7e61645c
La columna "Nombre de archivo" muestra que cxxfunction ha puesto esta DLL en la ubicación tempdir()
, que para mí es actualmente
C:/Users/TimP/AppData/Local/Temp/RtmpXuxtpa/file7e61645c.dll
Ahora, la forma obvia de llamar a la DLL es a través de f0
, como sigue
> f0(c(-7,0.7,77))
$out1
[1] -14.0 1.4 154.0
Pero, por supuesto, también podemos llamar a la DLL directamente por su nombre usando el comando .Call
:
> .Call("file7e61645c",c(-7,0.7,77))
$out1
[1] -14.0 1.4 154.0
Así que he llegado al punto en el que llamo a una DLL independiente directamente con la entrada R (aquí, el vector c(-7,0.7,77)
), y le c(-7,0.7,77)
que devuelva la respuesta correctamente a R.
Sin embargo, lo que realmente necesito es una facilidad para la depuración línea por línea (usando gdb, supongo) que me permitirá observar el valor de myvar
en 19, 27, 28, 29, 29, 30 y finalmente 101 a medida que avanza el código. El ejemplo anterior se configura deliberadamente para que la llamada a la DLL no nos diga nada sobre myvar.
Para aclarar, la "condición de ganancia" aquí es poder observar el cambio de myvar (¡ver el valor myvar = 19 sería el primer paso!) Sin agregar nada más al cuerpo del código. Obviamente, esto puede requerir cambios en la forma en que se compila el código (¿hay que activar la configuración del modo de depuración?), O la forma en que se llama R, pero no sé por dónde empezar. Como se señaló anteriormente, todo esto está basado en Windows.
Nota final: en mis experimentos, realicé algunas modificaciones menores a una copia de cxxfunction para que la DLL de salida, y el código que la contiene, reciba un nombre definido por el usuario y se ubique en un directorio definido por el usuario, en lugar de un nombre temporal y ubicación. Pero esto no afecta a la esencia de la pregunta. Menciono esto solo para enfatizar que debería ser bastante fácil modificar la configuración de compilación si alguien me da un codazo :)
Para completar, establecer verbose = TRUE en la llamada a la función cxx original anterior muestra que el argumento de compilación tiene la siguiente forma:
C:/R/R-2.13.2/bin/i386/R CMD SHLIB file7e61645c.cpp 2> file7e61645c.cpp.err.txt
g++ -I"C:/R/R-213~1.2/include" -I"C:/R/R-2.13.2/library/Rcpp/include" -O2 -Wall -c file7e61645c.cpp -o file7e61645c.o
g++ -shared -s -static-libgcc -o file7e61645c.dll tmp.def file7e61645c.o C:/R/R-2.13.2/library/Rcpp/lib/i386/libRcpp.a -LC:/R/R-213~1.2/bin/i386 -lR
Mi versión adaptada tiene un argumento de compilación idéntico al anterior, excepto que la cadena "file7e61645c" se reemplaza en todas partes por la elección del nombre del usuario (por ejemplo, "testdll") y los archivos relevantes copiados a una ubicación más permanente.
Gracias de antemano por vuestra ayuda, chicos :)
Estoy un poco sorprendido por la obsesión que algunos usuarios de Rcpp tienen con el paquete en inline y su función cxxfunction()
. Sí, es realmente muy útil y seguramente ha impulsado la adopción de Rcpp , ya que hace que la experimentación rápida sea mucho más fácil. Sí, nos permitió utilizar más de 700 pruebas de unidad en las fuentes. Sí, lo uso todo el tiempo para mostrar ejemplos aquí, en la lista de rcpp-devel o incluso en vivo en presentations .
¿Pero eso significa que deberíamos usarlo para cada tarea? ¿Significa que no tiene "costos", como nombres de archivos aleatorios en un directorio temporal, etc. pp? Romain y yo discutimos lo contrario en nuestra documentación.
Por último, la depuración de los módulos R cargados dinámicamente es difícil en su forma actual. Hay una sección completa en las Extensiones en R (obligatorias) al respecto, y Doug Bates publicó una o dos veces un tutorial sobre cómo hacerlo a través de ESS y Emacs (aunque siempre olvido dónde lo publicó; una vez fue IIRC en el rcpp - lista de niveles ).
Edición 2012-julio-07:
Aquí está tu paso a paso:
(Preámbulo: He usado gcc y g ++ durante muchos años, e incluso cuando agrego -g no siempre convierto -O2 en -O0. Realmente no estoy seguro de que necesites eso, pero a medida que lo pides ... .)
Establezca su variable de entorno CXXFLAGS en "-g -O0 -Wall". Existen numerosas formas de hacerlo, algunas dependen de la plataforma (por ejemplo, el panel de control de Windows) y, por lo tanto, son menos universales e interesantes. Yo uso
~/.R/Makevars
en Windows y Unix. Podría usar eso, o podría anular $ RHOME / etc / Makeconf de todo el sistema de R o podría usar Makeconf.site o ... Vea la documentación completa --- pero como dije,~/.R/Makevars
es mi forma preferida, ya que NO interfiere con la compilación fuera de R.Ahora, cada compilación que R hace a través de R CMD SHLIB, R CMD COMPILE, R CMD INSTALL, ... se utilizará. Por lo tanto, ya no importa si usa un paquete en línea o local . Continuando con inline ...
Para el resto, seguimos principalmente la "Sección 4.4.1, Búsqueda de puntos de entrada en el código cargado dinámicamente" de "Cómo escribir las extensiones R":
Inicia otra sesión R con R -d gdb.
Compila tu código. por
fun <- cxxfunction(signature(), plugin="Rcpp", verbose=TRUE, body='' int theAnswer = 42; return wrap(theAnswer); '')
yo obtengo
[...]
Compilation argument:
/usr/lib/R/bin/R CMD SHLIB file11673f928501.cpp 2> file11673f928501.cpp.err.txt
ccache g++-4.6 -I/usr/share/R/include -DNDEBUG -I"/usr/local/lib/R/site- library/Rcpp/include" -fpic -g -O0 -Wall -c file11673f928501.cpp -o file11673f928501.o
g++-4.6 -shared -o file11673f928501.so file11673f928501.o -L/usr/local/lib/R/site-library/Rcpp/lib -lRcpp -Wl,-rpath,/usr/local/lib/R/site-library/Rcpp/lib -L/usr/lib/R/lib -lR
- Invoque, por ejemplo,
tempdir()
para ver el directorio temporal, cd a este directorio temporal usado arriba ydyn.load()
el archivo creado arriba:
dyn.load("file11673f928501.so")
Ahora suspenda R enviando una señal de interrupción (en Emacs, una opción simple de un menú desplegable).
En gdb, establece un punto de interrupción. La única asignación anterior se convirtió en la línea 32 para mí, por lo que
break file11673f928501.cpp 32 cont
De vuelta en R, llame a la función:
divertido()
Presto, en el depurador en el punto de quiebre que queríamos:
R> fun() Breakpoint 1, file11673f928501 () at file11673f928501.cpp:32 32 int theAnswer = 42; (gdb)
- Ahora es "solo" depende de ti trabajar gdb a su magia
Ahora, como dije en mi primer intento, todo esto sería más fácil (a mis ojos) a través de un paquete simple que Rcpp.package.skeleton()
puede escribir para usted ya que no tiene que lidiar con directorios y nombres aleatorios. Pero cada uno a su propio ...