「プログラムはなぜ動くのか 第3版 知っておきたいプログラミングの基礎知識」を読んだのでまとめてみました!
著者:矢沢 久雄
出版社:日経BP
発売日:2021/5/13
どんな本か
プログラミングしたコードがどうやって動いているかが掴めます。
いわゆる低レイヤーで複雑そうなところをわかりやす〜く教えてくれます。
C言語やアセンブリ言語が出てきますが、きちんと説明してくれます。
簡単なプログラムを書いたことがあれば問題なく読めると思います。
第二版が2007年発売、14年の時を経て2021年の5月に第三版が発売されたようです。
追加された12章では機械学習が追加されており、時代の流れもしっかり捉えてくれる安心感。
今が読み時ですね!
なぜ読んだか
よくよく考えると普段書いているコードって、どうやってコンピュータを動かしているの?という疑問が湧いたので読みました。
ハードとかCPUとかOSとか、、少しは意識できるようになった気がします。
基礎は大事!
目次
1.CPUとは何か
2.2進数
3.メモリ
4.データの圧縮
5.プログラムの動作環境
6.OS
7.アセンブリ言語
8.機械学習
1-CPUとは何か
CPU:Central Processing Unitの略。
日本語に訳すと中央処理装置。
簡単に表現するとコンピュータの脳。
つまり、CPUを理解できればコンピュータの処理の流れが見えてくる。
実行の流れ
1.プログラムを機械語(0,1で表現された言語)にコンパイル
2.メモリに保管
3.CPUが実行
STEP1
実はCPUは機械語しか理解できない。
そのため、最初に普段書いているコードは実行時に機械語に変換される。
*機械語で表現されたコードをネイティブコードと言う。
ちなみにコードを1行ずつコンパイルする仕組みをインタプリンタ方式と呼び、言語にはPythonやRuby,PHPなどがある。
一方で全てを一括コンパイルする仕組みをコンパイル方式と呼びC言語などがある。
これがC言語が早い理由。
STEP2
CPUはメモリに保管されたコードしか実行してくれない。
そのため、コンパイルされたネイティブコードをメモリに移す。
STEP3
いよいよCPUが処理を実行する。
メモリに移されたコードをCPUのレジスタと呼ばれる記憶領域に移す
その後にレジスタにある処理を実行する。
*レジスタ
高速に読み書きできる記憶装置
メモリと一緒やんと思ったけどCPUが実際に使ってるのはレジスタ。メモリに比べてさらに容量が小さい。
関数処理の流れ
まずコール命令が実行される。
コール命令では、関数が実行された後の処理のアドレスをメモリーのスタックに入れる。
関数の実行が終わるとリターン命令でスタックからアドレスを取り出しCPUのカウンタに次のアドレスを設定する。
POINT
CPUの処理自体は演算処理やデータの読み書きがメインで超シンプル。
2-2進数
CPUは2進数しか理解できないから2進数を知ればCPUの演算処理が見えてくる。
なぜ2進数か?
ICが電圧の大きさで判断するつまり2つの状態しか判断できないから必然的に2進数になった。
マイナスの表現
CPUは引き算をマイナスの足し算として表す。
ここで重要なのが2進数のマイナス表現法。
実は正の数の補数をとって最上位桁を1にしたものをマイナスと表現している。
*補数:反転させて1を足したもの
例)-5を表現したい
正の数5(00000101)の補数は
反転させた11111010に1を足したもの
=> 11111011
00000101 + 11111011 = 0
小数の表し方
コンピュータは小数を浮動小数表現する。
浮動小数は2の指数で表せる値の範囲しか取れないから計算間違えを引き起こすことがあるので注意が必要。
浮動小数は符号部・仮数部・指数部からなる。
1.符号部
1bitを使ってマイナスorプラスを表す
2.仮数部
仮数部は小数点以上の値を1に固定する。
簡潔には整数部は絶対1になるルールがある。
絶対1なので整数部は省略される
3.指数部
イクセス表現を使う。
イクセス表現は中央値をゼロと考えることで符号ビットを使わずにマイナスを表せる。
例)1-10まででマイナスを表現する。
中央値である5をゼロと考える。
中央値からの距離で考える
10 = +5
0 = -5
例)
符号部:0
仮数部:10000000000000000000000
指数部:01111110
符号部が0から正の数
仮数部は整数部の1が省略されているから1.100...
指数部は最大値255(11111111)の中央値である127(01111111)がゼロと考えると
011111110 - 01111111 = -1
1.1 の 2^-1乗であることがわかる
1.1を10進数で表すと1.5だから0.75を表している。
POINT
金額計算など正確性が求められる計算は整数に直してから計算するのが良。
3-メモリー
CPUは扱うデータはメモリーに入っている必要がある。(ストアドプログラム方式)
メモリーを意識することが大事。
プログラミング時にメモリーを意識することがすくないが実はデータ型の定義がメモリの占有するサイズを示している。
中でも配列は一気に変更するメモリ容量を決めているデータ型
OOPだとデータ型はobject
オブジェクトに変数を入れていく
つもり実態がオブジェクト
注意
仮装記憶でディスクをメモリーのように使っても結局メモリに格納しないとCPUが実行できない。
仮装記憶を使うと遅くなるので
仮装記憶の容量はメモリと同等か2倍以内にする。
早くするには?
1.DLLファイルで関数を共通化する
2.stdcallを関数の前に置く。
基本、呼び出し側でメモリの解放処理が実行される、stdcallを用いると、呼び出された側で解放処理ができるので、重複を解消できる。
4-データの圧縮
zipなどでデータの圧縮を行う機会が多くなっている。
当書ではランレングス符号化とハフマン符号化という二つの圧縮アルゴリズムが紹介されていた。
ランレングス符合:同じ文字列の連続を圧縮
例)aabbbbc => a2b4c1
文章ファイルの中で同じ文字列が続くことは少ないので圧縮後のサイズが大きくなる場合がある。
ハフマン符合:文字列の出現回数が多い順にbitを割り当てる。割り当てにはハフマン木を用いる
例)aaaabbbbbbcccd
b = 0
a = 1
c = 10
d = 11
5-プログラムの動作環境
環境とはOSとハードウェアを指す。
OSがなぜ動作環境の要因か
アプリはAPI(アプリケーションからOSへの命令のやり方)に依存しているから。
普段意識しないが出力や日付データ取得などはOSのAPIを使っている。
ハードウェアがなぜ動作環境の要因か
ハードウェアのCPUに合わせたコンパイラに依存するから。
CPUごとに理解できるネイティブコードが違う。
実行環境としてのJava
言語のJavaではなく実行環境としてJavaというものが存在する。
Javaを使えば、異なるOSやハードウェアでもプログラムを動かすことができる。
Javaの仕組み
STEP1
高級言語で記述したファイルをJavaがバイトコードと呼ばれる中間ファイルにコンパイルする。
STEP2
Java仮想マシンがバイトコードを逐次ネイティブコードに変換してOSとハードへの依存を解消する
6-OS
ハードウェアを動かしているのはOSだからOSに対してアプリを作っている意識を持つことが重要。
もしアプリが思い通りに動かなかった時はハードウェアの問題ではなくOSの使い方が問題かも?
(ミドルウェア、デバイスドライバ、GUI)など
OSがやってくれること
1.APIを提供してOS操作を楽にしている
2.GUIの提供
3.ディスプレイに表示されているものをそのまま出力できる。(WYSIWYG)
4.複数のプログラムを実行できる(マルチタスク)
5.ネットワーク機能やデータベース機能といったミドルウェアの提供。
6.デバイスの接続が自動で設定される。(プラグ&プレイ)
7-アセンブリ言語
アセンブリ言語はネイティブコードと一対一対応したニーモニックを使う言語
ニーモニック:ネイティブコードの処理を表す簡単な略語。
ニーモニックをつけることで人間が理解できるようにしている。
アセンブリ言語はオペコード(命令部)とオペランド(命令の対象)からできている。
それぞれのニーモニックの例
movl $456, 4(%esp)
movl:オペコード。$456を4(%esp)に格納する命令
$456:オペランド。456というデータ
4(%esp):オペランド。(%esp)でスタックポインタと呼ばれるレジスタを表す。最初の4はespレジスタの値に4を加えたアドレスという意味。
8-機械学習
名前の通り、機会に学習させる。
機械学習には大きく教師あり学習・教師なし学習・強化学習がある。
当書では教師あり学習のうち、分類問題(例.手書き数字の認識)でSVMアルゴリズムが紹介されていた。
SVM
略さないとサポートベクトルマシン。
高速で少ないデータ量でも高性能を示す優秀なアルゴリズムらしい。
例
目的:犬と猫の判別をする境界線を求める。
特徴:人懐こさと飼いやすさの二つ。
境界と最も距離が近い犬と猫のデータを取得する。
このデータと境界線の距離の和が最大となる直線を得る