先日、Rubyで巨大な配列(Array)を扱うことがありました。
気になるのは巨大な配列を宣言したときのメモリ使用量です。試しに、要素数が10,000個の配列を宣言してみると89,712Byte
ほどメモリを使用していることが分かります。
require 'objspace' # memsize_ofで得られる情報は必ずしも正しいものではありません list = (1..10000).to_a puts ObjectSpace.memsize_of(list) # 89712Byte
下のグラフを見て分かるように要素数が増えれば増えるほど、必要なメモリはほぼ線形的に増えていきます。
要素数が一億(100,000,000)もある配列であれば1GB近くのメモリが必要になるようです。
require 'objspace' def list_mermory_verificator(elm_size) list = (1..elm_size).to_a puts "Array(size=#{elm_size}): #{ObjectSpace.memsize_of(list)}" end list_mermory_verificator(10000) # 89712Byte list_mermory_verificator(100000) # 1021592Byte list_mermory_verificator(1000000) # 11636312Byte list_mermory_verificator(10000000) # 88363080Byte list_mermory_verificator(100000000) # 1006510416Byte ≒ 0.9373...GB
配列の要素数に上限がない可変長なケースでは、何も考えずに巨大な配列を作ってしまい、メモリを圧迫してしまうことが想像できます。では、どうすれば巨大な配列をメモリの使用量を抑えたまま、扱うことができるでしょうか。
Enumeratorを使ってみる
この問題を解決するEnumerator
というイテレーターを扱うためのクラスがRubyには用意されています。
他の言語でも近しいものが提供されているのでRuby固有の概念ではありません。デザインパターンに「Iteratorパターン」というものがありますが、RubyのEnumerator
も「Iteratorパターン」を実装したものでしょう。
# Enumeratorのインスタンスを作成 enum_ = Enumerator.new do |e| e << 1 e << 2 e << 3 end # 要素を1つずつ取り出す puts enum_.next # 1 puts enum_.next # 2 puts enum_.next # 3
先ほどのように配列(Array)を普通に宣言すると全ての要素をメモリ領域に確保してしまいますが、Enumerator
の場合は要素が取り出されるまではメモリ領域に確保されないため、たとえ要素数が一億(100,000,000)あってもメモリの使用量はEnumerator
クラスのインスタンス分のみとなります。
require 'objspace' def enum_verificator(size) my_enum = Enumerator.new do |e| (1..size).each { |v| e << v } end puts ObjectSpace.memsize_of(my_enum) end enum_verificator(10000) # 136Bytes enum_verificator(100000) # 136Bytes enum_verificator(1000000) # 136Bytes enum_verificator(10000000) # 136Bytes enum_verificator(100000000) # 136Bytes
配列の要素数が増えてもメモリ使用量は変わらず136Bytesであることが分かります。
Enumerator
を使えば可変長な巨大な配列であってもメモリの使用量を気にせず実装することができそうです。
最後に
いかがでしたでしょうか。本日はRubyのEnumerator
について紹介しました。
こういった機能は書籍などで紹介されていても実際に使う機会が少なく、僕自身もEnumerator
の存在は知っていたものの使う機会がなかったので良い機会に恵まれたなと思います。
少しでも「ええな〜」と思ったらイイネ!・シェア!・はてなブックマークを頂けると励みになります。