Отладка (строка за строкой) Rcpp-сгенерированной DLL под Windows
Недавно я экспериментировал с Rcpp (inline) для генерации библиотек DLL, которые выполняют различные задачи на поставляемых R входах. Я хотел бы иметь возможность отлаживать код в этих DLL строк за строкой, учитывая определенный набор R-входов. (я работаю под Windows.)
Чтобы проиллюстрировать, рассмотрим конкретный пример того, что любой человек должен иметь возможность запускать...
Код ниже - это действительно простая функция cxx, которая просто удваивает входной вектор. Однако обратите внимание, что есть дополнительная переменная myvar
, которая несколько раз меняет значение, но не влияет на вывод - это было добавлено, чтобы мы могли видеть, когда процесс отладки работает правильно.
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));
')
После выполнения описанного выше введите команду
getLoadedDLLs()
выводит список DLL в сеансе R. Последний, указанный в списке, должен быть DLL, созданный вышеуказанным процессом, - он имеет случайное временное имя, которое в моем случае
file7e61645c
В столбце "Filename" указано, что cxxfunction поместил эту DLL в папку tempdir()
, которая для меня в настоящее время
C:/Users/TimP/AppData/Local/Temp/RtmpXuxtpa/file7e61645c.dll
Теперь очевидным способом вызова DLL является f0
, как показано ниже:
> f0(c(-7,0.7,77))
$out1
[1] -14.0 1.4 154.0
Но мы можем, конечно, также вызвать DLL напрямую по имени с помощью команды .Call
:
> .Call("file7e61645c",c(-7,0.7,77))
$out1
[1] -14.0 1.4 154.0
Итак, я дошел до того, что я вызываю отдельную DLL напрямую с помощью R-входа (здесь, вектор c(-7,0.7,77)
) и вернул ответ правильно в R.
Я действительно нуждаюсь в том, что это средство для линейной отладки (используя gdb, я полагаю), что позволит мне наблюдать за значением myvar
, установленным в 19, 27, 28, 29, 30 и, наконец, 101 по мере продвижения кода. Приведенный выше пример намеренно настроен таким образом, что вызов DLL ничего не сообщает нам о myvar.
Чтобы уточнить, "условие выигрыша" здесь может наблюдать изменение myvar (видение myvar = 19 будет первым шагом!), не добавляя ничего в тело кода. Это, очевидно, может потребовать внесения изменений в способ компиляции кода (есть ли настройки режима отладки для включения?) Или способ вызова R, но я не знаю с чего начать. Как отмечалось выше, все это основано на Windows.
Заключительное примечание. В моих экспериментах я фактически внес некоторые незначительные изменения в копию cxxfunction, чтобы выходная DLL и код внутри нее получали пользовательское имя и находились в пользовательском каталоге, а не временное имя и местоположение. Но это не влияет на суть вопроса. Я упоминаю это только для того, чтобы подчеркнуть, что довольно легко изменить настройки компиляции, если кто-то дает мне толчок:)
Для полноты установки verbose = TRUE в исходном вызове cxxfunction выше аргумент компиляции имеет следующий вид:
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
У моей адаптированной версии есть аргумент компиляции, идентичный приведенному выше, за исключением того, что строка "file7e61645c" заменяется везде выбором пользователя (например, "testdll" ) и соответствующими файлами, скопированными в более постоянное место.
Заранее спасибо за помощь ребятам:)
Ответы
Ответ 1
Я немного ошеломлен навязчивой идеей Rcpp у пользователей есть inline и cxxfunction()
. Да, это действительно очень полезно, и это, несомненно, привело к принятию Rcpp, поскольку это упрощает эксперименты. Да, это позволило нам использовать более 700 единичных тестов в источниках. Да, я использую его все время, чтобы продемонстрировать примеры здесь, в rcpp-devel list или даже жить в presentations.
Но значит ли это, что мы должны использовать его для каждой задачи? Означает ли это, что у него нет "затрат", таких как рандомизированные имена файлов во временном каталоге и т.д. Pp? В нашей документации Ромен и я утверждали иначе.
Наконец, отладка динамически загруженных R-модулей затруднена. Существует целый раздел (обязательный) Написание R-расширений об этом, и Дуг Бэйтс однажды или дважды разместил учебник о том, как это сделать через ESS и Emacs (хотя я всегда забываю, где он разместил его, когда-то был IIRC на rcpp-devel list).
Редактировать 2012-июль-07:
Вот ваш шаг за шагом:
-
(Преамбула: я использовал gcc и g++ уже много лет, и даже когда добавляю -g, я не всегда поворачиваю -O2 в -O0. Я действительно не уверен, что вам это нужно, но как вы просите об этом...)
-
Установите переменную среды CXXFLAGS в значение -g -O0 -Wall. Существуют многочисленные способы сделать это, некоторые из них зависят от платформы (например, панель управления Windows) и, следовательно, менее универсальны и интересны. Я использую ~/.R/Makevars
для Windows и Unix. Вы могли бы использовать это, или вы могли бы переопределить R-системный RHOME/etc/Makeconf в масштабе всей системы, или вы могли бы использовать Makeconf.site или... См. Полный docs --- но, как я уже сказал, ~/.R/Makevars
является моим предпочтительным способом, как он НЕ мешает компиляции вне R.
-
Теперь выполняется всякая компиляция R через R CMD SHLIB, R CMD COMPILE, R CMD INSTALL,.... Поэтому вам больше не нужно использовать встроенный или локальный пакет. Продолжение с помощью inline...
-
В остальном мы в основном следуем "Раздел 4.4.1 Поиск точек входа в динамически загружаемом коде" из "Написание R-расширений":
-
Запустите еще один сеанс R с R -d gdb.
-
Скомпилируйте свой код. Для
fun <- cxxfunction(signature(), plugin="Rcpp", verbose=TRUE, body='
int theAnswer = 42;
return wrap(theAnswer);
')
Я получаю
[...]
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
- Вызовите, например,
tempdir()
, чтобы увидеть временный каталог, cd, в этот временный каталог, использованный выше, и dyn.load()
файл, построенный выше:
dyn.load("file11673f928501.so")
-
Теперь приостановите R, отправив сигнал разрыва (в Emacs, простой выбор из раскрывающегося списка).
-
В gdb установите точку останова. Одно назначение выше стало для меня линией 32, поэтому
break file11673f928501.cpp 32
cont
-
Вернитесь в R, вызовите функцию:
весело()
-
Presto, в отладчике в точке разрыва мы хотели:
R> fun()
Breakpoint 1, file11673f928501 () at file11673f928501.cpp:32
32 int theAnswer = 42;
(gdb)
- Теперь вам просто нужно "работать" с gdb для его волшебства.
Теперь, как я сказал в первой попытке, все это было бы проще (на моих глазах) через простой пакет, который Rcpp.package.skeleton()
может написать для вас, так как вам не нужно иметь дело со случайными каталогами и именами файлов. Но каждый к своим...