やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

Rubyで巨大な配列を扱う時はEnumeratorを使うのが良さそう

先日、Rubyで巨大な配列(Array)を扱うことがありました。
気になるのは巨大な配列を宣言したときのメモリ使用量です。試しに、要素数が10,000個の配列を宣言してみると89,712Byteほどメモリを使用していることが分かります。

require 'objspace'

# memsize_ofで得られる情報は必ずしも正しいものではありません
list = (1..10000).to_a
puts ObjectSpace.memsize_of(list) # 89712Byte

docs.ruby-lang.org

下のグラフを見て分かるように要素数が増えれば増えるほど、必要なメモリはほぼ線形的に増えていきます。
要素数が一億(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の存在は知っていたものの使う機会がなかったので良い機会に恵まれたなと思います。

少しでも「ええな〜」と思ったらイイネ!・シェア!・はてなブックマークを頂けると励みになります。