LoginSignup
4
4

More than 5 years have passed since last update.

Enumerator.each_sliceはArrayのEnumeratorなのでメモリに注意

Last updated at Posted at 2014-11-20

ログファイルの処理をしていて,メモリに載せるのがまずいからEnumeratorを使うことがよくある.けれどeach_sliceを不用意に使ったせいでメモリオーバーしてしまったのでメモ.

起きた問題

ちゃんと意識すれば当たり前なのだが

pry(main)> (1..100000000000000000000000000).each_slice(1000000000000000000){|slice| slice.each{|item| p item} }
NoMemoryError: failed to allocate memory

こうなる.classをチェックすると

pry(main)> (1..100).each_slice(10).class
=> Enumerator
pry(main)> p (1..100).each_slice(10).map(&:class)
=> [Array, Array, Array, Array, Array, Array, Array, Array, Array, Array]

なので,各sliceはメモリ上に展開されることがわかる.メモリに制約がある状況ではEnumeratorのEnumeratorであってほしい.ということで雑だけど以下のコード

解決策

Enumerator::LazyのメソッドとしてEnumeratorのEnumeratorを返すslicesメソッドを作ってみる

注意: 以下の実装は使い方次第で無限ループに入る

class Enumerator::Lazy
  def slices(size)
    Enumerator.new do |parent_yielder|
      begin
        while true
          parent_yielder << Enumerator.new do |child_yielder|
            (0...size).each do |_|
              child_yielder << self.next
            end
          end
        end
      rescue StopIteration
      end
    end
  end
end

これで大きなサイズのsliceも扱える

pry(main)>(1..100000000000000000000000000).lazy.slices(1000000000000000000).each{|slice| slice.each{|item| p item} }
=>
1
2
3
...

この実装の問題

下位のEnumeratorの評価に上位のEnumeratorに依存するので,上位だけ評価すると無限ループ

(1..100).lazy.slices(10).each{|slice| p slice }
=> おわらない

これは,下位のExceptionを使って止めるんじゃなくて,上位でも独自にサイズをカウントすればまぁ防げる.ただし2重に評価するので2倍時間かかる

下位のEnumerator同士も評価順によって値が変わる

source = (1..100).map.slices(10)
child1 = source.next
child2 = source.next

p child1.next
=> 1
p child1.next
=> 2
p child2.next
=> 3

どうすりゃいいんだろう…

状態を保ったままEnumeratorをdupすることができれば,親側で消費しながら子を作ってくことができるので解決するが,Enumerator.dupは禁止されている.

(0..size).each{|_| self.next()}
child = self.dup
parent << child
# ↑できない
4
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4