Обработка java.lang.OutOfMemoryError при записи в Excel из R

Пакет xlsx может использоваться для чтения и записи таблиц Excel из R. К сожалению, даже для умеренно больших таблиц может возникнуть java.lang.OutOfMemoryError. В частности,

## Error in .jcall("RJavaTools", "Ljava/lang/Object;", "invokeMethod", cl,  : 
##   java.lang.OutOfMemoryError: Java heap space  

## Error in .jcall("RJavaTools", "Ljava/lang/Object;", "newInstance", .jfindClass(class),  : 
##   java.lang.OutOfMemoryError: GC overhead limit exceeded 

(Другие связанные исключения также возможны, но реже.)

Аналогичный вопрос был задан относительно этой ошибки при чтении электронных таблиц.

Импорт большого файла xlsx в R?

Основное преимущество использования электронных таблиц Excel в качестве носителя хранения данных по сравнению с CSV заключается в том, что вы можете хранить несколько листов в одном файле, поэтому здесь мы рассмотрим список кадровых фреймов для записи одного кадра данных на рабочий лист. Этот примерный набор данных содержит 40 кадров данных, каждый из которых имеет два столбца длиной до 200 тыс. Строк. Он предназначен для того, чтобы быть достаточно большим, чтобы быть проблематичным, но вы можете изменить размер, изменив n_sheets и n_rows.

library(xlsx)
set.seed(19790801)
n_sheets <- 40
the_data <- replicate(
  n_sheets,
  {
    n_rows <- sample(2e5, 1)
    data.frame(
      x = runif(n_rows),
      y = sample(letters, n_rows, replace = TRUE)
    )
  },
  simplify = FALSE
)
names(the_data) <- paste("Sheet", seq_len(n_sheets))

Естественным способом написания этого файла является создание книги с использованием createWorkbook, затем цикл над каждым кадром данных, вызывающий createSheet и addDataFrame. Наконец, книга может быть записана в файл с помощью saveWorkbook. Я добавил сообщения в цикл, чтобы было легче увидеть, где он падает.

wb <- createWorkbook()  
for(i in seq_along(the_data))
{
  message("Creating sheet", i)
  sheet <- createSheet(wb, sheetName = names(the_data)[i])
  message("Adding data frame", i)
  addDataFrame(the_data[[i]], sheet)
}
saveWorkbook(wb, "test.xlsx")  

Запустив это в 64-разрядном режиме на машине с 8 ГБ ОЗУ, он выдает ошибку GC overhead limit exceeded при запуске addDataFrame в первый раз.

Как написать большие наборы данных в электронные таблицы Excel с помощью xlsx?

Ответ 1

Это известная проблема: http://code.google.com/p/rexcel/issues/detail?id=33

Пока не разрешено, страница проблем ссылки на решение Gabor Grothendieck что размер кучи должен быть увеличен установкой параметра java.parameters до загрузки пакета rJava. (rJava - зависимость от xlsx.)

options(java.parameters = "-Xmx1000m")

Значение 1000 - это количество мегабайт ОЗУ, чтобы разрешить кучу Java; его можно заменить на любое значение, которое вам нравится. Мои эксперименты с этим предполагают, что более высокие значения лучше, и вы можете с радостью использовать свое полное право на ОЗУ. Например, я получил наилучшие результаты, используя:

options(java.parameters = "-Xmx8000m")

на машине с 8 ГБ оперативной памяти.

Дальнейшее улучшение можно получить, запросив сбор мусора на каждой итерации цикла. Как отмечено @gjabel, сборка мусора R может быть выполнена с помощью gc(). Мы можем определить функцию сбора мусора Java, которая вызывает метод Java System.gc():

jgc <- function()
{
  .jcall("java/lang/System", method = "gc")
}    

Затем цикл можно обновить до:

for(i in seq_along(the_data))
{
  gc()
  jgc()
  message("Creating sheet", i)
  sheet <- createSheet(wb, sheetName = names(the_data)[i])
  message("Adding data frame", i)
  addDataFrame(the_data[[i]], sheet)
}

При обоих этих исправлениях код до тех пор, пока не выкинул ошибку, пробежал до i = 29.

Один из методов, который я пробовал безуспешно, заключался в использовании write.xlsx2 для записи содержимого в файл на каждой итерации. Это было медленнее, чем другой код, и он упал на 10-ю итерацию (но по крайней мере часть содержимого была записана в файл).

for(i in seq_along(the_data))
{
  message("Writing sheet", i)
  write.xlsx2(
    the_data[[i]], 
    "test.xlsx", 
    sheetName = names(the_data)[i], 
    append    = i > 1
  )
}

Ответ 2

Основываясь на ответе @richie-cotton, я обнаружил, что добавление gc() к функции jgc обеспечило низкое потребление ЦП.

jgc <- function()
{
  gc()
  .jcall("java/lang/System", method = "gc")
}    

Мой предыдущий цикл for все еще боролся с исходной функцией jgc, но с дополнительной командой я больше не запускаюсь в сообщении об ошибке GC overhead limit exceeded.

Ответ 3

Решение для вышеуказанной ошибки: Пожалуйста, используйте приведенный ниже r-код:

открепление (пакет: XLSX) отсоединять (пакет: XLConnect) библиотека (openxlsx)

И попробуйте снова импортировать файл, и вы не получите никакой ошибки, поскольку он работает для меня.

Ответ 4

Вы также можете использовать gc() внутри цикла, если вы пишете подряд за строкой. gc() означает сбор мусора. gc() может использоваться в любом случае проблемы с памятью.