2020年12月23日

プログラミング

Ruby 後置whileの挙動をみる

目次

  1. はじめに
  2. 後置whileはloopより速い
  3. 後置whileはなぜ非推奨なのか
  4. まとめ

新しい技術にチャレンジし続けるpalanのアドベントカレンダーDay23です。
昨日は「AR.jsでLocation Based ARを作ってみる」という記事でした。

AR.jsでLocation Based ARを作ってみる

はじめに

先週書いたEnumerableモジュールの繰り返し処理に続き、本日は後置whileを使った繰り返し処理について書いていきます。

まず、テーマ選定の背景を簡単に書いていきます。

以前、オブジェクトにUUIDを割り当てるために下記①の処理を作成したのですが、Rubocopの警告を受け②に修正をしました。


# ①後置whileを使用

def set_uuid
  begin
    tmp_uuid = SecureRandom.alphanumeric(12)
  end while Picture.find_by(uuid: tmp_uuid)
  self.uuid = tmp_uuid
end


# ②loopを使用

def set_uuid
  self.uuid = loop do
    tmp_uuid = SecureRandom.alphanumeric(12)
    break tmp_uuid unless Picture.find_by(uuid: tmp_uuid)
  end
end

私個人としては後置whileの方が自然に上から処理の流れを追うことができるため読みやすいなと考えて書いたのですが、Rubyスタイルガイドに従い修正しました。

ところが最近whileが速いという話を聞き、なぜ処理速度が速い後置whileを使ってはいけないのか調べてみようと記事にすることにしました。

それでは本題へ移っていきます。

なお、本記事における実行環境は、2020年12月23日時点での最新の安定版である2.7.2を使用しています。

環境

  • Ruby: 2.7.2

後置whileはloopより速い

速度検証

まず、本当にwhileが速いのかを検証するために、後置whileとloopのベンチマークを取ってみました。
検証コードはRuby 2.7.0 リファレンスマニュアルを参考にして作成しました。

処理の内容としては空の配列を作成して 1 から 50,000 までの処理を順番に格納するものとなっています。


require 'benchmark'

n = 50_000
Benchmark.bm do |x|
    x.report("loop:") do
    ary = []
    count = 0
    loop do
      ary << count 
      count += 1
      break if count >= n
    end
  end
  x.report("end_while:") do
    ary = []
    count = 0
    begin
      ary << count 
      count += 1
    end while count <= n
  end
end

結果

Rubyの処理が動いたCPU 時間で比較するために、ユーザCPUを見ていきます。
また、結果を見やすくするためにloop処理 -> loop: 、 後置while -> end_while と識別子をつけて実行しました。

1回目

下記は1回目の実行結果です。loopが0.003378秒かかったのに対し、end_whileは0.001390秒で処理が完了している。後置whileの方が2倍以上速く処理が実行されていることが分かります。


       user     system      total        real
loop:  0.003378   0.000193   0.003571 (  0.003640)
end_while:  0.001390   0.000152   0.001542 (  0.001594)

2回目


       user     system      total        real
loop:  0.003064   0.000128   0.003192 (  0.003259)
end_while:  0.001416   0.000187   0.001603 (  0.001733)

3回目


       user     system      total        real
loop:  0.003095   0.000146   0.003241 (  0.003278)
end_while:  0.001361   0.000166   0.001527 (  0.001592)

2回目、3回目と実行してみましたが、結果に大きな差異はなく後置whileの方が速いという結果となりました。

後置whileはなぜ非推奨なのか

RuboCopの公式ドキュメントを参照したところ、下記のように書かれていました。

This cop checks for uses of begin…​end while/until something.

The cop is marked as unsafe because behaviour can change in some cases, including if a local variable inside the loop body is accessed outside of it, or if the loop body raises a StopIteration exception (which Kernel#loop rescues).

ループ内で定義されたローカル変数がループの外からアクセスされた場合、ループ本体がStopIteration例外を発生させた場合など、動作が変わるようです。

理由1: ループ内で定義した変数のスコープが変わる

loopの場合、ループ内で定義した変数に外部からアクセスすることができず、NameError: undefined local variable or method となりました。


loop do
  a = 1
  break
end
=> nil

a
Traceback (most recent call last):
.
.
.
NameError (undefined local variable or method `a' for main:Object)

一方 後置while を使う場合は、ループ内で定義した変数に外部からアクセスすることができました。意図せず変数が衝突してしまうケースが考えられそうです。


begin
  a = 1
end while false
=> nil

a
=> 1

理由2: StopIterationが発生した際の挙動が変わる

loopの場合、ループ内で StopIteration が発生すると、例外をrescueしてnullを返します。


loop do
  raise StopIteration
end
=> nil

後置while の場合は、StopIteration で処理が中断されます。


begin
  raise StopIteration
end while false

Traceback (most recent call last):
.
.
.
StopIteration (StopIteration)

StopIterationはどんな場面で発生するのか

StopIterationがそもそもどういった意味を持つ例外なのか分からなかったため、Ruby 2.7.0 リファレンスマニュアル を参照したところ、その名の通りイテレーションを止める際に発生する例外のようです。
実際の挙動を確認するため下記コードを実行してみました。
Enumeratorオブジェクトを生成してnextを繰り返すと、要素がなくなったタイミングでStopIterationが発生しました。


enumerator = [1,2,3].each
=> #

enumerator.next
=> 1

enumerator.next
=> 2

enumerator.next
=> 3
irb(main):117:0> enumerator.next
Traceback (most recent call last):
.
.
.
StopIteration (iteration reached an end)

上記のコードを少し書き換えてloop内で実行してみたところ、StopIterationが返されて処理が正常に終了しました。


enumerator = [1,2,3].each
=> #

loop do
  p enumerator.next
end
1
2
3
=> [1, 2, 3]

上記以外にもStopIterationが発生するケースはあるのかもしれないですが、今回確認できた限りだと、Enumeratorクラスのnextメソッドを実行した際に要素がなくなった場合のみでした。

後置whileを使う際の注意点

これまでの検証から、下記注意点を抑えた上で後置whileを使うことができそうです。

ポイント

  • 1. ループ内で定義した変数のスコープが変わることを理解する
  • 2. StopIterationが発生した際の挙動が変わることを理解する
  • 3. チーム内で共通認識が取れていることを確認する

これまで挙動の検証を実施してきましたが、3の共通認識が取れているかが特に大切なポイントになります。

まとめ

以上、今回の記事では後置whileの挙動についてご紹介してきました。

繰り返し処理の書き方ひとつにここまで検証をすることもなかなか無いので、自身の勉強にもなりました。
チーム内での議論の材料になればいいなと考えています。

この記事が何かのお役に立てば幸いです。

Rubyのお仕事に関するご相談

Bageleeの運営会社、palanではRubyに関するお仕事のご相談を無料で承っております。
zoomなどのオンラインミーティング、お電話、貴社への訪問、いずれも可能です。
ぜひお気軽にご相談ください。

無料相談フォームへ

1

0

AUTHOR

kobori

こぼり ともろう エンジニア

サーバーサイドエンジニア。SIerを経て2019年7月に入社。日々学習しながらRuby on Railsを使った開発を行っています。

アプリでもっと便利に!気になる記事をチェック!

記事のお気に入り登録やランキングが表示される昨日に対応!毎日の情報収集や調べ物にもっと身近なメディアになりました。

簡単に自分で作れるWebAR

「palanAR」はオンラインで簡単に作れるWebAR作成ツールです。WebARとはアプリを使用せずに、Webサイト上でARを体験できる新しい技術です。

palanARへ
palanar

palanはWebARの開発を
行っています

弊社では企画からサービスの公開終了まで一緒に関わらせていただきます。 企画からシステム開発、3DCG、デザインまで一貫して承ります。

webar_waterpark

palanでは一緒に働く仲間を募集しています

正社員や業務委託、アルバイトやインターンなど雇用形態にこだわらず、
ベテランの方から業界未経験の方まで様々なかたのお力をお借りしたいと考えております。

話を聞いてみたい

運営メンバー

eishis

Eishi Saito 総務

SIerやスタートアップ、フリーランスを経て2016年11月にpalan(旧eishis)を設立。 マーケター・ディレクター・エンジニアなど何でも屋。 COBOLからReactまで色んなことやります。

sasakki デザイナー

アメリカの大学を卒業後、日本、シンガポールでデザイナーとして活動。

yamakawa

やまかわたかし デザイナー

フロントエンドデザイナー。デザインからHTML / CSS、JSの実装を担当しています。最近はReactやReact Nativeをよく触っています。

Sayaka Osanai デザイナー

Sketchだいすきプロダクトデザイナー。シンプルだけどちょっとかわいいデザインが得意。 好きな食べものは生ハムとお寿司とカレーです。

はらた

はらた エンジニア

サーバーサイドエンジニア Ruby on Railsを使った開発を行なっています

kobori

こぼり ともろう エンジニア

サーバーサイドエンジニア。SIerを経て2019年7月に入社。日々学習しながらRuby on Railsを使った開発を行っています。

sasai

ささい エンジニア

フロントエンドエンジニア WebGLとReactが強みと言えるように頑張ってます。

damien

Damien

WebAR/VRの企画・開発をやっています。森に住んでいます。

ゲスト bagelee

ゲスト bagelee

かっきー

かっきー

まりな

まりな

suzuki

suzuki

miyagi

ogawa

ogawa

雑食デザイナー。UI/UXデザインやコーディング、時々フロントエンドやってます。最近はARも。

いわもと

いわもと

デザイナーをしています。 好きな食べ物はラーメンです。

kobari

taishi kobari

フロントエンドの開発を主に担当してます。Blitz.js好きです。

shogokubota

kubota shogo

サーバーサイドエンジニア。Ruby on Railsを使った開発を行いつつ月500kmほど走っています!

nishi tomoya

aihara

aihara

グラフィックデザイナーから、フロントエンドエンジニアになりました。最近はWebAR/VRの開発や、Blender、Unityを触っています。モノづくりとワンコが好きです。

nagao

SIerを経てアプリのエンジニアに。xR業界に興味があり、unityを使って開発をしたりしています。

kainuma

Kainuma

サーバーサイドエンジニア Ruby on Railsを使った開発を行なっています

sugimoto

sugimoto

asama

ando

iwasawa ayane

oshimo

oshimo

異業界からやってきたデザイナー。 palanARのUIをメインに担当してます。 これからたくさん吸収していきます!

CONTACT PAGE TOP