2013-12-03 3 views
7

Я вижу использование нечетной памяти при использовании присвоения по ссылке группой в data.table. Вот простой пример, чтобы продемонстрировать (пожалуйста, простите за тривиальность примера):Ошибка утечки памяти в таблице данных. Группировка по ссылке

N <- 1e6 
dt <- data.table(id=round(rnorm(N)), value=rnorm(N)) 

gc() 
for (i in seq(100)) { 
    dt[, value := value+1, by="id"] 
} 
gc() 
tables() 

, который производит следующий вывод:

> gc() 
used (Mb) gc trigger (Mb) max used (Mb) 
Ncells 303909 16.3  597831 32.0 407500 21.8 
Vcells 2442853 18.7 3260814 24.9 2689450 20.6 
> for (i in seq(100)) { 
    + dt[, value := value+1, by="id"] 
    + } 
> gc() 
used (Mb) gc trigger (Mb) max used (Mb) 
Ncells 315907 16.9  597831 32.0 407500 21.8 
Vcells 59966825 457.6 73320781 559.4 69633650 531.3 
> tables() 
NAME  NROW MB COLS  KEY 
[1,] dt 1,000,000 16 id,value  
Total: 16MB 

Так о 440MB использованной памяти Vcells после цикла были добавлены. Эта память не учитываются после удаления data.table из памяти:

утечки
> rm(dt) 
> gc() 
used (Mb) gc trigger (Mb) max used (Mb) 
Ncells 320888 17.2  597831 32 407500 21.8 
Vcells 57977069 442.4 77066820 588 69633650 531.3 
> tables() 
No objects of class data.table exist in .GlobalEnv 

памяти, кажется, исчезает при снятии с помощью = ... от задания:

>  gc() 
used (Mb) gc trigger (Mb) max used (Mb) 
Ncells 312955 16.8  597831 32.0 467875 25.0 
Vcells 2458890 18.8 3279586 25.1 2704448 20.7 
>  for (i in seq(100)) { 
    +  dt[, value := value+1] 
    +  } 
>  gc() 
used (Mb) gc trigger (Mb) max used (Mb) 
Ncells 322698 17.3  597831 32.0 467875 25.0 
Vcells 2478772 19.0 5826337 44.5 5139567 39.3 
>  tables() 
NAME  NROW MB COLS  KEY 
[1,] dt 1,000,000 16 id,value  
Total: 16MB 

Подводя итог, два вопроса:

  1. У меня что-то отсутствует или есть утечка памяти?
  2. Если есть утечка памяти, может ли кто-нибудь предложить обходной путь, который позволяет мне использовать назначение по ссылке группой без утечки памяти?

Для справки, вот выход sessionInfo():

R version 3.0.2 (2013-09-25) 
Platform: x86_64-pc-linux-gnu (64-bit) 

locale: 
    [1] LC_CTYPE=en_US.UTF-8  LC_NUMERIC=C    LC_TIME=en_US.UTF-8  LC_COLLATE=en_US.UTF-8  LC_MONETARY=en_US.UTF-8 
[6] LC_MESSAGES=en_US.UTF-8 LC_PAPER=en_US.UTF-8  LC_NAME=C     LC_ADDRESS=C    LC_TELEPHONE=C    
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C  

attached base packages: 
    [1] stats  graphics grDevices utils  datasets methods base  

other attached packages: 
    [1] data.table_1.8.10 

loaded via a namespace (and not attached): 
    [1] tools_3.0.2 
+1

Если есть вероятность ошибки, укажите, какие версии R и 'data.table' вы используете. – flodel

+0

Кажется, произойдет только тогда, когда количество элементов/групп неравномерно. Если вы это сделаете: 'dt <- data.table (id = 1: N, value = rnorm (N))' или любым другим способом, чтобы количество элементов на значение id было постоянным, это не происходит. – Arun

+0

@flodel - спасибо, я отредактирую сообщение, чтобы включить эти данные. –

ответ

6

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 на группировку с не одинаковой длиной группы имеет этот эффект, я до сих пор пытаюсь понять - проверить ссылку в редактировать выше.

+0

Спасибо, что посмотрели под капот и разобрали причину. В дополнение к предложенному выше тесту я провел более грубую проверку силы, свернув количество итераций до 1000 и контролируя использование памяти с использованием средств ОС, а не gc(). Действительно, в конце gc сообщила о 5 ГБ используемой памяти, в то время как ОС сообщила, что R никогда не использовала больше нескольких мегабайт. Спасибо также за полезный указатель на пост R Rеvel, хотя это немного обескураживает, чтобы узнать, что мы не можем доверять числам, выходящим из gc(), особенно учитывая, насколько голоден голод R. –

+2

На самом деле это может быть медленная настоящая утечка. Аналогичная утечка была зафиксирована финализатором в верхней части assign.c, но несколько отчетов с тех пор указывают на то, что проблема остается, но я не дошел до нее (например [здесь] (http://stackoverflow.com/questions/15651515/замедленная память втекания-данных-таблица, когда возвращающий названные-листы-в-J-пытающегося к reshap)). Теперь, глядя снова на свежие глаза, dogroups.c, кажется, не хватает «SETLENGTH» в конце на '.SD' и' I' до их первоначальной длины (размер самой большой группы) перед возвратом. Если это все, я буду пинать себя за то, что не заметил этого раньше! –

+0

@MattDowle, hm, похоже, нет абсолютно никакого увеличения памяти, используемой процессом r-сеанса на моем mac (или любой свободной/активной/неактивной памяти). Это заставляет меня думать, что это не утечка.При этом я не понимал, что просто установка длины назад может обеспечить «правильную» отчетность о памяти. Это блестяще! Я проверю это. – Arun

Смежные вопросы