TL;DR
require 'wrap_excel'
file_path = 'test.xlsx'
workbook = WrapExcel::Book.open(file_path)
sheet = workbook[0]
p sheet[0, 1].value
p sheet[20, 10].value
巨大Excelの読み取りは確実に早くなりますが、以下のことを注意すべきです。
- ファイルの新規作成には適していない
- 大量データの書込みに適していない
- ExcelインストールしたWindowsでしか動かない
はじめに
先日投稿した RubyXLでExcelファイルを読み取る時ハマったこと では、お馴染み?のGem RubyXLが巨大(数MB~数百MB)Excelファイルを読み込む速度が遅いとわかりました。
別の記事では、PythonはWin32APIを通して、Excelアプリケーションを使ってExcelファイルを操作できると知り、Excelアプリケーションを使っているであれば加速できると思い、Rubyでも同等なライブラリーがあるかを調べました。
Pythonと違い、RubyでWin32APIをアクセス機能は標準ライブラリーWIN32OLEとして提供されています。
またExcel操作に特化したラッピングライブラリー、WrapExcelも日本の有志より開発されました。
方法&結果
Excel生成
まずは先日と同じように、巨大?Excelファイルの生成です。
require 'benchmark'
require 'rubyXL'
file_path = 'test.xlsx'
# Create a test file
COL_SIZE = 50
ROW_SIZE = 10000
def init_xlsx(col_num, row_num)
workbook = RubyXL::Workbook.new
sheet = workbook[0]
add_cells(sheet, col_num, row_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 by RubyXL: ") do
workbook = init_xlsx(COL_SIZE, ROW_SIZE)
workbook.write(file_path)
puts "#{COL_SIZE}*#{ROW_SIZE}"
end
end
> ruby .\create_xlsx_rubyXL.rb
user system total real
Create Xlsx by RubyXL: 50*10000
5.766000 0.422000 6.188000 ( 22.548951)
前回の倍の50列x10000行のExcelファイルを生成しましたが、ファイルサイズは1.6MB程度でExcelの世界ではぜんぜん「巨大」とは言えません。
PS: WrapExcelを使った生成の方法は文末で紹介しますが、訳あっておすすめしません。
Excelアプリで開く
体感的には4,5秒程度で開けました。
WrapExcel(Win32API)で開く
require 'benchmark'
require 'wrap_excel'
file_path = 'test.xlsx'
Benchmark.bm do |x|
x.report("Read by WrapExcel: \n") do
workbook = WrapExcel::Book.open(file_path)
sheet = workbook[0]
p sheet[0, 1].value
p sheet[20, 10].value
end
end
> ruby .\read_xlsx_WarpExcel.rb
user system total real
Read by WrapExcel:
"0:0"
"20:9"
0.031000 0.031000 0.062000 ( 7.501389)
読み取り時間はExcelアプリよりは時間かかったものの、僅か7.5秒で前回の50*5000のファイルよりも早く開けました。
RubyXLで開く
対照実験として前回と同じように読み込んでみます。
require 'benchmark'
require 'rubyXL'
file_path = 'test.xlsx'
Benchmark.bm do |x|
x.report("Read by RubyXL: \n") do
workbook = RubyXL::Parser.parse(file_path)
p workbook[0][0][1].value
p workbook[0][20][10].value
end
end
> ruby .\read_xlsx_rubyXL.rb
user system total real
Read by RubyXL:
"0:0"
"20:9"
24.156000 0.297000 24.453000 ( 45.481205)
ご覧の通り、45秒もかかりました。
余談: WrapExcelでExcelファイルの生成
- 爆遅なので、100行生成するデモのみします
- WrapExcelはゼロからExcelファイルを生成することはできないので、生のWIN32OLEで生成することとなります
require 'benchmark'
require 'wrap_excel'
require 'win32ole'
FILE_PATH = 'test2.xlsx'
# Create a test file
COL_SIZE = 50
ROW_SIZE = 100
# wrap_excel does not support creating a new file, Create a new file by WIN32OLE
def init_new_xlsx(file_path)
excel = WIN32OLE.new('Excel.Application')
wbook = excel.Workbooks.Add
file_path = File.expand_path(file_path)
file_path = WrapExcel::Cygwin.cygpath('-w', file_path) if RUBY_PLATFORM =~ /cygwin/
fp = WIN32OLE.new('Scripting.FileSystemObject').GetAbsolutePathName(file_path)
excel.DisplayAlerts = false
wbook.SaveAs(fp)
wbook.Close
excel.Quit
end
def open_xlsx(file_path, col_num, row_num)
unless File.exist?(file_path)
init_new_xlsx(file_path)
end
workbook = WrapExcel::Book.open(file_path, read_only: false)
sheet = workbook[0]
add_cells(sheet, row_num, col_num)
workbook.save
workbook.close
end
def add_cells(sheet, col_num, row_num)
(0...row_num).each_with_index do |row, i|
sheet[row, 0] = i
(1...col_num).each_with_index do |col, j|
sheet[row, col] = "#{i}:#{j}"
end
end
end
Benchmark.bm do |x|
x.report("Create Xlsx By WrapExcel: ") do
open_xlsx(FILE_PATH, COL_SIZE, ROW_SIZE)
puts "#{COL_SIZE}*#{ROW_SIZE}"
end
end
> ruby .\create_xlsx_WrapExcel.rb
user system total real
Create Xlsx By WrapExcel: 50*100
1.171000 0.515000 1.686000 (111.216136)
100行だけのファイルを生成するにも111秒使いました。理由としてはセルの書き込みごとにWin32APIをCallしているせいかと推測しています。
考察
Excelアプリの助力で、Win32APIを使ったExcelファイルの読み取りは予想通り早かったです。しかしシステムコールを使った故、頻繫にデータを書き込むことに適していないようです。
また、当然なことに、Win32APIはWindowsでしか動かない上、Excel系のAPIを使うにはExcelアプリ(Microsoft Office)を購入する必要があります。
VBAを書かないで済むという点では有用かもしれませんが、ニッチな使い道しか適していないようです。