幸いにも、隣の席の同僚にあたる先輩エンジニアが持っていたらしく借りた。 そして、数日で読み終わった。斜め読みである。内容が難しすぎる。
https://www.sigbus.info/compilerbook/
ドラゴンブックを読む前に、上の rui314 さんによる作成入門を読んでいた(現時点ではステップ7まで) のだが、 こちらの説明の続きを期待して読んでいたら、あまりに難解かつ、アグレッシブに寄り道をしていくので読んでいて辟易していた。
良い本なのかもしれないが、あまりに詳しく解説しようとしすぎているようにも見える。 言いたくなるのはわかるが、そこまで話すのかと言うような。冗長な箇所が多いので読み飛ばす部分が多かった。
作成入門で簡単に言っていたことを、わざわざ難しく言い直しているかのように捉えてしまった。
それでも現時点で作成入門の方でまだ言及のない最適化周りの話はやはり読んでいて面白かった。 共通部分式とか基本ブロックとかレジスタ割り付けとかソフトウェアパイプラインとかメモリアクセスのブロック化とか ここまで頑張ってくれるなら頑張ってチューニングしなくても良いのでは?と思ったりする。 なんかグラフの話が多かった。
あとは作成入門で言及のなかった、トークナイザ/パーザー (lex/yacc) の話も出てくる。
後半の datalog 周辺のやつは全く読まなかった..。
そういえば、コンパイラといえば型!なイメージだけど、1000p くらいある中で型のセクションは 20p くらいだったような気がする。
rui314 さんのコンパイラ作成入門が進むことを期待して待つ。
そういえば、もともとこの本が読みたいとするモチベーションは、リンカ・ローダの本に感銘を受けて elf なりの オブジェクトファイルはどこからどうやってくるの?という疑問からだったが、オブジェクトファイルはアセンブリを入力として アセンブラが吐くものだったので、オブジェクトファイルはアセンブラから来るのだった。 対して、ドラゴンブックのコンパイラは狭義のコンパイラであり、ソースからアセンブリを吐くものだった。
アセンブリからアセンブラの変換に関する本、はあまり見かけないのだけど、大したことはしていないのだろうか。
同じ会社のエンジニアさんが退職なさる際の荷物整理で、いらない本としてリンカ・ローダの本をもらったので読んだ。
1冊目を読み終わって、2冊目の Linkers & Loaders をぱらぱらし始めたところ。 読了してる人いわく、1冊目は elf に焦点を絞っているので実用的だが、2冊目は COFF やら、いにしえのすでに 使われてないものにすらフォーカスを当てているので、いまさらだよな、と言っていた。
ローダといえば、最近は 30日 OS を読み終わって, mit の教育用資料の xv6 の教科書的なやつを一通り読んだので、あー、となったけど そういえばローダをする exec のやつは若干流し読みしていたので、おさらいもかねることができて、よい本だった。
実際に簡易的なリンカ、ローダの作成まで行なっているので、最適化されてないにしろ、どういうものなのかを理解することはできた。 readelf, objdump などでちらちら見かけるたくさんの section の意味 (.text, .data, .bss, .rel.XXXX, .plt, .got, .symtab, .dynsym, .symstr …) とか, リンカがすること, ローダがすること, リンカスクリプトとは, 動的リンク, 再配置, PIC, … などなど オブジェクトファイルから executable にしてロード/実行されるまでの概ねのことが理解できるようになった。
で、読了後に思ったのは、オブジェクトファイルはすでに与えられているもの、としているけど じゃあ elf 形式のオブジェクトファイルを吐くためのコンパイラは、本当はどうやっているのか? という疑問に行き着くので、結局コンパイラの本が読みたくなる、ということだった。
で、ドラゴンブックとか検索してみたら、高い…。近くの図書館にないかな、と思って探して見たけど、ない。
redis-benchmark で遊んで見る。
ryzen をほったらかしにしていたので、ちょっとベンチマークプログラムで遊んでみたくなった。
ところでベンチをとるにはベンチをとられるスペックを予め控えて置くことが重要ですよね。
CPU: Ryzen7 1700: 8 core 16 Thread:
L1 8x(32KB I Cache + 64KB D Cache)
L2 8x512KB L3 2x8MB
ベースクロック: 3GHz, ブーストクロック: 3.7GHz
L3 が 2x8MB となっているのは, ryzen が CCX (Core Complex?) という単位でダイにあることから来ていて、 CCX に物理 4 CPU があるらしく、このモデルは CCX が 2つ搭載されていることになっており、 L3 Cache は CCX につき 1 つとなるため、こうなっている。それより小さなキャッシュはコアごとに存在している。
Memory: 32GB DDR4-2133MHz, 8GBx4
redis-server: シングルスレッドで動作している。ように見える。詳しくないので知らず。 conf はなにもいじらず。
実行結果.
====== PING_INLINE ======
100000 requests completed in 0.71 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
141643.06 requests per second
====== PING_BULK ======
100000 requests completed in 0.57 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
176366.86 requests per second
====== SET ======
100000 requests completed in 0.65 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
153846.16 requests per second
====== GET ======
100000 requests completed in 0.75 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
132450.33 requests per second
====== INCR ======
100000 requests completed in 0.62 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
162074.56 requests per second
====== LPUSH ======
100000 requests completed in 0.52 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
190476.20 requests per second
====== LPOP ======
100000 requests completed in 0.54 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
186915.88 requests per second
====== SADD ======
100000 requests completed in 0.51 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
195312.48 requests per second
====== SPOP ======
100000 requests completed in 0.68 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
146627.56 requests per second
====== LPUSH (needed to benchmark LRANGE) ======
100000 requests completed in 0.74 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
134589.50 requests per second
====== LRANGE_100 (first 100 elements) ======
100000 requests completed in 1.40 seconds
50 parallel clients
3 bytes payload
keep alive: 1
99.73% <= 1 milliseconds
100.00% <= 1 milliseconds
71581.96 requests per second
====== LRANGE_300 (first 300 elements) ======
100000 requests completed in 3.69 seconds
50 parallel clients
3 bytes payload
keep alive: 1
81.65% <= 1 milliseconds
99.97% <= 2 milliseconds
100.00% <= 2 milliseconds
27100.27 requests per second
====== LRANGE_500 (first 450 elements) ======
100000 requests completed in 5.22 seconds
50 parallel clients
3 bytes payload
keep alive: 1
0.15% <= 1 milliseconds
99.78% <= 2 milliseconds
99.93% <= 3 milliseconds
99.98% <= 4 milliseconds
100.00% <= 4 milliseconds
19164.43 requests per second
====== LRANGE_600 (first 600 elements) ======
100000 requests completed in 7.20 seconds
50 parallel clients
3 bytes payload
keep alive: 1
0.04% <= 1 milliseconds
95.78% <= 2 milliseconds
99.85% <= 3 milliseconds
99.94% <= 4 milliseconds
====== MSET (10 keys) ======
100000 requests completed in 0.80 seconds
50 parallel clients
3 bytes payload
keep alive: 1
99.85% <= 1 milliseconds
100.00% <= 1 milliseconds
124843.95 requests per second
まず最初の ping は PING_INLINE と PING_BULK があるけど、違いがわからない。 そしてみなあまり関心がないのか、検索しても出てこない。payload も 3bytes とあるので、違いもわからず…。 https://redis.io/commands/ping ちなみに ping とはおそらくこれのことを指しているはず、だと思われる。
SET/GET は実行時オプションの -d
で要素サイズを指定出来るが、デフォルトでは 2byte。
2byte の要素を SET/GET するリクエストを 100k くらいやる, やつを 50 parallel clients で.. とかやるので計算しにくいな..。
マルチスレッドを考えないといけないのが。と思ったが CPU 使用率を見た感じではシングルスレッドで動作していた。
50 parallel と言っているのはグリーンスレッドみたいなことだろうか。
SET: 153846.16 requests per second GET: 132450.33 requests per second
2byte の書き込みは秒間 150k リクエストをさばけるが読み込みは 130k に下がるのがなんだか不思議だ。 いや、普通に?考えてみると single client の場合はうまくキャッシュのハードウェアプリフェッチがあたりそうだが、 平行に読み出すとプリフェッチが逆に悪さをして性能を阻害していそうな気もしなくもない。いや、さすがにそれくらい redis では気にしているか…。 毎回 uncache (non_temporal) で読み込みするようにし、redis 内部で cache 機構を持っているような気もする。まぁソース読まないとわからんけど。
Performance counter stats for 'redis-benchmark -t get -n 100000':
523.496255 task-clock (msec) # 0.983 CPUs utilized
248 context-switches # 0.474 K/sec
4 cpu-migrations # 0.008 K/sec
74 page-faults # 0.141 K/sec
Performance counter stats for 'redis-benchmark -t set -n 100000':
490.863560 task-clock (msec) # 0.997 CPUs utilized
60 context-switches # 0.122 K/sec
0 cpu-migrations # 0.000 K/sec
73 page-faults # 0.149 K/sec
perf で見てみる。なんだか set のほうが context-switch が低いな?いや誤差か。
そして以外なことに数値を上げていってもあまり性能が下がらない。8KBの書き込みでも 150K rps をいけるが
$ redis-benchmark -t set -n 100000 -d 8192
====== SET ======
100000 requests completed in 0.66 seconds
50 parallel clients
8192 bytes payload
keep alive: 1
99.98% <= 1 milliseconds
100.00% <= 1 milliseconds
151515.14 requests per second
$ redis-benchmark -t set -n 100000 -d 16384
====== SET ======
100000 requests completed in 1.01 seconds
50 parallel clients
16384 bytes payload
keep alive: 1
99.64% <= 1 milliseconds
100.00% <= 1 milliseconds
98814.23 requests per second
8KB を超えると性能が下がってきた。ここくらいまで大きくなってくると TLB ミスの回数とかいろいろ見たくなるのだけど、perf で ぱぱっと見られないので困った。いや、Ryzen のマニュアルに載ってるんだけど、前頑張ってみようとして折れたのだよね。
ちなみに get のほうもサイズを変えてやってみようとしたところ
$ redis-benchmark -t get -d 2
====== GET ======
100000 requests completed in 1.10 seconds
50 parallel clients
2 bytes payload
keep alive: 1
99.35% <= 1 milliseconds
100.00% <= 1 milliseconds
90909.09 requests per second
なぜか同じパラメータでもかなりパフォーマンスが低下していた。環境によって変わるものだろうけど set に比べ get はかなり変わっているようだった。
つかれたのでここまで。
RPGツクール MV で逆転裁判チックのポポポをつくりたいなと、ふと思い立った。
js を少しよむ. document/game/project1 にある。
game_message やら window_message やらが対象のよう。
WindowMessage
やりたいこと的には, そもそも文字の描画をゆっくりにしたい, 気持ちがある.
ゆっくりにする話がある。
ある領域を選択した状態で透明度をいじったキャンバスにお絵かきすると、それまでの透明度の状態が失われてしまうのが痛すぎる。
試しに同じことを clipstudio で試したら、こちらは問題なかった。 そりゃ, そうだよなぁ…。
このバグ?やら仕様を直して欲しい…。