セルのラッパオブジェクトの生成の手間を毎回かけないように,キャッシュ機構を実装した。
これにより,セルの読み書きの速度が4倍になった。
ベンチマーク結果(ミリ秒):
キャッシュありの場合
計測結果:223125
キャッシュなしの場合
計測結果:829219
223125/829219=0.269
4倍の高速化。※この数値はMS Excelインストール環境下で得られたもの。
以下は改良したライブラリ本体と,それを使ってベンチマークを計測するコード。
ベンチ.bat
@echo off cscript //nologo bench.wsf pause
bench.wsf
<job> <script language="jscript" src="lib_excel.js" /> <script language="jscript" src="main.js" /> </job>
lib_excel.js
// // MS ExcelとOOo CalcとKingsoft Spreadsheetsを // 共通して取り扱うためのライブラリ // // ver0.3(セルのキャッシュ機構でセル読み書きを高速化) // // 要件: // Excelで組んだロジックをOOcで使いまわし,その逆も可としたい。 // 設計方針: // Decorator・factoryパターンあたりを参考に, // オフィス製品の差異を内部にコンポジットで隠蔽して切り替え,外側のAPIは統一する。 /* クラス設計: IExcel --- IBook --- ISheet --- ICell 名前空間はIExcelに集約 */ // ---------- 表計算ソフトのラッパーオブジェクト ---------- var IExcel = function(){ // 初期化 this.defineExcelType(); }; IExcel.prototype = { // 内部で使うオフィス製品のタイプ isMS : false, isKS : false, isOO : false, type_code : null, // どれを使うか調査して決める defineExcelType : function(){ try{ // MS製のオフィスがインストールされていれば最優先する this._excel = WScript.CreateObject("Excel.Application"); // http://d.hatena.ne.jp/language_and_engineering/20140214/p1 this.isMS = true; this.type_code = "ms"; }catch(e){ // MSが無かったら try{ // KingSoftがあれば,Excelと同一のAPIなのでこれを使う this._excel = WScript.CreateObject("ET.Application"); // http://d.hatena.ne.jp/language_and_engineering/20121218/p1 this.isKS = true; this.type_code = "ks"; }catch(e2){ // Kingsoftも無かったら try{ // 最後の手段として,OpenOffice.org Calcを使う var service_manager = WScript.CreateObject("com.sun.star.ServiceManager"); this._ooo_desktop = service_manager.createInstance("com.sun.star.frame.Desktop") // http://d.hatena.ne.jp/language_and_engineering/20141227/OOoCalcByWSHJScript this.isOO = true; this.type_code = "oo"; }catch(e3){ WScript.Echo("オフィス製品を何か一つインストールしてください。"); } } } this._books = []; // 初期化完了 return; } , _excel : null, _ooo_desktop : null, // Visible setVisible : function( b ){ if( this.isMS || this.isKS ){ this._excel.Visible = b; }else{ // TODO: } } , // 新規ブックを開く getNewBook : function(){ var ibook; if( this.isMS || this.isKS ){ this._excel.Workbooks.Add(); var book = this._excel.Workbooks( this._excel.Workbooks.Count ); // インタフェースに変換 ibook = new IExcel.IBook( this, book ); return ibook; }else{ var doc = this._ooo_desktop.loadComponentFromURL( "private:factory/scalc", "_blank", 0, [] ); // インタフェースに変換 ibook = new IExcel.IBook( this, doc ); return ibook; } } , // 既存のブックをファイルパスで開く openBookByFilePath : function( file_path ){ var ibook; if( this.isMS || this.isKS ){ this._excel.Workbooks.Open( file_path ); var book = this._excel.Workbooks( this._excel.Workbooks.Count ); ibook = new IExcel.IBook( this, book ); return ibook; }else{ var doc = this._ooo_desktop.loadComponentFromURL( "file:///" + file_path.replace(/\\/g, "/"), "_blank", 0, [] ); ibook = new IExcel.IBook( this, doc ); return ibook; } } }; // ---------- ブックを表すラッパオブジェクト ---------- IExcel.IBook = function( parent, real_book ){ this._parent = parent; this._book = real_book; this.type_code = parent.type_code; this._sheets = []; }; IExcel.IBook.prototype = { _parent : null, _book : null, type_code : null, // ファイルパスを指定して保存 saveAs : function( file_path ){ if( this.type_code == "ms" || this.type_code == "ks" ){ this._parent.DisplayAlerts = false; this._book.SaveAs( file_path ); }else{ var file_url = "file:///" + file_path.replace(/\\/g, "/") ; this._book.storeAsURL( file_url, [] ); } }, // 上書き保存 save : function(){ if( this.type_code == "ms" || this.type_code == "ks" ){ this._parent.DisplayAlerts = false; this._book.Save(); // http://www.happy2-island.com/excelsmile/smile03/capter00303.shtml }else{ this._book.store(); } }, // 番号でシートを取得(1始まり) getSheetByIndex : function( index ){ //log("現在のシートの個数:" + this.getSheetsCount() ); //log("index: " + index); var sheet, isheet; if( this.type_code == "ms" || this.type_code == "ks" ){ sheet = this._book.Worksheets( index ); isheet = new IExcel.ISheet( this._parent, sheet ); return isheet; }else{ sheet = this._book.Sheets.getByIndex( index - 1 ); // 0始まり // http://blog.livedoor.jp/addinbox/archives/51243622.html // http://itref.fc2web.com/openoffice/basic/calc.html //sheet = this._book.Sheets( index - 1 ); // 0始まり //REM: これだとシートがうまく取れなかった //log( "取得したシートの名称は" + sheet.Name ); isheet = new IExcel.ISheet( this._parent, sheet ); return isheet; } } , // シートの個数 getSheetsCount : function(){ if( this.type_code == "ms" || this.type_code == "ks" ){ return this._book.Sheets.Count; }else{ return this._book.Sheets.getCount(); } } // TODO:他のブック操作メソッド }; // ---------- シートを表すラッパオブジェクト ---------- IExcel.ISheet = function( parent, real_sheet ){ this._parent = parent; this._sheet = real_sheet; this.type_code = parent.type_code; this.cell_caches = {}; }; IExcel.ISheet.prototype = { _parent : null, _sheet : null, type_code : null, // セル参照(番号は一始まり) getCell : function( y, x ){ //log( y + "行" + x + "列目" ); // オブジェクト生成のコストを省くため,可能ならキャッシュから返す if( this.has_cell_cache( y, x ) ){ return this.getCellByCache( y, x ); }else{ return this.getCellByNew( y, x ); } }, // シート内部に保持するセルのキャッシュ機構 // これがあるとないでは,セルの読み書きの速度が4倍異なる // セルキャッシュ cell_caches : null, // キャッシュがあるか has_cell_cache : function( y, x ){ return ( this.cell_caches[ y + "-" + x ] != null ); }, // キャッシュ登録 storeCellCache : function( y, x, icell ){ this.cell_caches[ y + "-" + x ] = icell; }, // セルキャッシュを返す getCellByCache : function( y, x ){ return this.cell_caches[ y + "-" + x ]; }, // セルオブジェクトを生成して返す getCellByNew : function( y, x ){ var cell; if( this.type_code == "ms" || this.type_code == "ks" ){ cell = this._sheet.Cells( y, x ); // 1始まり }else{ cell = this._sheet.getCellByPosition( x - 1, y - 1 ); // 0始まりでMSと逆 } // 変換 var icell = new IExcel.ICell( this._parent, cell ); // キャッシュ登録 this.storeCellCache( y, x, icell ); return icell; } // TODO:他のシート操作メソッド }; // ---------- セルを表すラッパオブジェクト ---------- IExcel.ICell = function( parent, real_cell ){ this._parent = parent; this._cell = real_cell; this.type_code = parent.type_code; }; IExcel.ICell.prototype = { _parent : null, _cell : null, type_code : null, // 値を書き込み setValue : function( v ){ if( this.type_code == "ms" || this.type_code == "ks" ){ this._cell.Value = v; }else{ this._cell.String = v; } } , // 値を取得 getValue : function(){ if( this.type_code == "ms" || this.type_code == "ks" ){ return this._cell.Value; }else{ return this._cell.String; } } // TODO:他のセル操作メソッド }; /* サンプルコード // Excel起動 var excel = new IExcel(); excel.setVisible( true ); // 対象ブックを開く var book = excel.openBookByFilePath( filepath ); // 最初のシート var sheet = book.getSheetByIndex(1); ・・・ // ブックを保存 book.saveAs( filepath ); */
main.js
function log(s){ WScript.Echo(s); } // ---- カレントフォルダにExcelを新規生成 var curr_dir = WScript.ScriptFullName.replace(WScript.ScriptName,""); var file_path = curr_dir + "bench.xls"; var fso = WScript.CreateObject("Scripting.FileSystemObject"); // Excel起動 var excel = new IExcel(); excel.setVisible( true ); // 新規ブック var book = excel.getNewBook(); // 先頭のシート var sheet = book.getSheetByIndex(1); // 計測開始 var tic = new Date().getTime(); log(tic); // 初期値 sheet.getCell( 1, 1 ).setValue( 1 ); // セルを繰り返しスキャン for( var i = 1; i <= 100; i ++ ){ for( var j = 1; j <= 1000; j ++ ){ // 読み書きを両方行なう sheet.getCell( j + 1, 1 ).setValue( sheet.getCell( j, 1 ).getValue() + i ); } } var tac = new Date().getTime(); log(tac); log("計測結果:" + (tac - tic) ); log("全処理が終了");