数値データのロード
数値データとは
「数値データ」というカテゴリに入るものにはさまざまなデータ形式がある.数値データは完全に数値である必要はない.数値データと一般的なデータの違いは,Wolfram言語で好まれるデータ形式は,文字列や記号ではなく実数,整数,有理数,複素数で表されるものであるという点である.
データのロードのための関数と手段
Import | 使いやすい.多数の形式をサポートする |
ReadList | 高速,柔軟なタイプ制御,ストリームをサポートする |
BinaryReadList | 上記2つの関数よりも高性能 |
Get(DumpSave) | 最も効率的なロード操作.パックアレーを維持する |
Import | メモリオーバーヘッドが大きい.性能に制約がある |
ReadList | 使いやすさに欠ける.テキスト形式に最も適している |
BinaryReadList | 4つの中で最も使いにくい.データ処理が必要 |
Get(DumpSave) | ファイルは転送可能ではない.まず別のメカニズムでロードする必要がある |
小さいファイルの場合や,新しいデータを初めて操作する場合は,Importでデータをロードするのが最も簡単であろう.Importはデータをロードするのに最も使いやすいインターフェースを提供し,これらの場合では余計なオーバーヘッドは通常大した問題にはならない.しかし,アプリケーションを配備したり非常に大きいデータを扱ったりするときは,ReadListあるいはBinaryReadListを使うことでデータロードのパフォーマンスが格段に向上する.
ReadListの制約
通常,ReadListはスピードとメモリオーバーヘッドの点で,対応するImport操作に勝っている.その理由の一つは,ReadListの第2引数で使われる型の制約があるためである.これにより,Wolfram言語はImportで使われるさまざまなパース操作を省略するのである.そのパース操作とは,文字列であるべき項目は文字列として解釈されることや,実数と整数は数値に対して適切に使われることを確実にするためのものである.それがあるためにImportは使いやすいものとなっているのであるが,ReadListで同じ操作をする場合以上の時間とメモリが必要となる.
メモリ使用量の違いが見たかったら,MemoryConstrained[…,4*1024^2](操作を4MBのRAMに限定する)を適用してみるとよい:
ReadListは,大きいデータファイルに1つの型(実数あるいは整数の行と列等)しかないときは簡単に使えるが,文字列と実数が混在していたり,実数値のデータで各列の1行目に文字列が入っていたり等,複数の型が存在すると扱いにくい.
Importは時系列データの日付文字列を自動的に変換する:
小さいデータセットの場合,時系列データに関してはImportを使った方が一般に効率的である.しかし,大量の情報が読み込まれる場合は,Importによる処理のオーバーヘッドを考えると,それより少ないメモリで済みより高速にデータが扱えるよう,この余分なステップを踏む価値はあるかもしれない.
コンバータを使ったインポート
すべてのImport操作がWolfram言語で直接扱えるというわけではなく,重要な操作を扱うために外部のコンバータバイナリに依存している.これらのバイナリにはファイルの大きさの制約があるか,これに関連したある程度のオーバーヘッドがあることが多い.
例えば,XLS(Microsoft Excelドキュメント)ファイルはXLSコンバータを使いXLSドキュメントを処理し,Wolfram言語に読み込む.この処理では,追加のメモリが必要である.Wolfram言語だけでなくコンバータのメモリにもファイルが一部必要となり,そのデータをWolfram Symbolic Transfer Protocol (WSTP)経由でWolfram言語に渡さなければならないからである.このようなファイル形式をインポートすることに関する多量のメモリオーバヘッドがあっても不思議ではない.
大きいデータに適した形式
Comma-Separated Values | .csv | 記録をカンマと改行で分けるテキスト形式 |
Tab-Separated Values | .tsv | 記録をタブと改行で分けるテキスト形式 |
Table | .dat | 記録をタブと改行で分けるテキスト形式 |
Plain Text | .txt | 任意の記録セパレータを使ってデータが保存できるテキスト形式 |
Binary | * | さまざまなファイル拡張子を持つこともファイル拡張子を持たないこともできるバイナリデータ形式 |
測定
データのロード操作を最適化する場合,ロードの時間とメモリオーバーヘッドを正確に測定することが大切である.Wolfram言語にはこのようなタスクのどちらにも使える関数が多数備わっている.これを使うことで,データのロードと処理に最適な方法を決めることができる.
AbsoluteTimingとTiming
Wolfram言語には,カーネル操作を実行するのにかかった時間を測定するTimingおよびAbsoluteTimingという2つの関数がある(その値はフロントエンドでの描画にかかった時間は含んでいないことに注意).
TimingとAbsoluteTimingの最大の違いは,Timingはカーネルでの計算時間を測定するのに対して,AbsoluteTimingはカーネル内で経過した時間を測定する.この違いをしたの例で見てみる.
Timingはカーネル内での計算にかかった時間を測定し,待ち時間は含まない.
データを読み込むとき,Timingの結果を見ると便利であるが,実際にかかった時間の方がアプリケーションに影響を与えやすいので,ほとんどの場合経過時間(それゆえAbsoluteTimingである)の方が速度の測定に適している.次の例題は,例のデータにおけるTimingとAbsoluteTimingの差を示すものである.
時間測定の値がキャッシュされた結果の影響を受けないよう,各評価の前にClearSystemCacheが使われる:
時間測定の別の方法として,データのロード操作の前後にタイムスタンプを手動で作成するというものがある.これでもAbsoluteTiming(この場合DateListよりも解像度が高い)と同じ値が返される.
時間測定に関する注意
時間測定をする場合,ある特定の操作に対する時間制限を設けておいた方がよい.TimeConstrainedでターゲット関数をラップし,時間制限(単位は秒)を指定することができる.
MemoryInUseとMaxMemoryUsed
Wolfram言語には,Wolfram言語カーネルのメモリオーバーヘッド(これには通常フロントエンドのオーバーヘッドは含まれない)を測定するMemoryInUseとMaxMemoryUsedの2つの関数がある.
メモリ測定に関する注意
大きいデータファイルを扱っているときは特に重要なことであるが,Wolfram言語は前の評価の履歴を記録している.これは環境変数$HistoryLengthで管理される.
つまり,たとえ変数がクリアされても,変数に含まれているデータのコピーがメモリに残っているということである.大きいデータを扱っているときは,不必要なメモリオーバーヘッドを防ぐために通常$HistoryLengthを小さい数かゼロに設定した方がよい.
初めて大きいデータを扱うときは,システムリソースの負担となっている評価を実行することは簡単であるかもしれない.作成するアプリケーションを初めてプロトタイプ化するとき,評価を
MemoryConstrained
でラップし,特定の評価に利用できるリソースの量を制限すると便利である.
ByteCountに関する注意
ByteCountの結果はMemoryInUseで返される結果と大きく異なる場合がある.ByteCountはそれぞれの式や部分式をあたかも一意の式であるかのように扱うからである.しかし,実際には部分式は同一であり共有されることがある.
以下がその例である.
結果が異なっているのは,100万の要素式(x等)はその要素がいくら取るかにかかわりなく800万バイト必要とする.これは著者が使用している64ビットマシンでのことであり,ポインタ1つが8バイトであるため100万のポインタ配列は800万バイトである.これらのポインタはすべて同じ式 Stringを指しており,その内容は「Sample long string...」である.
ByteCountは式の大きさの計算に,簡単な方法を使う.単に要素の大きさとコンテナの大きさを足すだけである.
上記の800万バイトにString式の100万のコピーを加えると,推定バイト数は1億5200万になる.
制約
Wolframシステムバージョン9以降,どれだけのデータがインポートできるかはシステムで利用できるメモリの量で制約される.正確に言うと,オペレーティングシステムで1つのプロセスにどれだけのメモリが使えるかということである.そのような訳で,データをロードするときや,より大きいデータのロードをどのように制約するかについて気をつけなければならない要因が多数ある.
データの次元とファイルの大きさ
メモリは式の内部に含まれるものだけでなく,データの次元にも影響されるこということに注意する必要がある.例えば,次の2つのデータ集合は新しいカーネルで,同じ要素数で,ReadListとImportを使って同じマシンにロードされるが,式をロードし保存するためのメモリ量が大きく異なる.
次のデータは0から10までのReal(実数)で構成されており,1列に468万要素ある:
パックされた式の大きさは,すべてのデータファイル同じであり,それぞれのデータファイルでそれぞれのデータファイルのImportとReadListとで同じであるが,ファイルの次元に基づいて式をロードしたり保存したりするのに必要なメモリ量には大きな違いがある.一次元だけで長い式には,通常より多くのメモリが必要である.
制約を超える
上のセクションではデータをWolfram言語にロードする場合の一般的な制約のいくつかを挙げた.ここでは,特に大きいデータを扱う場合にそのような制約を超えるために使うことのできるテクニックをいくつか紹介する.
ReadListとImportの比較
バイナリファイル
BinaryReadListはデータをロードするのに最もメモリ効率と時間効率のよい関数である.実質的にファイルの大きさとメモリフットプリントが1対1で実行される.
前述の通り,ネイティブでバイナリ形式のデータを操作する場合はReadListよりBinaryReadListの方が通常格段に速い.しかし,データがすでにテキスト/ASCII形式である場合もあろう.そのときでもBinaryReadListは使える.
ヘルパ関数のプログラミング
開発では,ファイルにImportを使ってデータを取得し,後はWolfram言語にフォーマットとデータ変換を任せる.
この関数はImportのような一般化された関数ではなく,このデータ形式用にカスタマイズされたものなので,Importよりもほぼ5倍速く,必要なメモリもわずかである.テキストファイルのインポートは,多様なデータ形式を扱い,追加の後処理を行う,非常にロバストで一般化されたReadListである.
ファイルの一部を読み込む
大きなデータファイル(特にスクリプトやログシステムで大きくなったもの)は,一度にはシステムにメモリがロードできないほど大きなスペースを必要とする場合がある.だからといってWolfram言語がデータにアクセスできないというわけではない.非常に大きいファイルからデータの一部を読み出すのに利用できる方法が多数ある.
データがファイルの上部にある場合は,大きいファイルに直接ReadListを使用し,データの部分集合を読み出すことができる.
しかし,データがファイルの中に隠れている場合や欲しい情報の前にあるデータすべてをロードすることができない場合は,OpenReadとFindを使ってデータ内の特定の要素(例えば日付等)を探すことができる.
データの再ロード
アプリケーションの中には,データを使うたびに再ロードを必要とするものもあれば,データファイルの特定のリストにアクセスする必要があるものもある.このような場合,Wolfram言語のDumpSave関数を使うとデータのロードが非常に最適化できる.
Wolfram言語には通常のWrite/Export関数以外に,データファイルの変数をソートする組込みの方法が2つある.SaveとDumpSaveはファイルシステムに変数値を保存するために使うことができ,その後もGetを使って再ロードすることができる.
SaveとDumpSaveでは,重要な違いがいくつかある.データ保存の点で最も大きい違いは,Saveが情報の保存にテキスト形式を使うのに対して,DumpSaveはバイナリデータ形式を使うという点である.後者は前者ほど容量を必要とせず,読込みもずっと速い.DumpSaveはパックアレーも保存するので,スピードとメモリオーバーヘッドの両方で向上が見られる.
DumpSaveの大きな制約は,データが符号化されたときと同じマシンアーキテクチャでデータをロードすることが,その符号化システムにより要求されるという点である.したがって,ファイルはプラットフォーム間で転送することができない.