Skip to content

Commit

Permalink
Fix content (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
momori256 authored Apr 25, 2024
1 parent ab80de6 commit 7ac0036
Show file tree
Hide file tree
Showing 35 changed files with 0 additions and 7,361 deletions.
307 changes: 0 additions & 307 deletions content/posts/build-logic.md

Large diffs are not rendered by default.

479 changes: 0 additions & 479 deletions content/posts/building-a-lisp-like-language-from-scratch-in-rust.md

Large diffs are not rendered by default.

657 changes: 0 additions & 657 deletions content/posts/building-a-websocket-chat-app-with-axum-and-react.md

Large diffs are not rendered by default.

197 changes: 0 additions & 197 deletions content/posts/c-server-for-apache-bench.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,203 +5,6 @@ tags = ["c", "network"]
cover.image = "https://source.unsplash.com/2YtdyCXjOuU"
+++

`ab` コマンド, つまり [Apache HTTP server benchmarking tool](https://httpd.apache.org/docs/2.4/programs/ab.html) をつかってベンチマークできる状態の HTTP サーバを C 言語で作る. なるべくシンプルに必要最小限の要素のみを持ったコードを目指す.

手堅いエンジニアは高速化のために, いきなりコードを書いたりしない. 計測できる環境を整えておかないと, 高速化をしてもその効果を測ることができない.
このサーバを出発点として手を加えて (例えばマルチスレッド化したり IO 多重化をしたりして) サーバのパフォーマンスがどのように変化するかを確かめるために使うことを想定している. ソースコード全体は https://github.com/momori256/cs2 にある.


## ソケット

`ab` を使うには HTTP を解すサーバでなければならないため, まずは TCP での通信を実装する.
ソケットプログラミングはお決まりのコードなので説明は省く. いつもお決まりを忘れてしまうので, `man getaddrinfo` の EXAMPLE をいつも参照している.

`socket`, `bind`, `listen` をして `accept` できるソケットを作成する部分は以下の関数だ.

```c
int sock_create(const char* const port, int backlog) {
typedef struct addrinfo addrinfo;

addrinfo hints = {0};
{
hints.ai_family = AF_INET; // IPv4.
hints.ai_socktype = SOCK_STREAM; // TCP.
hints.ai_flags = AI_PASSIVE; // Server.
}

addrinfo* head;
{
const int result = getaddrinfo(NULL, port, &hints, &head);
if (result) {
fprintf(stderr, "getaddrinfo. err[%s]\n", gai_strerror(result));
exit(1);
}
}

int sfd = 0;
for (addrinfo* p = head; p != NULL; p = p->ai_next) {
sfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (sfd == -1) {
continue;
}

int val = 1;
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) == -1) {
error("setsockopt");
}

if (bind(sfd, p->ai_addr, p->ai_addrlen) == -1) {
close(sfd);
continue;
}
break;
}
freeaddrinfo(head);

if (!sfd) {
error("socket, bind");
}

if (listen(sfd, backlog) == -1) {
error("listen");
}
return sfd;
}
```
## HTTP リクエストとレスポンス
`accept` して `read` すればメッセージを受信できる. 動作の確認には `telnet` のようなプリミティブなツールが役に立つ.
メッセージの受信ができるようになったので, 後は適切なレスポンスを返すだけだ. HTTP の仕様は MDN のページをいつも参考にしている. [HTTP Messages](https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages) によると, 例えば以下のようなレスポンスを返せば良さそうだ.
```
HTTP/1.0 200 OK
Content-Length: 5

hello
```
今回は受け取ったリクエストをそのまま body として返すことにする. 実際には例えば DB サーバなら, 典型的には SQL を実行してその結果を返すことになるだろう.
```c
const int BACKLOG = 10;
const int NBUF = 256;
#define CRLF "\r\n"
static void handle_request(int fd);
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "%s <PORT>\n", argv[0]);
return 0;
}
const char* const port = argv[1];
const int lfd = sock_create(port, BACKLOG);
while (1) {
const int pfd = sock_accept(lfd, NULL);
handle_request(pfd);
}
return 0;
}
static void handle_request(int fd) {
char request[NBUF];
const size_t nread = read(fd, request, sizeof(request));
if (nread == -1) {
error("read");
}
char response[NBUF];
const int nres = snprintf(
response,
sizeof(response),
"HTTP/1.0 200 OK" CRLF
"Content-Length: %lu" CRLF
CRLF
"%s",
nread,
request);
const size_t nwrite = write(fd, response, nres);
if (nwrite == -1) {
error("write");
}
if (close(fd) == -1) {
error("close");
}
}
```


早速 `ab` を使ってみる. まずはクライアント数 1, リクエスト数 1 とする.

```sh
ab -c 1 -n 1 localhost:22421/
```

リクエストは以下のような形式で body はなかった.

```
GET / HTTP/1.0
Host: localhost:22421
User-Agent: ApacheBench/2.3
Accept: */*
```

サーバが正常に動作していればベンチマークは一瞬で終わり, 以下のような結果が表示される.

```
> ab -c 1 -n 1 localhost:22421/
This is ApacheBench, Version 2.3 <$Revision: 1903618 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient).....done
Server Software:
Server Hostname: localhost
Server Port: 22421
Document Path: /
Document Length: 83 bytes
Concurrency Level: 1
Time taken for tests: 0.000 seconds
Complete requests: 1
Failed requests: 0
Total transferred: 122 bytes
HTML transferred: 83 bytes
Requests per second: 5952.38 [#/sec] (mean)
Time per request: 0.168 [ms] (mean)
Time per request: 0.168 [ms] (mean, across all concurrent requests)
Transfer rate: 709.17 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 0 0 0.0 0 0
Waiting: 0 0 0.0 0 0
Total: 0 0 0.0 0 0
```

83 bytes の HTML が 0.168 ms で返ってきたことが分かる. `ab` の引数を変えて様々な値のクライアント数, リクエスト数で試してみて, サーバのパフォーマンスを測る.
ベンチマークとしては `Requests per second`, つまり 1 秒間に何個のリクエストを処理できたかという指標がよく利用される ([Performance metrics](https://en.wikipedia.org/wiki/Web_server#Performances)).

## 結語

なるべくシンプルに, 最小限 HTTP をやり取りできるサーバを書いた. サーバのベンチマークは初めてだったが, `ab` のおかげで意外と簡単にできた.
マルチスレッド, マルチプロセス, スレッドプール, IO 多重化といった物事を試すのに, やはり定量的な指標があると良い指針となる. 実際に動かして簡単に実験できる環境を作ることがプログラミングでは重要だと思う.
+++
title = "Apache Bench でベンチマークできるミニマルな C 言語製 HTTP サーバ"
date = 2023-02-24
tags = ["c", "network"]
cover.image = "https://source.unsplash.com/2YtdyCXjOuU"
+++


`ab` コマンド, つまり [Apache HTTP server benchmarking tool](https://httpd.apache.org/docs/2.4/programs/ab.html) をつかってベンチマークできる状態の HTTP サーバを C 言語で作る. なるべくシンプルに必要最小限の要素のみを持ったコードを目指す.

Expand Down
59 changes: 0 additions & 59 deletions content/posts/computer-organization-and-desigin-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,6 @@ tags = ["cs"]
cover.image = "https://source.unsplash.com/iGheu30xAi8"
+++

[『コンピュータの構成と設計 MIPS Edition 第 6 版 上』](https://bookplus.nikkei.com/atcl/catalog/21/S70090/) はコンピュータ・アーキテクチャの教科書です.
2 名の著者パターソン&ヘネシーの名前を取ってパタへネという愛称で知られています. ヘネパタという紛らわしい愛称が付けられている『コンピュータ・アーキテクチャ』はより上級者向けの内容です.

本書はソフトウェアとハードウェアの境界付近についての本です. コンピュータの中核的な仕組みを説明し, プログラムを書く上でどうやってハードウェアを活用すればよいかという視点で語られます. コンピュータの中身を知りたい方におすすめです.


## Moore の法則の終わり

プロセッサは数百ものトランジスタが搭載された集積回路によって実現されている. トランジスタを始めとする半導体素子の材料になるのはシリコンという砂に含まれている物質だ. 円柱状のシリコン結晶を 0.1mm ほどに薄くスライスしたウェハ (wafer) を格子状にカットすると, 小さなチップができる.

チップ一つ当たりのトランジスタ数が 2 年で倍増するという, Intel の創始者の一人である Gordon Moore の予想「Moore の法則」は 50 年間に渡って正しかった. しかしいつまでも指数的な成長が続くわけではない. 消費電力の増加とともに発熱が増え, ついには冷却性能の限界を迎えたのだ. ここに来てプロセッサ開発者は方針転換を余儀なくされた. 一つのプロセッサの性能が頭打ちとなったので, 一つの CPU に複数のプロセッサを搭載することにしたのである.
マルチコア CPU の性能を引き出すにはプログラムの努力が欠かせない. 現代は, ソフトウェアエンジニアもハードのことを考えなければならない時代なのである.

## MIPS について

本書で取り扱われる MIPS という命令セットは, 命令数を抑えシンプルさを重視して設計された. フォーマットが単純であれば規則性が保たれ, 回路の実装が容易となる. そして単純な回路は消費電力を抑えらる.
スマートフォンの時代 (ポスト PC 時代) において, 消費電力は命令セットの良し悪しを決める鍵となった. MIPS と同様の思想を持って設計された ARMv8 や RISC-V が脚光を浴びるのは自然な流れであった.

MIPS のフォーマットはシンプルである. まず, 全ての命令が 32bit の固定幅を持つ. 算術/論理演算は全て二項演算で, 2 つのオペランドと 1 つの結果格納先を指定する. オペランドにメモリ上の値を指定することはできず, 事前にメモリ上の値をレジスタにロードしておかなければならない.
二項演算しかないということは NOT のような基本的な単項演算が存在しないということだ. MIPS では代わりに NOR(=Not Or) を用いて NOT を計算する (`x NOR 0 = NOT x`). 基本的な演算を削除する選択をしてまでもシンプルさを優先するのだ.
MISP に対して x86 は複雑である. 命令長からして 7-15bit と可変だし, 命令数は 1400 以上もある. 40 年以上に渡る開発の歴史の中で互換性を保ちつつ進歩を続けて来たのは驚異的だが, 実態としては複雑であるというしかないだろう. 見ようによっては混沌の中にも秩序があるのかも知れないが, とにかく一見すると複雑そうに見えるのは確かである.

## コンピュータの内側

コンピュータの内部で数がどのように表されるか, 計算はどうやって行われるのか. ブラックボックスの中を除くのが本書の趣旨だ.
2 の補数や浮動小数点はコンピュータ開発に携わるものの教養なのだろう. 初めは戸惑ったはずだが, どの教科書にも書いてあるので, 今ではすっかりお馴染みとなった.

2 の補数, 浮動小数点, 文字. データは全てビット列で表される. ビット列をどう解釈するかによって意味が変わるのだ. 同じビット列が符号付き整数の +1131443456 だったり, ヌル終端文字列の "Cpu" であったりする. 全てがビット列であるのは命令も同じだ. つまり, プログラム自体もビット列で表されるデータなのである.
実行すべきプログラムをメモリに展開して, 一つずつ読み取る. プログラムを数値や文字と言ったデータと同様に扱えるプログラム内蔵方式のおかげで, コンピュータはプログラムを容易に切り替えることが可能となり, 飛躍的な汎用性を獲得した. と言っても, プログラム内蔵方式ではないコンピュータを見たことがないので, いまいちその恩恵に対する実感が沸かないのだが.

## プロセッサの実装

OS が実行ファイルの内容をメモリに展開するところからプログラムは始まる.
命令メモリから命令をフェッチする. 命令をデコードし, 命令の種類やオペランドを取り出す. レジスタから値を読み出し, ALU で演算を行う. 最後に演算結果をレジスタやメモリに書き込む. プロセッサは大まかにこの繰り返しだ.
一つ一つの処理を実装する回路はそれほど複雑ではないので, 全てを組み合わせてできるプロセッサ全体も意外なほど単純である. 単純さを重視した MIPS の設計のおかげであろう.

処理を順番にこなす回路は分かりやすいが, 無駄が多くて遅い. 例えば ALU で計算を行っている間, 命令をフェッチする機構は暇をしている. そこで各ステップでは結果を次のステップに渡すと, 全体が終わるのを待つのではなくすぐに次の命令の処理に取り掛かる.
バケツリレーに例えられるパイプライン処理はプロセッサの根幹を成すアイデアだ. MIPS はパイプライン処理を念頭に置いて設計された. パイプライン処理によって各ステップを平行に進めると, 理想的にはステップ数倍のスループット向上が見込める. 全てのステップが満たされているとき, プロセッサはある時点でステップ数個の命令を同時に処理していると見なすことができるからだ.

しかし, パイプラインのスムーズな流れを妨げる厄介なハザードがある. 前の命令の結果に依存した計算を行う命令や, 計算が終わるまで次の命令が確定しない条件分岐である.
命令に依存関係があるとき, 結果をレジスタやメモリに書き戻すのを待たずに次のステップにデータを送るフォワーディングが有効だ. 更に高度な手法として, コンパイル時や実行時に命令の順番を変えて依存関係による待ち時間を減らす静的/動的パイプラインスケジューリングという手法もある. しかし当然の疑問として, 実行時に順序を変えると元のプログラムと結果が変わってしまうのではないだろうかという気がする. 実は最初と最後の順番は変えず, 途中の実行だけ順番を変えるのだ. イン・オーダー発行 (順番通りに命令をフェッチする), アウト・オブ・オーダー実行 (順番を変えて実行する), イン・オーダー確定 (順番通りに結果をレジスタやメモリに書き込む). これによって, 外から見た結果が元のプログラムと同じに保たれる.
条件分岐では分岐先が確定するのを待たずに, 分岐先を予想して先に実行を始めてしまう投機的実行で待ち時間を減らす. 予想が外れたときは計算が無駄になるから, 高い精度で分岐するか否かを予想することが肝心だ.
Intel Core i7 6700 での予想成功率はどのくらいだろう?50% そこそこだろうか. 実際は, なんと驚異の 97.7%(SPECCPUint2006 のベンチマーク結果による). これほどまでに高い精度を実現している秘訣の一つは動的分岐予想だ. 分岐命令ごとに前回の分岐成否を記録しておくのだ. 実際にこの方法で for ループでは最初と最後意外の予想を当てることができるから, シンプルながら強力な方法である.

## 結び

以前読んだコンピュータ・アーキテクチャの教科書『コンピュータ・システム』と共通する部分が多いと感じました. 既知の内容は多かったものの、同じ事柄に対する別の説明を聞くことで理解を深めることができると実感したので, 短いスパンで同分野の本を読むのは悪くないと思いました.

改訂を繰り返して第 6 版となった本書は時代を反映してます. スマートフォンが一年で 1500 万代 (=PC の 6 倍) も生産されるようになった現代において, プロセッサには消費電力を抑えることが強く求められるようになったことが随所で説明されます.
練習問題で興味深かったのは Google が考案した 16bit 浮動小数点フォーマット「Brain Float 16」の消費電力を計算するものです. 乗算器の消費電力は入力サイズの二乗に比例するらしく, 仮数部が 7bit(= 省略された先頭の 1 を加えて 8bit 幅) である Brain Float 16 は IEEE の 16bit フォーマットに比べて約半分(`8^2 : 11^2`), 32bit のフォーマットに比べて 11% 程度(`8^2 : 24^2`)の消費電力で済みます. 機械学習には 16bit の精度で十分らしいですから, Brain Float 16 が考案され広く使われるようになるものうなずけます.

今回読んだのは上巻ですが, 下巻はメモリ階層, ソフトウェアレベルでの並行プログラミングといったトピックが扱われます. 続けて読もうと思います.
+++
title = "『コンピュータの構成と設計 上』でソフトとハードを股に掛ける"
date = 2022-10-05
tags = ["cs"]
cover.image = "https://source.unsplash.com/iGheu30xAi8"
+++


[『コンピュータの構成と設計 MIPS Edition 第 6 版 上』](https://bookplus.nikkei.com/atcl/catalog/21/S70090/) はコンピュータ・アーキテクチャの教科書です.
2 名の著者パターソン&ヘネシーの名前を取ってパタへネという愛称で知られています. ヘネパタという紛らわしい愛称が付けられている『コンピュータ・アーキテクチャ』はより上級者向けの内容です.
Expand Down
Loading

0 comments on commit 7ac0036

Please sign in to comment.