メモ化のRubyistsガイド
今日は、パフォーマンスを向上させるための私のお気に入りのテクニックの1つについて話したいと思いました。これは、パフォーマンスを簡単に向上させるためのソースであり、最終的には合計され、アプリケーションがくすぶっている瓦礫の山にたまにしか減少しません。ごくまれに。
この手法は「メモ化」と呼ばれます。 10ドルのコンピュータサイエンスの言葉にもかかわらず、それは単に、メソッドを呼び出すたびに同じ作業を行う代わりに、戻り値を変数に保存して、代わりにそれを使用することを意味します。
擬似コードでは次のようになります。
def my_method
@memo = <work> if @memo is undefined
return @memo
end
そして、これがRubyでそれを行う方法です。これは最も堅牢なアプローチですが、非常に冗長です。他にも、後で説明するより簡潔なアプローチがあります。
class MyClass
def my_method
unless defined?(@my_method)
@my_method = begin
# Do your calculation, database query
# or other long-running thing here.
end
end
@my_method
end
end
上記のコードは3つのことを行います:
-
@my_method
という名前のインスタンス変数があるかどうかを確認します 。 - ある場合は、何らかの作業を行い、結果を
@my_method
に保存します。 。 -
@my_method
を返します
my_method
という名前のメソッドとインスタンス変数の両方があるという事実と混同しないでください。 。変数には何でも名前を付けることができますが、メモ化されているメソッドにちなんで名前を付けるのが慣例です。
上記のコードの問題の1つは、少し面倒なことです。このため、ほぼ同じことを行う速記バージョンが表示される可能性がはるかに高くなります。
class MyClass
def my_method1
@my_method1 ||= some_long_calculation
end
def my_method2
@my_method2 ||= begin
# The begin-end block lets you easily
# use multiple lines of code here.
end
end
end
これらは両方ともRubyのa ||= b
を使用します a || (a = b)
、それ自体は多かれ少なかれ略記です:
# You wouldn't use return like this in real life.
# I'm just using it to express to beginners the idea
# that the conditional evaluates to whatever winds up in `a`.
if a
return a
else
a = b
return a
end
非常に細心の注意を払っている場合は、短縮バージョンがメモ変数の存在をチェックするのではなく、メモ変数の「真実性」を評価していることに気付いたかもしれません。これは、短縮バージョンの主な制限の1つの原因です。nil
をメモ化することはありません。 またはfalse
。
これが重要ではないユースケースはたくさんあります。しかし、それはあなたがメモするときはいつでもあなたがあなたの心の後ろに保たなければならないそれらの厄介な事実の1つです。
これまでは、単一の値のメモ化のみを扱ってきました。しかし、常に同じ結果を返す関数は多くありません。技術面接の昔からのお気に入りであるフィボナッチ数列を見てみましょう。
次のように、Rubyでフィボナッチ数列を再帰的に計算できます。
class Fibonacci
def self.calculate(n)
return n if n == 0 || n == 1
calculate(n - 1) + calculate(n - 2)
end
end
Fibonacci.calculate(10) # => 55
この実装の問題は、非効率的であるということです。これを証明するために、print
を追加しましょう n
の値を表示するステートメント 。
class Fibonacci
def self.calculate(n)
print "#{ n } "
return n if n == 0 || n == 1
calculate(n - 1) + calculate(n - 2)
end
end
Fibonacci.calculate(4)
# Outputs: 4 3 2 1 0 1 2 1 0
ご覧のとおり、calculate
n
の同じ値の多くで繰り返し呼び出されています 。実際、calculate
への呼び出しの数 n
で指数関数的に成長します 。
これを回避する1つの方法は、calculate
の結果をメモ化することです。 。そうすることは、私たちがカバーした他のメモ化の例と大差ありません。
class Fibonacci
def self.calculate(n)
@calculate ||= {}
@calculate[n] ||= begin
print "#{ n } "
if n == 0 || n == 1
n
else
calculate(n - 1) + calculate(n - 2)
end
end
end
end
Fibonacci.calculate(4)
# Outputs: 4 3 2 1 0
calculate
をメモ化したので 、呼び出しの数がn
で指数関数的に増加しなくなりました 。
Fibonacci.calculate(20)
# Outputs: 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
メモ化はキャッシュによく似ていますが、メモ化された結果が自動的に期限切れにならないことと、一度設定すると簡単にクリアする方法がない点が異なります。
フィボナッチ数列ジェネレータのようなユースケースでは、これはほとんど問題になりません。 Fibonacci.calculate(10)
常に同じ結果を返します。しかし、他のユースケースではそれは重要です。
たとえば、次のようなコードが表示される場合があります。
# Not the best idea
class User
def full_name
@full_name ||= [first_name, last_name].join(" ")
end
end
個人的には、名前や姓を変更するとフルネームが更新されない可能性があるため、ここではメモ化を使用しません。
もう少しリラックスできる場所の1つは、Railsコントローラーの内部です。このようなコードを見るのは非常に一般的です:
class ApplicationController
def current_user
@current_user ||= User.find(...)
end
end
コントローラインスタンスは各Webリクエストの後に破棄されるため、これは問題ありません。現在ログインしているユーザーが通常のリクエスト中に変更される可能性はほとんどありません。
ActionCableのようなストリーミング接続を扱うときは、もっと注意する必要があるかもしれません。知らない。私はそれを使ったことがありません。
最後に、他のことと同じように、メモ化が行き過ぎてしまう可能性があることを指摘しておきたいと思います。これは、メモ変数の存続期間を通じて変更されることのない高価な操作にのみ実際に適用する必要がある手法です。
-
Rubyの入出力(IO):決定的なガイド
I/Oは入力/出力の略です。 入力とは、何か(コンピューター、Rubyメソッド、脳)に入力されるすべてのデータと情報を意味します。 入力の例 : キーボードのプレスキーを押します マウスクリック 読んだ本 出力とは、入力の結果として出てくるすべてのものを意味します。 出力の例 : 1+1の結果 読んだ記事の要約を書く コーヒー Rubyでは、I / Oについて話すとき、通常、ファイルの読み取り、ネットワークソケットの操作、画面への情報の印刷を指します。 IOクラスを理解する IOはRubyのクラスでもあります。 Fileなどの他のオブジェクトの親クラスです &Socke
-
Mac セキュリティ:基本ガイド
Mac にはウイルス対策が必要ですか? Mac は、主に 2 つの理由から、何十年にもわたって優れたセキュリティ記録を保持してきました。 1 つは、Mac が強力な保護機能を備えて構築されていることです。 悪用可能な脆弱性はほとんどありません .もう 1 つは、世界のほとんどの人が PC を所有しているため、サイバー犯罪者が PC に集中して取り組んでいるということです。しかし、潮流は変わりつつあり、ハッカーは Mac のセキュリティ プロトコルを回避する方法を学んでいます. 2016 年の macOS 向けの最初のトロイの木馬ランサムウェアである KeRanger と、2017 年の B