1. ホーム
  2. r

[解決済み] RからExcelへの書き込み時のjava.lang.OutOfMemoryErrorの処理について

2023-04-28 09:03:12

質問

質問 xlsx パッケージを使用すると、RからExcelスプレッドシートを読み書きすることができますが、残念ながら、中程度の大きさのスプレッドシートでも java.lang.OutOfMemoryError が発生することがあります。 特に

.jcall("RJavaTools", "Ljava/lang/Object;", "invokeMethod", cl, .でエラーとなりました。

java.lang.OutOfMemoryError: Javaヒープスペース

.jcall("RJavaTools", "Ljava/lang/Object;", "newInstance", .jfindClass(class), ...でエラーとなりました。

java.lang.OutOfMemoryError: GCオーバーヘッドの制限を超えました

(他の関連する例外も起こり得ますが、稀です)。

同様の質問が、スプレッドシートを読み込む際のこのエラーに関してもありました。

R に大きな xlsx ファイルをインポートしますか?

CSVよりもExcelのスプレッドシートをデータ保存媒体として使用する主な利点は、同じファイルに複数のシートを保存できることなので、ここではデータフレームのリストは、1つのワークシートに1つのデータフレームを書き込むと考えます。 このデータセットの例では、40個のデータフレームがあり、それぞれ2列で最大200k行のデータフレームとなっています。 問題にならない程度の大きさに設計されていますが、サイズを変更するために n_sheetsn_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")  

8GB RAM のマシンで 64-bit でこれを実行すると、以下のようになります。 GC overhead limit exceeded を実行中にエラーが発生します。 addDataFrame を初めて実行したときに発生します。

大きなデータセットをExcelスプレッドシートに書き込むには、どのように xlsx ?

どのように解決するのですか?

これは既知の問題です。 http://code.google.com/p/rexcel/issues/detail?id=33

未解決のまま、問題ページ は解決策にリンクしています。 によって ガボール・グローセンディーク がヒープサイズを大きくすることを示唆しています。 java.parameters オプションの前に rJava パッケージがロードされます。 ( rJava の依存関係は xlsx .)

options(java.parameters = "-Xmx1000m")

1000 はJavaヒープのために許容されるRAMのメガバイトの数です; それはあなたが望む任意の値で置き換えることができます。 私の実験によると、より大きな値を指定した方がよく、RAM の権利を完全に使用することができます。 たとえば、私は次のように使って最良の結果を得ました。

options(java.parameters = "-Xmx8000m")

を8GB RAMのマシンで実行します。

ループの各反復でガベージコレクションを要求することで、さらなる改善を得ることができます。 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
  )
}