Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyがプログラムを解釈して実行する方法

ツールについてよく知っているほど、開発者としてより良い決定を下すことができます。特にパフォーマンスの問題をデバッグする場合は、Rubyがプログラムを実行するときに実際に何をしているのかを理解しておくと便利です。

この投稿では、簡単なプログラムが字句解析され、解析され、バイトコードにコンパイルされるまでの道のりをたどります。 Rubyが提供するツールを使用して、あらゆる段階でインタープリターをスパイします。

心配しないでください。専門家でなくても、この投稿は非常に簡単にフォローできるはずです。テクニカルマニュアルというよりはガイド付きツアーです。

サンプルプログラムをご覧ください

例として、単一のif/elseステートメントを使用します。スペースを節約するために、三項演算子を使用してこれを記述します。しかし、だまされてはいけません。それは単なるif/elseです。

x > 100 ? 'foo' : 'bar'

ご覧のとおり、このような単純なプログラムでも、処理されるとかなりの量のデータに変換されます。

注:この投稿のすべての例は、Ruby(MRI)2.2で作成されています。 Rubyの他の実装を使用している場合、それらはおそらく機能しません。

トークン化

Rubyインタープリターがプログラムを実行する前に、プログラムをある程度自由形式のプログラミング言語からより構造化されたデータに変換する必要があります。

最初のステップは、プログラムをチャンクに分割することかもしれません。これらのチャンクはトークンと呼ばれます。

# This is a string
"x > 1"

# These are tokens
["x", ">", "1"]

Ruby標準ライブラリには、Rubyインタープリターとほぼ同じ方法でRubyコードを処理できるRipperというモジュールが用意されています。

以下の例では、Rubyコードでtokenizeメソッドを使用しています。ご覧のとおり、トークンの配列を返します。

require 'ripper'
Ripper.tokenize("x > 1 ? 'foo' : 'bar'")
# => ["x", " ", ">", " ", "1", " ", "?", " ", "'", "foo", "'", " ", ":", " ", "'", "bar", "'"]

トークナイザーはかなり愚かです。完全に無効なRubyをフィードしても、トークン化されます。

# bad code
Ripper.tokenize("1var @= \/foobar`")
# => ["1", "var"]
字句解析

字句解析は、トークン化を超えた一歩です。文字列はまだトークンに分割されていますが、追加のデータがトークンに追加されます。

以下の例では、Ripperを使用して小さなプログラムをLexしています。ご覧のとおり、各トークンに識別子:on_identのタグが付けられています。 、演算子:on_op 、整数:on_int 、など。

require 'ripper'
require 'pp'

pp Ripper.lex("x > 100 ? 'foo' : 'bar'")

# [[[1, 0], :on_ident, "x"],
#  [[1, 1], :on_sp, " "],
#  [[1, 2], :on_op, ">"],
#  [[1, 3], :on_sp, " "],
#  [[1, 4], :on_int, "100"],
#  [[1, 5], :on_sp, " "],
#  [[1, 6], :on_op, "?"],
#  [[1, 7], :on_sp, " "],
#  [[1, 8], :on_tstring_beg, "'"],
#  [[1, 9], :on_tstring_content, "foo"],
#  [[1, 12], :on_tstring_end, "'"],
#  [[1, 13], :on_sp, " "],
#  [[1, 14], :on_op, ":"],
#  [[1, 15], :on_sp, " "],
#  [[1, 16], :on_tstring_beg, "'"],
#  [[1, 17], :on_tstring_content, "bar"],
#  [[1, 20], :on_tstring_end, "'"]]

この時点では、実際の構文チェックはまだ行われていません。レクサーは無効なコードを問題なく処理します。

解析

Rubyがコードをより管理しやすいチャンクに分割したので、解析を開始します。

解析段階で、Rubyはテキストを抽象構文木(AST)と呼ばれるものに変換します。抽象構文木は、メモリ内のプログラムを表したものです。

一般に、プログラミング言語は、抽象構文木を記述するためのよりユーザーフレンドリーな方法であると言うかもしれません。

require 'ripper'
require 'pp'

pp Ripper.sexp("x > 100 ? 'foo' : 'bar'")

# [:program,
#  [[:ifop,
#    [:binary, [:vcall, [:@ident, "x", [1, 0]]], :>, [:@int, "100", [1, 4]]],
#    [:string_literal, [:string_content, [:@tstring_content, "foo", [1, 11]]]],
#    [:string_literal, [:string_content, [:@tstring_content, "foobar", [1, 19]]]]]]]

この出力を読むのは簡単ではないかもしれませんが、それを十分に長く見つめると、元のプログラムにどのようにマッピングされるかを見ることができます。

# Define a progam
[:program,
 # Do an "if" operation
 [[:ifop,
   # Check the conditional (x > 100)
   [:binary, [:vcall, [:@ident, "x", [1, 0]]], :>, [:@int, "100", [1, 4]]],
   # If true, return "foo"
   [:string_literal, [:string_content, [:@tstring_content, "foo", [1, 11]]]],
   # If false, return "bar"
   [:string_literal, [:string_content, [:@tstring_content, "foobar", [1, 19]]]]]]]

この時点で、Rubyインタープリターはあなたが何をしたいのかを正確に知っています。今すぐプログラムを実行できます。そして、Ruby 1.9より前は、そうだったでしょう。しかし今、もう1つのステップがあります。

バイトコードへのコンパイル

抽象構文ツリーを直接トラバースする代わりに、現在、Rubyは抽象構文ツリーを下位レベルのバイトコードにコンパイルします。

このバイトコードは、Ruby仮想マシンによって実行されます。

RubyVM::InstructionSequenceを介して、仮想マシンの内部動作を確認できます。 クラス。以下の例では、サンプルプログラムをコンパイルしてから分解し、人間が読める形式にします。

puts RubyVM::InstructionSequence.compile("x > 100 ? 'foo' : 'bar'").disassemble
# == disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
# 0000 trace            1                                               (   1)
# 0002 putself
# 0003 opt_send_without_block <callinfo!mid:x, argc:0, FCALL|VCALL|ARGS_SIMPLE>
# 0005 putobject        100
# 0007 opt_gt           <callinfo!mid:>, argc:1, ARGS_SIMPLE>
# 0009 branchunless     15
# 0011 putstring        "foo"
# 0013 leave
# 0014 pop
# 0015 putstring        "bar"
# 0017 leave

うわあ!これは突然、Rubyよりもアセンブリ言語のように見えます。それをステップスルーして、私たちがそれを理解できるかどうか見てみましょう。

# Call the method `x` on self and save the result on the stack
0002 putself
0003 opt_send_without_block <callinfo!mid:x, argc:0, FCALL|VCALL|ARGS_SIMPLE>

# Put the number 100 on the stack
0005 putobject        100

# Do the comparison (x > 100)
0007 opt_gt           <callinfo!mid:>, argc:1, ARGS_SIMPLE>

# If the comparison was false, go to line 15
0009 branchunless     15

# If the comparison was true, return "foo"
0011 putstring        "foo"
0013 leave
0014 pop

# Here's line 15. We jumped here if comparison was false. Return "bar"
0015 putstring        "bar"
0017 leave

次に、ruby仮想マシン(YARV)がこれらの命令をステップ実行し、実行します。以上です!

結論

これで、Rubyインタープリターの非常に単純化された漫画のツアーは終了です。ここで紹介したツールを使用すると、Rubyがプログラムをどのように解釈しているかから多くの当て推量を取り除くことができます。つまり、ASTより具体的になることはありません。そして、次に奇妙なパフォーマンスの問題に悩まされたときは、バイトコードを見てみてください。それはおそらくあなたの問題を解決しないでしょう、しかしそれはあなたの心をそれから取り除くかもしれません。 :)


  1. キーロガーを検出して削除する方法

    キーロガーとは? キーロガーはスパイウェアの一種で、キーボード入力を記録し、その情報を管理者に送り返します。パスワード、アカウント情報、電子メール、検索、個人情報など、入力した内容はすべて追跡されます。 キーロガーの仕事は 1 つです。コンピューターのキーストロークを記録するか、携帯電話やタブレットの指でタップすることです。匿名ブラウザを使用している場合でも、キーロガーはデバイスに直接インストールされるため、入力内容を追跡できます. キーロガーを検出する方法 コンピューターでキーロガー プログラムを検出するための警告サインは単純です。ブラウザが遅い、マウスの動きやキーストロークが遅い、カ

  2. PC 上の不要なプログラムをアンインストールする方法

    すっきりと整理された PC は、雑然とした PC よりも常に優れています。パフォーマンスを向上させ、パフォーマンスとセキュリティを維持するために、常に最適化する必要があります。不要なプログラムのクラスターにより、マシンの動作が遅くなり、ハードディスク スペースが詰まる。 したがって、清掃は必須ですが、簡単な作業ではありません。通常、不要なプログラムをアンインストールしようとすると、痕跡が残るか、さまざまな理由でアンインストールされません。 では、そのような状況であなたは何をしますか?一緒に、不要なプログラムを完全にアンインストールするさまざまな方法をチェックしてください。 ソフトウェ