-
nickleb
- Гуру
- Сообщения: 961
- Зарегистрирован: 22 май 2010, 20:20
- Репутация: 152
R Cran: «Ошибка: не могу разместить вектор размером…»
Здравствуйте. R Cran не может разместить в памяти вектор:
Код: Выделить всё
> tpot<-ncvar_get(ncin,"THETA")
Ошибка: не могу разместить вектор размером 197.8 Mb
Windows XP, RAM 4 Gb
Что делать?
-
rhot
- Гуру
- Сообщения: 1727
- Зарегистрирован: 25 янв 2011, 17:50
- Статьи: 1
- Репутация: 194
- Ваше звание: доктор
- Откуда: Архангельск
Re: R Cran: «Ошибка: не могу разместить вектор размером…»
Сообщение
rhot » 10 апр 2015, 17:37
memory.limit()
___________(¯`·.¸(¯`·.¸ Scientia potentia est _/ {SILVA}:::{FOSS}:::{GIS} _ Знание сила ¸.·´¯)¸.·´¯)___________
-
nickleb
- Гуру
- Сообщения: 961
- Зарегистрирован: 22 май 2010, 20:20
- Репутация: 152
Re: R Cran: «Ошибка: не могу разместить вектор размером…»
Сообщение
nickleb » 10 апр 2015, 18:24
rhot писал(а):memory.limit()
-
nickleb
- Гуру
- Сообщения: 961
- Зарегистрирован: 22 май 2010, 20:20
- Репутация: 152
Re: R Cran: «Ошибка: не могу разместить вектор размером…»
Сообщение
nickleb » 10 апр 2015, 19:38
… а package bigmemory?…
-
rhot
- Гуру
- Сообщения: 1727
- Зарегистрирован: 25 янв 2011, 17:50
- Статьи: 1
- Репутация: 194
- Ваше звание: доктор
- Откуда: Архангельск
Re: R Cran: «Ошибка: не могу разместить вектор размером…»
Сообщение
rhot » 10 апр 2015, 19:40
установить memory.limit > 3000 не пробовали?
___________(¯`·.¸(¯`·.¸ Scientia potentia est _/ {SILVA}:::{FOSS}:::{GIS} _ Знание сила ¸.·´¯)¸.·´¯)___________
-
gamm
- Гуру
- Сообщения: 3934
- Зарегистрирован: 15 окт 2010, 08:33
- Репутация: 1029
- Ваше звание: программист
- Откуда: Казань
Re: R Cran: «Ошибка: не могу разместить вектор размером…»
Сообщение
gamm » 10 апр 2015, 21:21
nickleb писал(а):Что делать?
поставить нормальную х64 систему (Win7) и 16-32 Гб памяти. Выдавать периодически gc() и удалять ненужные объекты. Не использовать data.frame() для больших данных, ограничиться matrix(). Работать с большими объемами данных вне R, готовя выборки. Использовать подкачу данных по мере необходимости, разбив задачу на части (для некоторых случаев есть соответствующие пакеты). Но главное — поставить побольше памяти.
Хранение объектов
Для мониторинга используемой памяти можно обращаться к выводу функции gc()
(основной её функционал — сбор мусора, garbage collection).
## used (Mb) gc trigger (Mb) max used (Mb)
## Ncells 636223 34.0 1211135 64.7 1211135 64.7
## Vcells 1166455 8.9 8388608 64.0 1819344 13.9
В R есть некоторое различие в том, как хранятся и мониторятся разные объекты, поэтому в выводе gc()
мы и видим две строки. Первая, Ncells
, — это так называемые cons cells (пришедшие в R из Lisp и его особенностей хранения объектов в памяти). В R cons cells используются для хранения объектов, относящихся к самому языку: окружения, замыкания, парные списки, названия объектов и так далее. Полную статистику по этим объектам можно посмотреть с помощью функции memory.profile()
.
Вторая строчка вывода gc()
, Vcells
, — это, собственно, статистика используемых пользователем объектов. Правда, на данный момент это не выделяемая память (heaps
), а блоки по 8 байт.
Группа used (Mb)
отображает, сколько ячеек было использовано для хранения всех текущих объектов, а также объём задействованной памяти в мегабайтах. Группа колонок gc trigger (Mb)
отражает, при каком количестве ячеек и памяти будет запущен сборщик мусора, группа max used (Mb)
— статистики на момент предыдущего запуска сборщика.
При интерпретации вывода gc()
в первую очередь надо смотреть на первую колонку (Mb)
, так как именно сумма значений обеих строк по этой колонке отражает состояние на текущий момент. Притом наибольший фокус должен быть, конечно, на строку Vcells
: по сравнению с пользовательскими объектами объекты Ncells
намного меньше, и их сложно увеличить до сколько-нибудь значимого объёма.
Использовать функцию gc()
только для мониторинга памяти может быть несколько неудобно по ряду причин. В первую очередь, это всё же функция для вызова сборщика мусора, поэтому её выполнение может быть медленным в некоторых процедурах (в частности, не надо gc()
использовать в циклах). Во-вторых, её данные могут быть несколько неточны, так как это оценка изнутри. Поэтому оценивать загруженность оперативной памяти лучше средствами операционной системы.
Размер объекта
При работе с большими объектами нередко возникает необходимость контролировать, какой объём оперативной памяти он занимает. Наиболее полезные инструменты здесь — функция object.size()
базового пакета utils
и функция obj_size()
пакета lobstr
. Обе функции отдают размер в байтах:
## 8000048 bytes
## 8,000,048 B
Результат функции object.size()
можно представить в нужном формате с помощью format()
и аргумента units
, который позволяет задать единицу измерения (“b”, “Kb”, “Mb”, “Gb” и проч.) и стандарт представления единицы измерения (standard
с допустимыми значениями “legacy”, “IEC”, “SI” и “auto”). Стандарты legacy
и IEC
считают байты по степеням двойки, то есть 1024 байта равны 1 килобайту. Стандарт SI
опирается на десятичную шкалу, в которой 1 килобайт равен 1000 байтам.
## [1] "7.6 Mb"
## [1] "7.6 MiB"
## [1] "8 MB"
Оценки размеров объектов в памяти с помощью функции object.size()
достаточно грубые, так как, во-первых, не учитывают ни окружение, которому принадлежит объект, ни название объекта, ни ситуации, когда части сложного объекта делятся с другими объектами (характерно для списков и таблиц, подробнее ниже). Во-вторых, размеры объектов могут несколько различаться на 64- и 32-битных системах.
Лимиты памяти
R хранит объекты в оперативной памяти. Как следствие, это накладывает некоторые ограничения на размер объектов в зависимости от операционной системы и её архитектуры, а также архитектуры приложения. Стоит отметить, что использование 32-битных версий R на 64-битных ОС также имеет свои особенности (в частности, на Windows), однако это достаточно редкое сочетание, и мы его не будем рассматривать.
Пользователи чаще всего сталкиваются с таким сообщением об ошибке: cannot allocate vector of size XXX
(не могу разместить вектор размером XXX
). Размер, который упоминается в этой ошибке, — это необходимое количество памяти сверх уже выделенного (а не сколько всего требуется памяти). В некоторых случаях это ошибка возникает из-за того, что операционная система не может выделить столько памяти под процесс (например, из-за высокой фрагментированности свободной памяти). В основном же эта ошибка возникает, когда размер объекта превышает (2^{31} — 1) байтов по какому-либо измерению (длина вектора, количество элементов в матрице, длина колонки и проч).
Для *nix-систем количество памяти, выделяемое под процесс, зависит от разрядности системы: для 32-битных систем под процесс выделяется 3 Gb (если не учитывать резервируемое под ядро), а в 64-битной системе уже намного больше — 128 Tb.
В Windows ситуация несколько сложнее. Во-первых, в 32-битных системах под процесс выделяется 2 Gb (при определенных настройках — 3 Gb). В 64-битных системах больше, но с ограничением в 8 Tb. Из-за того, что Windows ограничивает размер выделяемой памяти под процесс, в R есть инструменты управлениями этими лимитами — функции memory.size()
и memory.limit()
. Первая возвращает текущий или максимальный лимит памяти. Вторая возвращает текущий лимит или же увеличивает лимит памяти. Обе функции могут быть использованы только в Windows, в *nix-системах они будут отдавать Inf
.
Понимать, какие есть лимиты памяти в операционной системе и в приложении, полезно, но рядовой пользователь с ними редко сталкивается. Скорее, возникновение ошибки cannot allocate vector of size XXX
свидетельствует либо о плохо или с ошибкой написанном коде (что чаще), либо же о плохо построенном процессе работы с данными.
Трассировка объектов
Для отслеживания адреса объекта в памяти и его изменения при различных операциях используется функция tracemem()
:
## [1] "<0x561be4411450>"
При изменении элемента объекта адрес меняется и изменение адресов отображается сразу, без повторного вызова tracemem()
:
## tracemem[0x561be4411450 -> 0x561be817c1c8]: eval eval withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
Для отключения трассировки можно воспользоваться функцией untracemem()
, только для начала сохраним актуальный адрес объекта:
# сохраняем
prev_address <- tracemem(x)
# отключаем трассировку и изменяем
untracemem(x)
x[3] <- 99
При желании после отключения трассировки можно сделать какую-нибудь новую операцию (или несколько) и c помощью retracemem()
отследить изменение адреса с последнего известного до текущего:
# добавляем ещё одно изменение
x <- 9
# смотрим изменение адреса с предыдущего известного
retracemem(x, previous = prev_address)
## tracemem[<0x561be817c1c8> -> 0x561be7263ea8]: eval eval withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
Функция tracemem()
, использует вывод C-функции duplicate()
, которая вызывается при копировании объектов. Как следствие, tracemem()
бессмысленна или неприменима к функциям, окружениям и прочим объектам, которые нельзя дуплицировать.
Помимо tracemem()
для получения адреса объекта можно использовать более информативный вызов внутренней функции inspect()
, которая также возвращает адрес в памяти и C-тип данных, а также функцию lobstr::obj_addr()
, которая является оболочкой над inspect()
:
## [1] "<0x561be7263ea8>"
## @561be7263ea8 14 REALSXP g0c1 [REF(3),TR] (len=1, tl=0) 9
## [1] "0x561be7263ea8"
Хранение разных типов данных
Для лучшего понимания основных типов данных в R рассмотрим их с точки зрения хранения в памяти. Это можно сделать как с помощью tracemem()
, так и с помощью функций ref()
, obj_addr()
и obj_addrs()
пакета lobstr
, которые возвращают более детальную информацию об объекте и его элементах.
Векторы
Векторы элементов атомарных типов, за исключением строкового, имеют только адрес начала вектора. Со строковыми значениями ситуация несколько иная: в R используется так называемый глобальный кэш строковых значений (global string pooling). Это значит, что каждое новое строковое значение получает свой адрес и попадает в кэш, все же дальнейшие обращения к этой строке используют закэшированное значение (то есть обращаются по тому же адресу). Таким образом, вектор строковых значений будет содержать значения из кэша и какое-то количество ссылок на эти значения, которых может быть больше, чем уникальных строковых значений:
x_char <- sample(c('ab', 'ac', 'cb'), 7, replace = TRUE)
x_char <- sort(x_char)
print(x_char)
## [1] "ab" "ac" "ac" "ac" "cb" "cb" "cb"
lobstr::ref(x_char, character = TRUE)
## █ [1:0x561be66ea228] <chr>
## ├─[2:0x561be68c0458] <string: "ab">
## ├─[3:0x561be68c0500] <string: "ac">
## ├─[3:0x561be68c0500]
## ├─[3:0x561be68c0500]
## ├─[4:0x561be27fbf98] <string: "cb">
## ├─[4:0x561be27fbf98]
## └─[4:0x561be27fbf98]
## [1] "0x561be68c0458" "0x561be68c0500" "0x561be68c0500" "0x561be68c0500"
## [5] "0x561be27fbf98" "0x561be27fbf98" "0x561be27fbf98"
Такая особенность хранения строк может давать ощутимый прирост в оптимизации хранения и использования данных. Помимо очевидного хранения только уникальных строковых значений вместо каждого вхождения, строки можно использовать как альтернативы числовым значениям. В частности, когда в качестве идентификатора наблюдения или еще какого-либо объекта (тех же штрихкодов) используется большое число, можно хранить эти идентификаторы как строковые. За счёт глобального кэша это будет почти так же эффективно, как и с числовыми идентификаторами, так как указатели на адрес в кэше в любом случае числовые. При этом, по опыту, строковое хранение идентификаторов безопаснее для использования (как минимум, нет опасности бесконтрольного перехода в академическую нотацию и использование плавающей точки или же переполнения типа).
Списки и таблицы
Списки и таблицы являются коллекциями ссылок на векторы, каждый из которых имеет собственный адрес. Соответственно, можно отдельно отслеживать изменения подсписков или колонок в таблице. Например, когда мы добавляем новый элемент в список, видно, что меняется адрес всего списка, но адреса уже существующих подсписков остаются такими же:
# создадим список и посмотрим его адрес и адреса его подсписков
my_list <- list(e1 = 1:3, e2 = letters[1:5], e3 = month.abb[1])
lobstr::ref(my_list)
## █ [1:0x561be759a708] <named list>
## ├─e1 = [2:0x561be61d8a20] <int>
## ├─e2 = [3:0x561be7fd24f8] <chr>
## └─e3 = [4:0x561be6321ad8] <chr>
# создаём новый подсписок
my_list$e4 <- sample(5)
lobstr::ref(my_list)
## █ [1:0x561be7e33108] <named list>
## ├─e1 = [2:0x561be61d8a20] <int>
## ├─e2 = [3:0x561be7fd24f8] <chr>
## ├─e3 = [4:0x561be6321ad8] <chr>
## └─e4 = [5:0x561be7e331f8] <int>
Аналогично с таблицами:
# создаём таблицу и смотрим её адреса
my_df <- data.frame(col1 = 1:5, col2 = letters[1:5], col3 = month.abb[1:5], stringsAsFactors = FALSE)
lobstr::ref(my_df)
## █ [1:0x561be7fe6f18] <df[,3]>
## ├─col1 = [2:0x561be5dc3f20] <int>
## ├─col2 = [3:0x561be809a998] <chr>
## └─col3 = [4:0x561be809a928] <chr>
# создаём новую колонку и смотрим изменения
my_df$col4 <- sample(5)
lobstr::ref(my_df)
## █ [1:0x561be57f2588] <df[,4]>
## ├─col1 = [2:0x561be5dc3f20] <int>
## ├─col2 = [3:0x561be809a998] <chr>
## ├─col3 = [4:0x561be809a928] <chr>
## └─col4 = [5:0x561be57f26c8] <int>
Подходы к копированию и изменению объектов
Copy-on-modify
В R реализован вызов по значению (call-by-value
). Правда, в несколько нетрадиционном виде: его корректнее было бы назвать вызовом по соиспользованию (call-by-sharing
), в R Internals встречается формулировка call by value illusion
. Смысл заключается в следующем: когда мы создаём копию объекта, то первоначально адреса исходного объекта и копии совпадают:
# создаём объект
x <- 1:5
# создаём его копию
y <- x
# смотрим адреса обоих объектов
tracemem(x)
## [1] "<0x561be52e0a00>"
## [1] "<0x561be52e0a00>"
То есть до какого-то момента объект и его копия используют один и тот же адрес в памяти. По-настоящему копия создаётся в тот момент, когда над копией производится какое-то действие:
## tracemem[0x561be52e0a00 -> 0x561be74d9da8]: eval eval withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
Одновременно с механизмом copy-on-modify
проявляется и обратная сторона процесса: изменение объекта всегда сопровождается его копированием, в некоторых случаях изменение может быть даже скрыто от пользователя. Например, когда мы хотим добавить к вектору целых значений numeric-значение.
## [1] "integer"
## [1] "<0x561be733eae0>"
## tracemem[0x561be733eae0 -> 0x561be7156318]: eval eval withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
## tracemem[0x561be7156318 -> 0x561be756c4c8]: eval eval withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
## [1] "numeric"
Как мы видим, сначала за счёт неявного преобразования типов (integer
в numeric
) происходит копирование и изменение объекта, а потом — происходит добавление нового элемента, также с копированием и созданием объекта новой длины.
Shallow copy
Полное копирование объекта при его изменении (deep copy
) — это достаточно дорогостоящая процедура, особенно при больших объемах данных, так как фактически требует двух- или даже трёхкратного объёма памяти объекта для проведения минимальных операций. В частности, из-за такого механизма R долгое время считался очень медленным языком. Тем не менее, в версии 3.1.0 был введён новый механизм копирования — shallow copy
, поверхностное копирование. В первую очередь это касается списков и таблиц.
Основная идея механизма — копировать только тот элемент (подсписок или колонку), которая реально изменяется. Остальные элементы оставлять неизменными до тех пор, пока не возникнет какая-то необходимость в их изменении. По сути, механизм shallow copy
— сочетание особенности хранения списков в памяти и механизма copy-on-modify
.
# создаём таблицу и смотрим её адреса
my_df <- data.frame(col1 = 1:5, col2 = letters[1:5], col3 = month.abb[1:5], stringsAsFactors = FALSE)
lobstr::ref(my_df)
## █ [1:0x561be7fdde68] <df[,3]>
## ├─col1 = [2:0x561be6ac3310] <int>
## ├─col2 = [3:0x561be551a3f8] <chr>
## └─col3 = [4:0x561be56ff338] <chr>
# создаём копию таблицы
my_df_copy <- my_df
lobstr::ref(my_df_copy)
## █ [1:0x561be7fdde68] <df[,3]>
## ├─col1 = [2:0x561be6ac3310] <int>
## ├─col2 = [3:0x561be551a3f8] <chr>
## └─col3 = [4:0x561be56ff338] <chr>
# создаём новую колонку и смотрим изменения
my_df_copy$col1 <- my_df_copy$col1 * 2
my_df_copy$col4 <- sample(5)
lobstr::ref(my_df_copy)
## █ [1:0x561be80abfa8] <df[,4]>
## ├─col1 = [2:0x561be704b568] <dbl>
## ├─col2 = [3:0x561be551a3f8] <chr>
## ├─col3 = [4:0x561be56ff338] <chr>
## └─col4 = [5:0x561be80ac098] <int>
При копировании таблицы адреса её и копии, а также колонок в таблице и копии совпадали. После изменения первой колонки и создания четвёртой колонки в копии адрес копии и изменённых/созданных колонок изменился. При этом вторая и третья колонки, которые мы не меняли, остались по тому же адресу, что и в исходной таблице, в совместном использовании.
Как следствие, логичным выводом будет, что если мы изменим строку, то изменятся все колонки. То есть это очень невыгодная операция, особенно при больших размерах таблицы: обновлять надо будет полностью всю таблицу, а не только изменяемые колонки:
# смотрим адреса таблицы и колонок
lobstr::ref(my_df_copy)
## █ [1:0x561be80abfa8] <df[,4]>
## ├─col1 = [2:0x561be704b568] <dbl>
## ├─col2 = [3:0x561be551a3f8] <chr>
## ├─col3 = [4:0x561be56ff338] <chr>
## └─col4 = [5:0x561be80ac098] <int>
# переприсваиваем в строку её же значение (формально ничего не меняется)
my_df_copy[2, ] <- my_df_copy[2, ]
# смотрим ещё раз адреса
lobstr::ref(my_df_copy)
## █ [1:0x561be7e57fd8] <df[,4]>
## ├─col1 = [2:0x561be7fbd9d8] <dbl>
## ├─col2 = [3:0x561be7fbd888] <chr>
## ├─col3 = [4:0x561be7fbd738] <chr>
## └─col4 = [5:0x561be7e57da8] <int>
Полное обновление таблицы при добавлении строки — одна из причин, почему неэффективны циклы, использующие rbind()
в теле цикла.
Modify-in-place
Помимо copy-on-modify
, также есть возможность модификации объектов без копирования, на месте, modify-in-place
. Подобные модификации возможны для векторов, а также для окружений — при изменении объекта внутри окружения адрес самого окружения не меняется:
# создаём новое окружение и один объект в нём
my_env <- new.env()
my_env$a <- 1:5
lobstr::ref(my_env)
## █ [1:0x561be6007210] <env>
## └─a = [2:0x561be5fb87b8] <int>
# меняем объект
my_env$a[6] <- 99
lobstr::ref(my_env)
## █ [1:0x561be6007210] <env>
## └─a = [2:0x561be80cd458] <dbl>
На практике лучше всего (да и полезней) изменение на месте без копирования реализовано в пакете data.table
. При изменении колонки вся таблица не копируется, а меняется только колонка или даже её часть.
## █ [1:0x561be7e622e0] <data.table[,3]>
## ├─col1 = [2:0x561be720e558] <int>
## ├─col2 = [3:0x561be756c618] <chr>
## └─col3 = [4:0x561be756cdf8] <chr>
my_dt[, col1 := col1 * 2]
lobstr::ref(my_dt)
## █ [1:0x561be7e622e0] <data.table[,3]>
## ├─col1 = [2:0x561be7fbcf58] <dbl>
## ├─col2 = [3:0x561be756c618] <chr>
## └─col3 = [4:0x561be756cdf8] <chr>
Однако к data.table
также применима логика copy-on-modify
, что вызывает казусы: когда стандартным путём создаётся и изменяется копия таблицы, изменения также проявляются и в исходной таблице:
# создаём таблицу и её копию
my_dt <- data.table(col1 = 1:5, col2 = letters[1:5], col3 = month.abb[1:5])
my_dt_copy <- my_dt
# смотрим их адреса
lobstr::ref(my_dt)
## █ [1:0x561be7489b50] <data.table[,3]>
## ├─col1 = [2:0x561be7ee2918] <int>
## ├─col2 = [3:0x561be5801eb8] <chr>
## └─col3 = [4:0x561be5801d68] <chr>
## █ [1:0x561be7489b50] <data.table[,3]>
## ├─col1 = [2:0x561be7ee2918] <int>
## ├─col2 = [3:0x561be5801eb8] <chr>
## └─col3 = [4:0x561be5801d68] <chr>
# изменяем колонку в копии таблицы
my_dt_copy[, col1 := col1 * 2]
# смотрим результат
my_dt_copy
## col1 col2 col3
## 1: 2 a Jan
## 2: 4 b Feb
## 3: 6 c Mar
## 4: 8 d Apr
## 5: 10 e May
## col1 col2 col3
## 1: 2 a Jan
## 2: 4 b Feb
## 3: 6 c Mar
## 4: 8 d Apr
## 5: 10 e May
# проверяем на тождество
all.equal(my_dt, my_dt_copy)
## [1] TRUE
# ещё раз смотрим адреса
lobstr::ref(my_dt)
## █ [1:0x561be7489b50] <data.table[,3]>
## ├─col1 = [2:0x561be74804a8] <dbl>
## ├─col2 = [3:0x561be5801eb8] <chr>
## └─col3 = [4:0x561be5801d68] <chr>
## █ [1:0x561be7489b50] <data.table[,3]>
## ├─col1 = [2:0x561be74804a8] <dbl>
## ├─col2 = [3:0x561be5801eb8] <chr>
## └─col3 = [4:0x561be5801d68] <chr>
Для того чтобы избегать подобных ситуаций, следует пользоваться явной функцией копирования data-table
объектов — data.table::copy()
.
Сборщик мусора
Сборщик мусора в R использует классический алгоритм — подсчёт ссылок на объекты в памяти. То есть подсчитывается, сколько ссылок есть на объект при каждом изменении, и если количество ссылок равно нулю, то объект может быть удалён, а занимаемая им память — возвращена операционной системе.
При этом сборщик мусора работает “поколениями”: исходя из предположения, что на новые объекты ссылки могут чаще меняться, чем на старые, новые объекты просматриваются сборщиком мусора чаще. К сожалению, такой механизм вынужден просматривать весь кэш строковых значений, что в некоторых ситуациях может замедлять работу сборщика мусора.
Сборщик мусора в R работает в автоматическом режиме, однако в некоторых случаях есть смысл форсировать процесс и вызвать сборщик вручную. Основная функция вызова сборщика мусора — уже описанная ранее функция gc()
. Аргумент full = TRUE
позволяет игнорировать поколения объектов, в результате сборщик мусора просматривает все объекты.
Также есть функция gcinfo()
, если её запустить с аргументом verbose = TRUE
, то она присутствует в фоновом режиме во время сессии и при автоматическом срабатывании сборщика мусора сообщает пользователю текущие статистики Ncells/Vcells
. Функции gctorture()
и gctorture2()
используются в очень редких случаях (и в основном разработчиками R), так как форсируют сборщик мусора при каждом размещении объекта в памяти, что существенно замедляет работу.
Помимо сборщика мусора, в R есть возможность вручную запускать финализатор во время выполнения какой-либо операции или же при завершении сессии. Тем не менее, функция reg.finalizer()
используется достаточно редко, и её применяют обычно только к R6-классам.
This tutorial shows how to increase or decrease the memory limit in R.
The tutorial contains two examples for the modification of memory limits. To be more specific, the article consists of the following topics:
So let’s take a look at some R codes in action:
The Starting Point: Running Memory Intense Function with Default Specifications
Let’s assume we want to create a huge vector of randomly distributed values using the rnorm function in R. Then we could use the following R code:
x <- rnorm(4000000000) # Trying to run rnorm function # Error: cannot allocate vector of size 29.8 Gb
Unfortunately, the RStudio console returns the error message: “cannot allocate vector of size 29.8 Gb”.
Even though there is no general solution to this problem, I’ll show in two examples how you might be able to fix this issue. So keep on reading!
Example 1: Garbage Collection Using gc() Function in R
The first possible solution for this problem is provided by the gc function. The gc function causes a garbage collection and prints memory usage statistics.
Let’s do this in R:
gc() # Apply gc() function # used (Mb) gc trigger (Mb) max used (Mb) # Ncells 531230 28.4 1156495 61.8 1156495 61.8 # Vcells 988751 7.6 8388608 64.0 1761543 13.5
Have a look at the previous output of the RStudio console. It shows some memory usage statistics that might be helpful to evaluate your RAM usage.
Perhaps, the call of the gc function solves our memory problems:
x <- rnorm(4000000000) # Trying to run rnorm function # Error: cannot allocate vector of size 29.8 Gb
Unfortunately, this is not the case in this example. So let’s try something else…
Example 2: Increase Memory Limit Using memory.limit() Function
Another solution for the error message: “cannot allocate vector of size X Gb” can be the increasing of the memory limit available to R. First, let’s check the memory limit that is currently specified in R. For this, we simply have to call the memory.limit function as shown below:
memory.limit() # Check currently set limit # [1] 16267
The RStudio console shows that our current memory limit is 16267.
We can also use the memory.limit function to increase (or decrease) memory limits in R. Let’s increase our memory limit to 35000:
memory.limit(size = 35000) # Increase limit # [1] 35000
Now, we can run the rnorm function again:
x <- rnorm(4000000000) # Successfully running rnorm function
Nice, this time it worked (even though it took a very long time to compute)!
Video, Further Resources & Summary
Do you want to learn more about adjusting memory limits? Then you may want to have a look at the following video of my YouTube channel. In the video, I’m explaining the R syntax of this tutorial.
The YouTube video will be added soon.
Note that the examples shown in this tutorial are only two out of many possible solutions for the error message “cannot allocate vector of size X Gb”. Have a look at this thread on Stack Overflow to find additional tips and detailed instructions.
Furthermore, you may want to have a look at the other tutorials on Statistics Globe.
- Clear RStudio Console
- Remove All Objects But One from Workspace in R
- Difference between rm() & rm(list=ls())
- Clean Up Memory in R
- Determine Memory Usage of Data Objects
- What’s the Difference Between the rm & gc Functions?
- R Programming Tutorials
In summary: At this point you should know how to increase or decrease the memory limit in R programming. If you have additional questions, please let me know in the comments. Besides that, don’t forget to subscribe to my email newsletter in order to get updates on the newest tutorials.
-
-
January 28 2013, 16:23
- История
- Cancel
Решила поработать с одним из файлов (формат SPSS) международного исследования PISA (http://www.oecd.org/pisa/pisaproducts/pisa2000/) за 2000 г. Файл с оценками по математическим тестам, большой: включает все страны-участницы, по 3 тысячи с лишним кейсов для каждой страны, свыше 400 переменных, в общем, общим весом свыше 150 мегабайт.
После запуска командной строки:
library(foreign)
pisa<-read.spss(«intstud_math.sav», to.data.frame = TRUE)
R выдал следующее:
Ошибка: не могу разместить вектор размером 498 Kb
Вдобавок: Предупреждения
1: In structure(list(message = as.character(message), call = call), :
Reached total allocation of 1535Mb: see help(memory.size)
Если грузить без добавления to.data.frame, то грузится нормально. Получаем объект класса list. Но тут возникает следующая проблема: мне не нужен файл целиком, нужен только кусок по РФ. Есть ли какой-то способ «вытащить» из list российскую подвыборку? У меня пока получается вытаскивать по одной переменной при помощи двойных квадратных скобок.
Буду признательна за помощь. Есть, конечно, простой способ — залезть в SPSS и там «отмотать», но очень хочется слезть с SPSS.
The “cannot allocate vector of size” error message occurs when there isn’t enough available memory (RAM) capacity to allocate a vector , array, or objects in R. You’ll see this error when you are working with large data sets or managing the allocation of large data objects. The good news this is usually isn’t a code error (like other R errors) but rather a system constrain that you’re going to need to maneuver around.
Why You’re Seeing This R Error
The “cannot allocate vector of size” memory error message occurs when you are creating or loading an extremely large amount of data that takes up a lot of virtual memory usage. When dealing with such large datasets it is possible to actually run out of usable memory. It is most likely to happen when a data set is loaded in from an external source such as a package or another type of file. This is because you cannot control the size of an object contained in a source that you did not create. It is most likely to occur when accessing the function to load or create a vector or other data set. They can also occur with smaller objects if you have an extremely large number of objects in your program.
A List of More Specific Causes of the R Error
The cause of the “cannot allocate vector of size” error message is a virtual memory allocation problem. That being said, here are some specific things to watch out for with this error:
- Memory allocation problems due to other applications open on your machine. The total memory usage from the other applications is consuming the available memory capacity.
- Bumping into the memory size limits for the R application. You can use the memory.limit() function to manage this on Windows systems.
- Sloppy memory allocation practices and data model design in designing your program; look for things like sparsely populated arrays and matrices, excessive large objects, or loading unused packages and code into the R environment.
- Limit vector size, array length, and column definitions to the values that you actually need; this includes being aware of na values, which occupy memory regardless. Consider making performance tradeoffs between holding data in memory and generating values via a function or a code loop.
- Difficulties recycling unused element(s) in R memory management. If you need them, look to move them into storage on disk or other environment.
Ways To Fix This Error
The “cannot allocate vector of size” memory issue error message has several R code solutions. The best thing about these solutions is that none of them is overly complicated, most are a simple single process that is easy to do in your R script.
- The simplest solution is to avoid using overly large objects or excessively large numbers of them in one program or R session, for example try removing unneeded objects from your calculation to better fit within your total allocation of memory.
- When reading in an external file enclose the read() function inside a subset() function resulting in the format of subset(read.(“filename”, header = TRUE), select = c(columns to be kept)) this will reduce the size of individual objects being created by removing unwanted columns.
- You can clear out unneeded objects using the rm() function.
- You can clean out address space using the gc() function.
- You can also test for a limit to the free memory using the memory.limit() and set a higher value with the format of memory.limit(size=number).
- Shut down any programs you are not using to clear up extra space.
Finding the best process to fix this error may take some trial and error but they are not hard or time-consuming. While this error message is not a coding problem it is still not difficult to fix within your R session.