Java.lang.OutOfMemoryError afhandelen bij het schrijven naar Excel vanuit R

Het pakket xlsxkan worden gebruikt om Excel-spreadsheets van R te lezen en te schrijven. Helaas kan zelfs voor redelijk grote spreadsheets java.lang.OutOfMemoryErroroptreden. In het bijzonder,

Fout in .jcall(“RJavaTools”, “Ljava/lang/Object;”, “invokeMethod”, cl, :
java.lang.OutOfMemoryError: Java-heapruimte

Fout in .jcall(“RJavaTools”, “Ljava/lang/Object;”, “newInstance”, .jfindClass(class), :
java.lang.OutOfMemoryError: GC-overheadlimiet overschreden

(Andere gerelateerde uitzonderingen zijn ook mogelijk, maar zeldzamer.)

Er is een soortgelijke vraag gesteld over deze fout bij het lezen van spreadsheets.

Importeer je een groot xlsx-bestand in R?

Het belangrijkste voordeel van het gebruik van Excel-spreadsheets als een gegevensopslagmedium ten opzichte van CSV is dat u meerdere bladen in hetzelfde bestand kunt opslaan, dus hier beschouwen we een lijst met gegevensframes die één gegevensframe per werkblad moet schrijven. Deze voorbeeldgegevensset bevat 40 gegevensframes, elk met twee kolommen van maximaal 200k rijen. Het is ontworpen om groot genoeg te zijn om problematisch te zijn, maar u kunt de grootte wijzigen door n_sheetsen n_rowste wijzigen.

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))

De natuurlijke methode om dit naar een bestand te schrijven, is door een werkmap te maken met createWorkbooken loop vervolgens over elk dataframe met de aanroep van createSheeten addDataFrame. Ten slotte kan de werkmap naar een bestand worden geschreven met behulp van saveWorkbook. Ik heb berichten aan de lus toegevoegd om het gemakkelijker te maken te zien waar deze omvalt.

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")  

Als je dit in 64-bit uitvoert op een machine met 8 GB RAM, krijg je de fout GC overhead limit exceededterwijl addDataFramevoor de eerste keer wordt uitgevoerd.

Hoe schrijf ik grote datasets naar Excel-spreadsheets met xlsx?


Antwoord 1, autoriteit 100%

Dit is een bekend probleem:
http://code.google.com/p/rexcel/issues/detail ?id=33

Hoewel onopgelost, linkt de probleempagina naar een oplossing door Gabor Grothendiecksuggereert dat de heapgrootte moet worden vergroot door de java.parametersoptie voordat het rJavapakket wordt geladen. (rJavais een afhankelijkheid van xlsx.)

options(java.parameters = "-Xmx1000m")

De waarde 1000is het aantal megabytes RAM voor de Java-heap; het kan worden vervangen door elke gewenste waarde. Mijn experimenten hiermee suggereren dat grotere waarden beter zijn, en dat je met plezier je volledige RAM-rechten kunt gebruiken. Ik kreeg bijvoorbeeld de beste resultaten met:

options(java.parameters = "-Xmx8000m")

op de machine met 8 GB RAM.

Een verdere verbetering kan worden verkregen door een garbagecollection aan te vragen in elke iteratie van de lus. Zoals opgemerkt door @gjabel, kan R garbage collection worden uitgevoerd met gc(). We kunnen een Java-garbagecollectiefunctie definiëren die de Java System.gc()methode:

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

Vervolgens kan de lus worden bijgewerkt naar:

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)
}

Met beide codecorrecties liep de code tot i = 29voordat er een fout werd gegenereerd.

Een techniek die ik zonder succes heb geprobeerd, was om write.xlsx2te gebruiken om de inhoud bij elke iteratie naar een bestand te schrijven. Dit was langzamer dan de andere code en viel om bij de 10e iteratie (maar ten minste een deel van de inhoud werd naar een bestand geschreven).

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
  )
}

Antwoord 2, autoriteit 9%

Voortbouwend op het antwoord van @richie-cotton, merkte ik dat het toevoegen van gc()aan de functie jgchet CPU-gebruik laag hield.

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

Mijn vorige for-lus worstelde nog steeds met de originele jgc-functie, maar met een extra commando loop ik niet langer tegen GC overhead limit exceededfoutmelding.


Antwoord 3

Oplossing voor de bovenstaande fout:
Gebruik de onderstaande r – code:

detach(package:xlsx)
detach(package:XLConnect)
library(openxlsx)

En probeer het bestand opnieuw te importeren en je zult geen foutmelding krijgen omdat het voor mij werkt.


Antwoord 4

Herstart R en voeg, voordat u de R-pakketten laadt, in:

options(java.parameters = "-Xmx2048m")  

of

options(java.parameters = "-Xmx8000m")

Antwoord 5

Je kunt ook gc() in de lus gebruiken als je rij voor rij schrijft. gc() staat voor garbage collection. gc() kan in elk geval van geheugenproblemen worden gebruikt.


Antwoord 6

Ik had problemen met write.xlsx()in plaats van te lezen…. maar realiseerde me toen dat ik per ongeluk 32bit R had gebruikt. Door het om te wisselen naar 64bit is het probleem opgelost.

Other episodes