проблема с функцией суммы после редактирования на месте с помощью Rcpp - PullRequest
0 голосов
/ 25 февраля 2019

При изменении значения IntegerVector в Rcpp:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
void test(IntegerVector x) {
  x[5] = 77;
}

После запуска функции test() в R:

x <- 10:1
test(x)
print(x)  #  10  9  8  7  6 77  4  3  2  1
sum(x)  # 55

Функция sum возвращает значение исходного массива10:1.Как я могу решить эту проблему?

Нет проблем при использовании, например, x <- sample(10L).

Ответы [ 2 ]

0 голосов
/ 01 марта 2019

Вот то, что я опубликовал в другом месте относительно этого:

Итак, здесь есть пара вещей, на большинство из которых ссылаются @ltierney и / или @kalibera, но которые, возможно, могут извлечь выгоду изболее конкретные возможные шаблоны кодирования.

Суть вопроса заключается в том, чтобы оперативно изменить полезную нагрузку SEXP путем записи в элементы его памяти с адресом DATAPTR.Это когда-либо нормально делать (да), это может быть безопасно делать без подтверждения / гарантии, что это нормально делать (нет, никогда).А для объектов, созданных в одном и том же фрагменте кода C / C ++, у вас могут быть такие априорные гарантии, но вы не собираетесь использовать объекты, переданные из R.

Если говорить конкретно, я незнаю, что в любое время было бы нормально записать указатель, возвращаемый INTEGER() на SEXP, который живет в R-пространстве, без предварительной проверки MAYBE_SHARED().Если MAYBE_SHARED(x) возвращает FALSE, тогда все в порядке, и вы можете приступить к записи в указатель, точно так же, как это сделал ваш код.

Если MAYBE_SHARED(x) == TRUE, вам нужно выполнить дублирование, выполнить операцию с копией, а затемверни это.Когда вы находитесь в коде C / C ++, на уровне указателей на данные, то вы, ваш код, должны явно вызывать это дублирование, защищать новый дублированный результат и т. Д.

Теперь причина в том, чтоэта конкретная вещь происходит в случае с компактной последовательностью, которая заключается в том, что, если сам R не построен определенным не по умолчанию способом, компактные последовательности ВСЕГДА имеют NAMED(x) == MAXNAMED (то есть 2) с точки создания.Как указал Люк, это может измениться, но в настоящее время это дизайн.Таким образом, даже в ситуации .Call, когда замыкание не заставляет подсчитывать NAMED, компактные последовательности всегда будут нуждаться в дублировании перед встроенной модификацией.И хотя это выбор, который мы могли бы сделать другим в случае компактной последовательности, точка Люка о других ALTREP более важна.

Могут быть ALTREP SEXP, где память, на которую указывает указатель, возвращается, например,INTEGER буквально не записываемая память по той или иной причине.Способ, которым эти классы ALTREP объявят, - это то же самое, что делают компактные последовательности, помечая себя как «IMMUTABLE», то есть, делая MARK_NOT_MUTABLE(x) (который в настоящее время устанавливает NAMED в MAXNAMED, но в будущем-защищен от возможного изменения подсчета ссылок).Это объявляет контракт о том, что SEXP должен дублироваться перед любым кодом, который захватывает указатель данных и записывает в него.

В конечном счете, я согласен, что это действительно странное неожиданное поведение, но из-за неспособности выполнить контракткоторый всегда был там.Возможно, в некоторых случаях в прошлом было безопасно (и я все еще сомневаюсь в этом) игнорировать / проявлять слабость, но с появлением ALTREP теперь всегда нужно следовать по причинам, изложенным в этой теме.

Таким образом, весь весь код, который собирается получить dataptr из ранее существующего (R-уровня) SEXP и записать в него, должен следовать шаблону вдоль (или эквивалентно осторожному):

SEXP awesomefun(SEXP x) 
{
    int nprot = 0;
    if(MAYBE_SHARED(x)) {
        PROTECT(x = duplicate(x)); nprot++; 
    }
    /* do awesome things to x that modify it inline
        protect other things as necessary but always increment nprot when you do, 
        decrement nprot if you ever unprotect */
    if(nprot) UNPROTECT(nprot);
    return x;
}

Любой код, который записывает данные в указатели данных, извлеченные из SEXP, которые он сам не создал (т. Е. Все, что происходит со стороны R) без выполнения этого, уже нарушал контракт C-API, но теперь также является небезопасным ALTREP, так какпоказывал мотивирующий пример.

И, еще раз, помните, компактные последовательности могли вести себя по-разному, так что этот код работал, но другие классы ALTREP (Люк упоминает ALTREPS, поддерживаемые отображенным в файл файлом) не могли,поэтому поведение компактных последовательностей на самом деле не является проблемой.

Я надеюсь, что это полезно и проясняет ситуациюer.

Best

0 голосов
/ 25 февраля 2019

@F.Privé подозрение верно.Это проблема с ALTREP, которую Rcpp пока не поддерживает, cf Rcpp / # 812 и Rcpp / # 906 .Мы можем увидеть это более явно, проверив переменную x:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
void test(IntegerVector x) {
  x[5] = 77;
}

/*** R
x <- 10:1
.Internal(inspect(x))
test(x)
.Internal(inspect(x))
print(x)  #  10  9  8  7  6 77  4  3  2  1
sum(x)  # 55

x <- 10:1
.Internal(inspect(x))
x[6] <- 77L
.Internal(inspect(x))
print(x)  #  10  9  8  7  6 77  4  3  2  1
sum(x)
*/

Первый блок дает:

> x <- 10:1

> .Internal(inspect(x))
@55f79a9d6c58 13 INTSXP g0c0 [NAM(3)]  10 : 1 (compact)

> test(x)

> .Internal(inspect(x))
@55f79a9d6c58 13 INTSXP g0c0 [NAM(3)]  10 : 1 (expanded)

> print(x)  #  10  9  8  7  6 77  4  3  2  1
 [1] 10  9  8  7  6 77  4  3  2  1

> sum(x) # 55
[1] 55

, а второй блок дает:

> x <- 10:1

> .Internal(inspect(x))
@55f79b1f9018 13 INTSXP g0c0 [NAM(3)]  10 : 1 (compact)

> x[6] <- 77L

> .Internal(inspect(x))
@55f7a096e5e8 13 INTSXP g0c4 [NAM(1)] (len=10, tl=0) 10,9,8,7,6,...

> print(x)  #  10  9  8  7  6 77  4  3  2  1
 [1] 10  9  8  7  6 77  4  3  2  1

> sum(x)
[1] 127

Таким образом, после изменения значения в векторе он по-прежнему претендует на значение 10 : 1, для которого sum использует сокращение.См. здесь для дальнейшего чтения (включая ссылки) по ALTREP.

На данный момент единственным решением, по-видимому, является воздержание от изменения аргумента функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...