第2回 コンピュータのしくみ#

この授業で学ぶこと#

この授業では、プログラミングを行うにあたり使用している環境がどれほど膨大な抽象化の下に成り立っているかをかいつまんで説明していく。

我々が直接見ている光景#

この授業を受けている間、皆さんはコンピュータの前に座って、ディスプレイ上のWebブラウザに表示されたこのテキストやGoogle Colabの画面を見て、キーボードやマウスを操作しながらプログラムを書いていくことになる。現在のほとんどのコンピュータの基本的な形はノイマン型のコンピュータアーキテクチャに準ずるものであり、以下の図[1]のように

  • 入力装置: キーボード、マウス

  • 出力装置: ディスプレイ

  • 記憶装置: メモリ、ディスク

  • CPU

から構成されている。

_images/Von_Neumann_Architecture.png

Fig. 1 ノイマン型コンピュータ#

ここで、入力装置、出力装置については実際に触れるモノがあるため想像しやすいが、記憶装置、CPUの部分がどうなっているかを想像するのは難しいと思う。 CPUは主にコンピュータの中のあらゆるものを制御したり、数値データをもとに演算をしたりする部分であり、記憶装置はその過程で一時的に情報を記憶しておく部分である。 記憶装置、CPUは目の前にある筐体(謎の箱?)の中に入っており、基本的なコンピュータの構成としては目の前に見えるものだけで完結している。 しかし、WebブラウザでGoogle Colab等を使ってプログラムを書く上ではこれでは完結しない。

Google Colabとクラウド#

初回の授業でGoogle Colab上のプログラムを実行したとき、最初のセットアップの時間を除けば一瞬で答えが返ってきたと思う。 ところで、この計算を行った実体は何であるかご存知だろうか。 一見するとGoogle Colabを表示しているWebブラウザもしくは目の前のコンピュータが計算を実行しているように見えたかもしれない。 しかし実はそうではない。

正解はGoogle Colab上で !curl ipinfo.io というコマンドを実行することで確かめることができる。 このコマンドは、コンピュータの位置情報を返してくれる。 私の環境では、次のような結果が返ってきた。

    {
     "ip": "34.125.107.162",
     "hostname": "162.107.125.34.bc.googleusercontent.com",
     "city": "Las Vegas",
     "region": "Nevada",
     "country": "US",
     "loc": "36.1750,-115.1372",
     "org": "AS396982 Google LLC",
     "postal": "89111",
     "timezone": "America/Los_Angeles",
     "readme": "https://ipinfo.io/missingauth"
    }

このうち ip の横の番号 34.125.107.162 は、IPアドレスというインターネット上での住所を表す。 IPアドレスの番号は、通信を行う全世界のネットワークで一意となるように決められており、電話番号のようなものだと思ってよい(あなたのPC、スマホ、ゲーム機などもインターネット接続時にはIPアドレスが定められている)。 さらに上記の結果からは、IPアドレスに紐づいた地理情報なども確認することができる。 countryにはアメリカ、regionにはネバダ、cityにはラスベガスと書かれている。 実は計算を行った実体は、Google社の所有するアメリカのデータセンターのコンピュータ(写真)だったのである!

つまり、一瞬の間にプログラムの情報を乗せた光信号(一部のケーブルでは電気信号)が日本のあなたのPCを出発し、太平洋の海底に張り巡らされた海底ケーブルを伝わって、アメリカのとあるコンピュータに到達し、そして実行結果を乗せて帰ってきたということである。

Google Colabのように利用者の必要とするコンピュータ資源を、必要なときに必要な量提供する仕組みのことをクラウドと呼ぶ。 私たちが検索したり、SNSや動画配信サイト、ECサイト、オンラインゲームを利用したりするとき、実は多くの場合に裏ではクラウドを利用しており、上で見たような世界規模の通信が行われている。 クラウドは私たちの社会・経済を支える重要なインフラとなりつつあり、皆さんも概要は知っておいてほしいので少し説明を加える。

まず、インターネットなどのネットワークを通じて何かしらのリクエストを受け、それに応じてサービスを提供するコンピュータのことをサーバーという。リクエストを送る側のコンピュータをクライアント 従来、サーバーと言えば対応する1台のコンピュータが存在し、各社がWebサービスを展開するには、それを自前に用意してインターネットに公開するか他社からレンタルする必要があった。 レンタルサーバーの場合は1台のサーバーを複数ユーザーで利用するのが一般的だが、あくまで1つの大きな環境を共用で使うという形式である。 一方、クラウドが行っていることは、 1台の物理的なコンピュータに複数の独立した仮想的なサーバーを作り、そのうち1つを1ユーザーに貸し出すということである。 利用者の視点に立つと、物理的な単位・実体を気にすることなく、必要なときに必要なサイズのサーバーを借りることができる。 またクラウドプロバイダーの視点に立つと、大勢の利用者にコンピュータ資源をうまく配分してコンピュータの稼働率を100%に近づけることで、利益の最大化を図ることができる。 つまりクラウドとは、コンピュータの物理的な実体を抽象化し、自在にコンピュータ資源を配分できるようにすることで、ユーザーの利便性・資源の効率性を高めた技術と言うことができる。

このようにどこかの国のどこかのデータセンターの中のコンピュータを、日常的に意識することなく利用しているというのが、現在の私たちとコンピュータの関係の一例である。

実際に何が起きているのか#

Google Colabのコードセルに 1+1 を入力したときに 2 が表示されるまでの間におよそ何が起きているのかを説明していく。なおここに書いている内容は雰囲気を理解いただくために講師が想像で記述したものであり、実際は大きく異なる可能性があることに留意いただきたい。

アプリケーションレベルの話#

目の前に見えているGoogle Colabの画面は手元のコンピュータのWebブラウザ上でクライアントとして動作しているプログラムのようなものである。 このプログラムがキーボードやマウスから画面の操作を受け付け、Google Colabに操作をリクエストしたり画面を描き直したりする。 キーボードから 1+1という入力を受け付け、キーボードやマウスの操作により画面から実行操作がなされた時にGoogle Colabに実行リクエストが送られる。 Google Colabは 1+1 と書かれたコードセルを実行し、 2を取得する

プログラミング言語処理系レベルの話#

手元のWebブラウザではGoogle Colabの画面を描画するのに、HTMLエレメントで構成されたHTMLソースによる見た目の定義とJavaScript (ECMAScript)で書かれた振る舞いの記述を用いる。然るべき操作をした時にサーバ側へリクエストを送るような振る舞いもこのレベルで記述されている。 Google Colab側では、Jupyterやデータサイエンス系のライブラリがインストールされたPython言語処理系が動いており、クライアントから送られた1+1という式を解析し、下の図のような抽象構文木を得る。1+1という式を評価し 2を得て、a2を代入する。

_images/ast_1+1.png

Fig. 2 a=1+1の抽象構文木#

計算機回路レベルの話#

1+1の演算はCPUによって行われる。CPUは膨大な数の半導体素子によって構成された巨大な計算機回路である。 実際はもっと想像も及ばないほど複雑であるが、単純化のためにここでは 1+1という足し算を計算する回路についてだけ考える。 このような回路は、論理演算を実現するAND, OR, NOT, XOR, NAND, NOR等の回路の組合せで構成される。 この回路のA=1,B=1が入力され、計算結果S=0,C=1が出力される(10進数の2は2進数で10であり、ここでは2進数で計算するため、桁上がりが発生する)

_images/halfadder.png

Fig. 3 半加算器#

2進数による情報の表現#

まずコンピュータ上のすべての情報・処理は、細かい要素に分解していけば、必ず0または1の数字の並び・操作にまで還元することができる。 例えば、私たちが普段スマホやパソコンで目にする画像や音楽、動画、文字、数字もすべて、大量の1/0の並びとして表現されている。

その基本となるのが数字の1/0での表現である。 1/0で表現された数字のことを2進数バイナリ)という。2進数では2の累乗(2, 4, 8, …)ごとに桁が増える。 一方、私たちが普段使い慣れている数字は10進数である。これは10の累乗(10, 100, 1000, …)ごとに桁が増える記法であった。 0から10までの数字について、対応する2進数の表記は以下のとおりである。

10進数

2進数

0

0

1

1

2

10

3

11

4

100

5

101

6

110

7

111

8

1000

9

1001

10

1010

2進数の桁の重みは右から順に1, 2, 4, 8なので、例えば2進数の1001は \(8\times1 + 4\times0 + 2\times0 + 1\times1 = 9\) より10進数の9に対応する。 3桁の2進数で0〜7までの8種類の数字を表現できていることに注目しよう。 \(n\) 桁の2進数で \(2^n\) 種類の数字を表現できるという点は重要である。 \(2^n\) は先ほど出てきた指数的な関係を表している。 したがって、少ない桁数で想像以上に多くのことを表現することができる。

なおコンピュータの世界では、2進数における1桁分をビット、8桁分をバイトという。 よく出てくる単位なので覚えておこう。

Tip

\(2^n\) の増加速度がいかにすごいかがわかる例として「紙を30回折ったら」というのがある。 紙の厚さを0.1mmとして30回折ると、\(0.1\times 2^{30} {\rm mm}\simeq 10^8 ~{\rm mm} = 100 ~{\rm km}\) よりその厚さはなんと100km近くになる。これは地表から宇宙に到達する厚みである! そう聞くと実際に試してみたくなるかもしれないが、現実には30回折る前に紙は破損してしまうだろう。

次に画像の1/0での表現について紹介する。 まずフィルムカメラで写真を撮ったときのことを考える。 このときフィルムカメラが行っていることは、被写体の光を集めて感光用の化学物質が塗られたフィルムに当てることで、光の情報を写しとることである。 フィルムカメラで撮られた写真は、光の位置や色をそのまま写しとるという意味でアナログ(連続的)な画像である。

一方で、スマホやパソコンに表示される画像は、光の位置や色についてとびとびの値で表現されたデジタル(離散的)な画像である。 画面の液晶ディスプレイは、ピクセルと呼ばれる小さな点が格子状に並べられてできており、虫眼鏡などで画面を拡大すれば1つ1つのピクセルを確認することができる。 また色についても、赤、緑、青の光の3原色に分解した上で、それぞれの度合いを基本的には0〜255の256段階の値で表現し、それに応じて各ピクセルが発光する。

赤、緑、青の各色の度合いについて256段階の数値で表現しているのは、まさに内部で2進数が使われていることの証左である。 というのも \(2^{8} = 256\) であり、ちょうど1バイトの2進数を使うと無駄なく表現できる範囲の数だからだ。 したがって、赤、緑、青の合わせて3バイトで1つの色を表現していることになる(この表現法をRGBという)。

したがって例えば \(100\times100\) ピクセルの画像であれば \(100\times100\times3\) バイトの2進数で表現される。 単位としてキロバイトKB)(= 1000バイト)を用いれば、30KBの画像ということである。 ただし実際には、このように単純な方式で保存していることは少なく、さらに圧縮した形式(jpg、pngなど)で保存されているため、パソコン上で容量を調べるともっと小さい値になる。

ここでは数字と画像を例にとって、データの表現方法を説明した。 バイナリレベルに立ち返って考えれば、今のコンピュータも昔のコンピュータも扱っているデータは同じである。 さらに言うと、これらバイナリのデータに対して行える操作も昔から変わっていない。 どのような計算が行えるかという意味では、コンピュータはすべて(今も昔も、またスマホもパソコンも)同じなのである。

ソフトウェアのしくみ#

プログラムやデータを総称してソフトウェアという。これは、コンピュータの物理的な構成要素を表すハードウェアに対比される言葉である。 最後にソフトウェアのしくみについて、今後の講義に関係する部分に絞って簡単に解説する。

まず、改めての説明になるが、コンピュータに行わせたい処理の命令のことをプログラムと呼ぶ。 皆さんがこれから学ぶPythonは、プログラムを記述するためのプログラミング言語の1つであり、プログラミング言語でプログラムを記述したものをソースコード(またはコード)という。 私たちが普段パソコンで操作しているブラウザや、ワープロソフト、表計算ソフト、画像編集ソフトなどアプリケーションソフトウェア(略してアプリ)は、全て何かしらのプログラミング言語を使って作られている。 Pythonを学び続けることで原理的にはこういったソフトウェアも作れるようになる。

コンピュータに行わせたい処理には、しばしばハードウェアの操作が含まれる。 このときソフトウェアとハードウェアの間に立って、ハードウェアの操作を仲介する役割を持つのがOSオペレーティングシステム)である。 OSはハードウェアの詳細を抽象化により隠して、裸のコンピュータを人間に使いやすい姿に見せる役割を持っている。 例えば、ハードウェアの基本的な制御はシステムコールというプログラムとしてOSから提供されており、Pythonでも内部で利用されている。 キーボードの入力を読み取るという処理一つとっても、OSなしでは相当に複雑な手続きが必要であるが、OSのおかげで単にシステムコールを呼び出すだけで済む。

次に、Pythonプログラムをコンピュータが実行するしくみを解説する。 プログラムを実行する主体であるプロセッサは、Pythonのソースコードをそのままでは理解することができない。 プロセッサが直接理解できるプログラムは、 1/0の数字の並びで表される機械語である(データだけでなくプログラムも最終的にはバイナリになるのだ!)。 Pythonでは、以下の流れでソースコードから機械語への変換が行われる。 Pythonを実行すると、まずソースコードはバイトコードという中間データに変換される。 そしてPythonの仮想マシンがバイトコードを解釈して機械語を生成し、プロセッサに渡されて実行される。

機械語はプロセッサと強く結びついた言語であり、同じバイナリでもプロセッサごとに意味合いが変わる。 一方で、Pythonのソースコードの書き方はプロセッサなどの実行環境に依存していない。 このようなことが可能になっているのは、Pythonの仮想マシンが環境ごとの違いを吸収してくれているからである。 つまり、ソースコードやバイトコードは環境に依存しないが、Pythonの仮想マシンは機械語を生成する際、環境に合わせてバイナリを生成しているのである。 これもまた抽象化の一つであり、これにより私たちはプロセッサなどの環境の違いを意識することなく生産的にプログラムを作成できるほか、ソースコードの別の環境への移植が容易になっている。

ここではソフトウェアのしくみについてその一部分を簡単に解説した。ソフトウェアは様々な側面で抽象化が行われ、特に物理的な詳細をユーザーから隠すように作られている。 抽象化された論理的なしくみはあまり変わらないからこそ、私たちは知識のアップデートに追われることなくコンピュータの物理的な性能向上の恩恵を受けられるのである。