プログラムが動く時って内部的にどうなってるの、ということをまとめてみました。普段は特に意識しませんが、知っていれば役立つ事があるかもしれませんので、楽しんで読んでいただければと思います!
機械語
まず大前提としてコンピュータは1と0しか理解できません。そしてこの1と0だけで書かれたものを機械語と呼びます。つまり、どんなプログラミング言語で書かれたプログラムも、最終的にはこの機械語を実行しているということです。その上でその1と0の羅列はどういうルールに従って解釈されているのかというと、それはコンピュータ内部に存在するCPUの種類によります。もっと厳密にいうと、そのCPUが採用している命令セットによります1。命令セットとは1と0の羅列をどう解釈するかを定めたルールブックのことで、例えばIntel x86やARM、MIPSのようなものがあったりします。例えば今までMacの中に搭載されていたCPUの命令セットはIntel x86でしたが、最近のM1チップではARMの命令セットが採用されています2。つまり同じ機械語でもコンピュータによって動くものと動かないものがあるという事です。
アセンブリ言語
歴史的なお話になるのですが、昔はプログラムを直接1と0の機械語で入力していました。ただしそれだと大変なので、アセンブリ言語というものが開発されました。アセンブリ言語はadd A, Bのように人間にとってわかりやすい言語になっており、基本的に機械語と完全に1対1対応しています。例えばadd A, Bは10001100101000000になる、みたいな感じです。その上でアセンブリ言語を自動で機械語に変換するソフトウェアも作られました。このソフトウェアのことをアセンブラ、アセンブラがアセンブリ言語を機械語に変換することをアセンブルと言います。また機械語とアセンブリ言語は1対1対応しているので、命令セットが違えばアセンブリ言語の書き方も、多少似た部分はあれど変わります。そのためIntel x86のアセンブリ言語、ARMのアセンブリ言語、みたいになっています。どんなコンピュータ上でも動くアセンブリ言語というものは基本的に存在しません。また、1対1対応しているということは、プログラマーは依然としてコンピュータ側に寄り添った思考をする必要があります。そのため現在はアセンブリ言語で1から組まれるプログラムはほとんどありません。
高水準プログラミング言語
上記のアセンブリ言語の問題を解決したのが高水準プログラミング言語です。いわゆるプログラミング言語と呼ばれるもので、非常に英語に近い表記になっており、同じ1つのプログラムをほぼ全てのコンピュータ上で動かすことができます。どうしてそんなことができるのかというと、コンパイラやインタプリタといった強力なソフトウェアの存在があるからです。それぞれ紹介していきます。
コンパイラ
先にコンパイラから紹介します。こちらはCやC++、Go、Rust、Swiftなどで使用されることが多く、高水準プログラミング言語をアセンブリ言語に変換するソフトウェアのことです1。ちょうどアセンブラの層が1つ上がったバージョンみたいなイメージです。高水準プログラミング言語からアセンブリ言語に変換することをコンパイルと言います。変換先のアセンブリ言語がどの命令セットに対するアセンブリ言語なのかというと、これはコンパイラによります。どういうことかというと、コンパイラや命令セットの作成者は、高水準プログラミング言語が全てのコンピュータ上で動くように、Intel x86用のコンパイラ、ARM用のコンパイラみたいにそれぞれの命令セットに対応したコンパイラを作っているということです。そのため多くの高水準プログラミング言語は、どのコンピュータ上でも動くようになっています。ただし作りたての命令セットとかだとコンパイラが対応していないことはあり得ます。もちろん一度アセンブリ言語にコンパイルしたものを、他の命令セットを持ったコンピュータ上で動かすことはできません。また言語によっては機械語にいきなり変換できたり実行までできたりするコマンドが用意されていたりもします。
インタプリタ
次にインタプリタです。こちらはPython、Ruby、PHPなどで使用されることが多いのですが、コンパイラとは少し違います。 コンパイラは受け取ったプログラムを機械語に変換しますが、インタプリタは事前に汎用的な機械語を用意しておき、受け取ったプログラムに対応する機械語を選択して実行します。 つまり実際に機械語を作り出すのではなく、受け取ったプログラムを見て、こう書いているからこの機械語を実行、こう書いてるからこの機械語を実行、みたいに内部的に条件分岐をして、事前に用意した機械語を選択して実行しているに過ぎません3。ちなみにこの機械語自体は大体C言語などで書かれたものがコンパイルされたものになります。またインタプリタもコンパイラと同じように、Intel x86用のインタプリタ、ARM用のインタプリタみたいに、それぞれの命令セットに対応したインタプリタが用意されています。
コンパイラとインタプリタのメリットとデメリット
コンパイラとインタプリタのメリットとデメリットですが、先に結論を書くとインタプリタのメリットはコンパイルをする必要がないのですぐに実行できることで、デメリットは最大パフォーマンスが低い、つまり一番速く出せるスピードが遅いということです。逆にコンパイラのメリットは最大パフォーマンスが高く、デメリットはコンパイルの時間を含めると実行までに時間がかかることです。どういうことかというと、 インタプリタとコンパイラの違いは機械語を作るか作らないか です。つまり機械語を作っているコンパイラは最初時間がかかりますし、インタプリタは既存の機械語を使用するので、ぱぱっと実行できちゃうわけです。またインタプリタはいちいち元のソースコードに対応する機械語を探してから実行する必要があるので、探す必要のない機械語を作るコンパイラと比べると、最大パフォーマンス、つまり出せる最高速度は遅いわけです。
JITコンパイラ
JITコンパイラという、インタプリタとコンパイラを両方使うことで、2つの良いとこ取りをする方式もあります。主にJavaScriptなどで使用されており、基本はインタプリタを使い、ループ文などで何度も同じプログラムが出てきたらその部分だけを機械語にコンパイルするというものです。つまり JITコンパイラは、何度も使いまわせるところだけを実行時間が速くなる機械語にして、一回しか実行されない部分はインタプリタでぱぱっと実行する というものです。これにより単純なインタプリタと比べると実行時間は速くなります。JITはJust In Timeの略で、必要なものを必要な時に必要な分だけという意味がありまして、例えばトヨタ自動車が作成したジャストインタイム生産方式もこの考え方になっていたりします。
まとめ
なんでプログラムって動いてるの、というところをまとめてみました。普段意識する必要は無いと思いますが、全体像がわからないとなんだかモヤモヤする、という方の役に立てれば嬉しいです。これからも低レイヤの支えに感謝して、楽しくプログラムを書いていきたいと思います!
余談:なぜ1と0で動くのか
余談ですが機械語よりもさらに低レイヤ、コンピュータはどうして1と0で動くのか、というとそれは1と0が電気信号である正の電圧と負の電圧を表しているからです4。つまり機械語のファイルが作られるというのは、言い換えれば1と0の電圧の集合体をコンピュータ上に保存する、という意味になるわけです。その上で電圧の正と負だけでどうやって複雑な処理ができるのかというと、それはトランジスタという、電圧を加えることで導線になったり絶縁体になったりする素子を、上手くパズルのように組み合わせることで実現しています。でじゃあトランジスタはどうやって作られているのかというと、それはシリコン(ケイ素)に少し電子を足したり抜いたりした素材でできています5。
-
パターソン&ヘネシー, コンピュタの構成と設計 第5版[上] 〜ハードウエアとソフトウエアのインターフェース〜 , 日経BP社, 2014年12月8日. ↩ ↩2
-
Mac transition to Apple Silicon, Wikipedia. ↩
-
What's the Difference Between Interpreted Languages and Compiled Languages?, Teb's Lab. ↩
-
閾値電圧を基準電圧と考えています ↩
-
OHM大学テキスト 集積回路工学, オーム社, 平成29年8月25日. ↩