TL;DR
workbook = RubyXL::Parser.parse_buffer(buffer)
workbook.stream
上記コードはStream
とか書いていますが、巨大TEXTファイルを読み取る時みたいに、一行ずつ読んで性能アップすることはないので、かえって遅くなります。
こちらのメソッドは単にWebサーバーに送信されたExcelファイルを、ファイルハンドラーのままで処理可能にして、明示的に一時ファイルを保存しなくて済むように作られています。
オーソドックスなworkbook = RubyXL::Parser.parse("path/to/Excel/file.xlsx")
の方が断然早いですが、そもそもRubyXLやPythonのOpenPyXLのようなサードパーティーモジュールは巨大Excelファイルに不向きです。
巨大Excelファイルをプログラム的に処理するには、現段階Excelの中にVBAを書くしかないと思います。
実験
巨大Excelの作成
require 'benchmark'
require 'rubyXL'
file_path = 'test.xlsx'
# Create a test file
COL_SIZE = 50
ROW_SIZE = 5000
def init_xlsx(col_num, row_num)
workbook = RubyXL::Workbook.new
sheet = workbook[0]
add_cells(sheet, row_num, col_num)
workbook
end
def add_cells(sheet, col_num, row_num)
(0...row_num).each_with_index do |row, i|
sheet.add_cell(row, 0, i)
(1...col_num).each_with_index do |col, j|
sheet.add_cell(row, col, "#{i}:#{j}")
end
end
end
Benchmark.bm do |x|
x.report("Create Xlsx:") do
workbook = init_xlsx(COL_SIZE, ROW_SIZE)
workbook.write(file_path)
puts "#{COL_SIZE}*#{ROW_SIZE}"
end
end
Benchmark
user system total real
Create Xlsx:50*5000
4.297000 0.032000 4.329000 ( 6.334323)
実は上記50列*5000行のExcelはたっだの1.3MBしかなくて、Excelファイル的にぜんぜん「巨大」とは言えないです。
Batch
file_path = 'test.xlsx'
Benchmark.bm do |x|
x.report("Batch: ") do
workbook = RubyXL::Parser.parse(file_path)
p workbook[0][0][1].value
p workbook[0][20][10].value
end
end
Benchmark
user system total real
Batch: "0:0"
"20:9"
8.500000 0.140000 8.640000 ( 13.540754)
Stream
file_path = 'test.xlsx'
Benchmark.bm do |x|
x.report("Stream: ") do
f = File.open(file_path)
workbook = RubyXL::Parser.parse_buffer(f)
workbook.stream
p workbook[0][0][1].value
p workbook[0][20][10].value
end
end
Benchmark
user system total real
Stream: "0:0"
"20:9"
13.797000 0.125000 13.922000 ( 21.485774)
考察
XLSXファイルは、ZIPで固めたXMLファイル(群)である。
ZIPにせよ、XMLにせよStreamで処理できるような単純な構造ではないため、この結果も納得できます。
そもそもRubyXLのparse_buffer
メソッドは、RubyZipのZip::File.open_buffer
から来たものなので、本家でも「一時ファイルを保存せず、処理したい時使ってね」としか言ってなくて、勝手にStream的な処理ができて、性能アップできるぞと過大解釈した自分が悪かったです。