UPDATE от Мэтта - теперь фиксируется в v1.8.11. От NEWS:
Длинная выдающаяся (как правило, небольшая) утечка памяти при фиксированном группировании. Когда последняя группа меньше самой большой группы, разница в этих размеров не выпускалась. Большинство пользователей запускают запрос группировки один раз и никогда не заметят, но могут возникнуть любые циклические вызовы для группировки (например, при параллельном запуске или бенчмаркинге), # 2648. Добавлен тест.
Большое спасибо vc273, Y T и другие.
С Арун ...
Почему это происходит?
Жаль, что я не встретил this post, прежде чем сидеть на этой проблеме. Тем не менее, хороший опыт обучения. Саймон Урбанек кратко излагает проблему довольно кратко, что это не утечка памяти, но плохая отчетность используемой/освобожденной памяти. У меня было ощущение, что это то, что происходит.
В чем причина, чтобы это произошло в data.table
? Эта часть предназначена для определения части кода от dogroups.c
, ответственной за кажущееся увеличение памяти.
Хорошо, поэтому после некоторых утомительных испытаний, я думаю, мне удалось хотя бы найти, в чем причина этого. Надеюсь, кто-то может помочь мне добраться туда с этой должности. Я пришел к выводу, что это не a утечка памяти.
Краткое объяснение состоит в том, что это, по-видимому, является следствием использования функции SETLENGTH
(из C-интерфейса R) в data.table's dogroups.c
.
В data.table
, при использовании by=...
, например,
set.seed(45)
DT <- data.table(x=sample(3, 12, TRUE), id=rep(3:1, c(2,4,6)))
DT[, list(y=mean(x)), by=id]
В соответствии с id=1
, значение "х" (=c(1,2,1,1,2,3)
) должно быть выбрано. Это означает, что необходимо выделить память для .SD
(все столбцы не в by
) за by
значение.
Для преодоления этого распределения для каждой группы в by
, data.table
выполняет это умен первым выделение .SD
с длиной самой большой группы в by
(который здесь, соответствующая id=1
, длиной 6). Затем мы могли бы, для каждого значения id
, повторно использовать (overly) распределенные таблицы данных. С помощью функции SETLENGTH
мы можем просто отрегулировать длину до текущей группы. Обратите внимание, что при этом здесь фактически не выделяется память, кроме только того, что выделено для самой большой группы.
Но все же кажется странным, что, когда количество элементов для каждой группы в by
имеет одинаковое количество элементов, ничего особенного не происходит в отношении вывода gc()
. Однако, когда они не совпадают, gc()
, похоже, сообщает об увеличении использования в Vcells. Это несмотря на то, что в обоих случаях не выделяется дополнительная память.
Чтобы проиллюстрировать это, я написал C-код, который имитирует использование SETLENGTH
функции в dogroups.c
в `data.table.
// test.c
#include <R.h>
#define USE_RINTERNALS
#include <Rinternals.h>
#include <Rdefines.h>
int sizes[100];
#define SIZEOF(x) sizes[TYPEOF(x)]
// test function - no checks!
SEXP test(SEXP vec, SEXP SD, SEXP lengths)
{
R_len_t i, j;
char before_address[32], after_address[32];
SEXP tmp, ans;
PROTECT(tmp = allocVector(INTSXP, 1));
PROTECT(ans = allocVector(STRSXP, 2));
snprintf(before_address, 32, "%p", (void *)SD);
for (i=0; i<LENGTH(lengths); i++) {
memcpy((char *)DATAPTR(SD), (char *)DATAPTR(vec), INTEGER(lengths)[i] * SIZEOF(tmp));
SETLENGTH(SD, INTEGER(lengths)[i]);
// do some computation here.. ex: mean(SD)
}
snprintf(after_address, 32, "%p", (void *)SD);
SET_STRING_ELT(ans, 0, mkChar(before_address));
SET_STRING_ELT(ans, 1, mkChar(after_address));
UNPROTECT(2);
return(ans);
}
Здесь vec
эквивалентна любой data.table dt
и SD
эквивалентно .SD
и lengths
длина каждой группы. Это всего лишь фиктивная программа. В основном для каждого значения lengths
, скажем n
, первые n
элементов скопированы с vec
на SD
. Затем можно вычислить все, что захочет на этом SD (что здесь не делается). Для наших целей возвращается адрес SD до и после работы с использованием SETLENGTH, чтобы проиллюстрировать, что SETLENGTH не производит копию.
Сохраните этот файл в качестве test.c
, а затем скомпилировать его как следует из терминала:
R CMD SHLIB -o test.so test.c
Теперь откройте новый R-сессию, перейдите в каталог, где test.so
существует, а затем введите:
dyn.load("test.so")
require(data.table)
set.seed(45)
max_len <- as.integer(1e6)
lengths <- as.integer(sample(4:(max_len)/10, max_len/10))
gc()
vec <- 1:max_len
for (i in 1:100) {
SD <- vec[1:max(lengths)]
bla <- .Call("test", vec, SD, lengths)
print(gc())
}
Обратите внимание, что для каждого i
здесь .SD
будет выделено другое место памяти, и здесь репликация осуществляется путем назначения SD
для каждого i
.
При запуске этого кода вы обнаружите, что 1) два возвращаемых значения идентичны для каждого i
, равным address(SD)
, и 2) Vcells used Mb
продолжает увеличиваться. Теперь удалите все переменные из рабочей области с помощью rm(list=ls())
, а затем выполните gc()
, вы обнаружите, что не все память восстанавливается/освобождается.
Исходные:
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 332708 17.8 597831 32.0 467875 25.0
Vcells 1033531 7.9 2327578 17.8 2313676 17.7
После 100 работает:
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 332912 17.8 597831 32.0 467875 25.0
Vcells 2631370 20.1 4202816 32.1 2765872 21.2
После rm(list=ls())
и gc()
:
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 341275 18.3 597831 32.0 467875 25.0
Vcells 2061531 15.8 4202816 32.1 3121469 23.9
Если вы удалите строку SETLENGTH(SD, ...)
из C-кода, и запуска опять же, вы обнаружите, что никаких изменений в Vcells нет.
Теперь, чтобы почему SetLength на группировку с не одинаковой длиной группы имеет этот эффект, я до сих пор пытаюсь понять - проверить ссылку в редактировать выше.
Если есть вероятность ошибки, укажите, какие версии R и 'data.table' вы используете. – flodel
Кажется, произойдет только тогда, когда количество элементов/групп неравномерно. Если вы это сделаете: 'dt <- data.table (id = 1: N, value = rnorm (N))' или любым другим способом, чтобы количество элементов на значение id было постоянным, это не происходит. – Arun
@flodel - спасибо, я отредактирую сообщение, чтобы включить эти данные. –