make -j 4
腕と足の同期
ニュートン誌のバックナンバーを見てたら、無理しない運動には散歩がお勧め。効果的に行うなら、早足と普通足の3分間切り替えが効果的ですって。早足は、歩幅を普段より5cm広く取ると実現出来るそう。
前足を着地する時は、足を伸ばして状態でかかとから。後ろ足は、つま先で蹴る感じ。俯いて、100円落ちていないか探す姿勢じゃなく、はるか遠方25m先を見るようにさっそうと。
腕は直角に曲げ、後ろの人に肘鉄を食わせるぐらいにしましょう。
みんな分かりきった姿勢? まて、オイラーはずっと腕を伸ばした状態で散歩してた。これだと、振り子の腕が長くなっちゃうんで、回転が上がらないんだな。なんせ、腕と足は、同期するからね。
時速7kmウオーキングが健康促進、アシックスとカシオが新アプリ
こんなのが出てた。こういうのに頼らないと、運動出来ないのかね?
gauche tips
言う程のものじゃないけど、取り敢えずメモしとく。
disp ScmObj at gdb
昔scmで使われていた技がgaucheに適用出来無いか相談した所、おあつらえな手続を教えて頂いた。トンマな事に、 write_ss
とかを試していて諦めかけていたんだ。 Scm_Printf
なんて、gaucheの世界の物とばかり思ってて眼中に無かった。DSLだからC言語の関数と同様に使えるんだね。こういうのは、実体験して身について行くんだね。
sakae@deb:~/src/Gauche$ cat .gdbinit define disp call Scm_Printf(Scm_VM()->curout, "%S\n", $arg0) end sakae@deb:~/src/Gauche$ cat ~/.gdbinit add-auto-load-safe-path /home/sakae/src/Gauche/.gdbinit
gdbのコマンドを拡張して、それを使えるようにする。
Breakpoint 1, test_paths_setup (av=0xbffff624) at main.c:474 474 ScmObj self = Scm_ExecutablePath(); (gdb) n 475 if (SCM_STRINGP(self)) { (gdb) p self $2 = (ScmObj) 0x243ec0 (gdb) disp self "/home/sakae/src/Gauche/src/gosh"
裸のgdbコマンドprintでは、意味不明の数値が出てくるだけ。でも、gdbの拡張したコマンドdispを使えば、人間に優しく表示してくれる。これで、gauche観光が楽になるなあ。
topdf
裏でコンパイルしてる空時間を利用して、DISTを眺めていたんだ。そしたら、非公開のコマンドを発見。docの下にある説明書をpdfに変換出来るとな。
do_pdf () { ./DIST gen ./configure --enable-threads=pthreads --enable-multibyte=utf8 make (cd doc; make gauche-refe.pdf; mv gauche-refe.pdf ../../Gauche-$VERSION-ref\ e.pdf) }
その通りにやったら、1000頁越えのPDFが出来上がった。
sakae@deb:~/src/Gauche/doc$ make gauche-refj.pdf : Output written on gauche-refj.pdf (800 pages, 2377483 bytes). Transcript written on gauche-refj.log. /usr/bin/texi2dvi: luatex exited with bad status, quitting. make: *** [Makefile:105: gauche-refj.pdf] Error 1
気分をよくして、日本語もと思ってやってみたら、途中でエラーになった。残念。
make -j 4
気持を少し切り換えて、OpenBSDで遊んでみる。
最近出たPython-3.10.0をOpenBSDでコンパイルしてみる。別にPythonを使ってみようと言う訳ではなくて、make -j 4 の効き具合いを確かめてみたいから。
ob$ time make -j 4 1m18.84s real 2m38.64s user 1m33.32s system
これが4CPUの威力。って言っても評価の基準がないと比較のしようがない。
ob$ time make 2m37.82s real 0m49.52s user 1m51.14s system
ふーん、2倍か。もっと効果が顕著に出ると思ってたよ。今度は、OpenBSDのmakeをコンパイルする時間で比較。
ob$ time make -j 4 > /dev/null 0m02.43s real 0m02.08s user 0m06.56s system ob$ make clean ob$ time make > /dev/null 0m04.77s real 0m00.77s user 0m03.64s system
トータル時間で見るか、実際にCPUを使った時間で見るかによって、評価は変ってくるね。
debug flags
man firstと言う事で、確認してたら、debug用の多種のフラグが用意されてた。そのうちの-jに関係ありそうなのを試してみた。
ob$ make -j 4 -d j cc -g -O0 -I/tmp/make -I/tmp/make -DHAS_PATHS_H -DHAS_EXTENDED_GETCWD -I/tmp/make/lst.lib -MD -MP -c arch.c [91887] Running 5605 (arch.o) cc -g -O0 -I/tmp/make -I/tmp/make -DHAS_PATHS_H -DHAS_EXTENDED_GETCWD -I/tmp/make/lst.lib -MD -MP -c buf.c [91887] Running 10467 (buf.o) cc -g -O0 -I/tmp/make -I/tmp/make -DHAS_PATHS_H -DHAS_EXTENDED_GETCWD -I/tmp/make/lst.lib -MD -MP -c cmd_exec.c [91887] Running 9722 (cmd_exec.o) cc -g -O0 -I/tmp/make -I/tmp/make -DHAS_PATHS_H -DHAS_EXTENDED_GETCWD -I/tmp/make/lst.lib -MD -MP -c compat.c [91887] Running 15462 (compat.o) [91887] Process 9722 (cmd_exec.o) exited with status 0. : [91887] Running 87278 (make) [91887] Process 87278 (make) exited with status 0.
jフラグ無しでも、同じ構図になったので、余り関係無かったか。
m Print debugging information about making targets, including modification dates.
こんなフラグ付きで実行すると、実にまめに、更新日時をチェックしまくっている事が分って、褒めてあげたくなるぞ。それがmakeの本分なんて言う、味気ない事を言うな!
SEE ALSO Adam de Boor, “PMake — A Tutorial”, 4.4BSD Programmer's Supplementary Documents (PSD).
ob$ cd PSD.doc/ ob$ make *** Parse error in /tmp/make/PSD.doc: Could not find bsd.doc.mk (Makefile:11) ob$ man -Tascii ./tutorial.ms >make.tut
PSDって言う歴史書が有ったので、読もうとしたら、makefile の includeが無かった。ROFFを使って整形してるんで、manで代用出来るかなと思ったら、デケタ。
OpenBSD's Make is based upon PMake, a parallel make originally developed for the distributed operating system called Sprite.
paralled makeってのに痺れますなあ。昔からムーアの法則が破綻する事を予想してた訳だ。
並列
パラレルって業界用語では、並行なの? それとも並列が正しいの? 正確に用語を使わないと、偉い人に怒られそう。電池の並行接続とは言わないけど、並列接続なら、少学校の理科の時間に出てくるんで、並列が正しいのだろう? ちょっと自信なし。
GNU株式会社のmakeも、当然、並列makeをサポートしてる。親玉makeが居て、それがワーカーmakeを起動するらしい。やり取りはパイプを使ってるそうだ。
機構はどうなってるんだろう? 32個ものソースで出来ているんで、全部見たら日が暮れる。-jに絞ってみていくか。
void Job_Init(int maxJobs) { /* we allocate n+1 jobs, since we may need an extra job for * running .INTERRUPT. */ j = ereallocarray(NULL, sizeof(Job), maxJobs+1); for (i = 0; i != maxJobs; i++) { j[i].next = availableJobs; availableJobs = &j[i]; } extra_job = &j[maxJobs];
ふーん、指定した個数より一つ多めに確保するのね。
make -s -j 4 で、静かに実行させた
(gdb) bt #0 do_run_command (job=0x9c200e4e2a8, pre=0x7f7ffffcca50 "\250", <incomplete sequence \342\344>) at engine.c:711 #1 0x000009bfa2337714 in job_run_next (job=0x9c200e4e2a8) at engine.c:812 #2 0x000009bfa233a813 in may_continue_job (job=0x9c200e4e2a8) at job.c:660 #3 0x000009bfa233a74a in Job_Make (gn=0x9c22078ec00) at job.c:703 #4 0x000009bfa2340ff7 in try_to_make_node (gn=0x9c22078ec00) at make.c:384 #5 0x000009bfa23405ee in MakeStartJobs () at make.c:418 #6 0x000009bfa234014a in Make_Run (targs=0x9bfa2358d30 <main.targs>, has_errors=0x7f7ffffccbf6, out_of_date=0x7f7ffffccbf7) at make.c:556 #7 0x000009bfa2337f3c in engine_run_list (l=0x9bfa2358d30 <main.targs>, has_errors=0x7f7ffffccbf6, out_of_date=0x7f7ffffccbf7) at enginechoice.c:51 #8 0x000009bfa233ec3b in main (argc=4, argv=0x7f7ffffccc88) at main.c:809
run_command
にもBPを置いてから set follow-fork-mode child してchild側を追ってみると
(gdb) bt #0 run_command (cmd=0x5ac2f887400 "cc -g -O0 -I/tmp/make -I/tmp/make -DHAS_PATHS_H -DHAS_EXTENDED_GETCWD -I/tmp/make/lst.lib -MD -MP -c arch.c", errCheck=true) at engine.c:559 #1 0x000005aa21bdda8d in do_run_command (job=0x5acae9348a8, pre=0x5ac2f8781d0 "${COMPILE.c} ${.IMPSRC}") at engine.c:779 #2 0x000005aa21bdd714 in job_run_next (job=0x5acae9348a8) at engine.c:812 :
この run_command
は、各プロセスにおいて一度しか実行されない。実際は、sh -ec "cc -c xx.c" のような呼出をexecvpで行っている。ようするに、C言語のファイルをコンパイルして、オブジェクトファイルを作るって事を、一つのプロセスとして実行してるだけ。
各ソースが独立してれば、コンパイル過程を複数のCPUに分散させて実行出来る。特にCフラフラ言語みたいに、コンパイルに時間がかかる場合は有利だ。
じゃ、どうやって各CPUにプロセスを割当てる? makeのソースを見ても、forkしてるだけ。 そこでハテナである。
process control に概要が出てた。
forkでプロセスが生成される。それは具体的には、各CPUが共有してるメモリー上だ。実行準備が整ったら、順番待ちの行列に並ぶんだだ。後は、スケジューラーが、それを各CPUに割り振るって仕組みか。
そうなると、スケジューラーが動いているCPUとそれ以外のCPUっている仕事の分担が出来そう。
cpu0 at mainbus0: apid 0 (boot processor) cpu1 at mainbus0: apid 2 (application processor) cpu2 at mainbus0: apid 4 (application processor) cpu3 at mainbus0: apid 6 (application processor)
dmesgから引っ張り出してきた説明データだ。何となくスケジューラーが居座るKERNELはcpu0が担当してるっぽい。sysctlから、関係ありそうなものを抜き出すと
hw.machine=amd64 hw.model=Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz hw.ncpu=4 hw.byteorder=1234 hw.ncpuonline=4
これぐらいかな。後は、
ob$ cd /sys ob$ grep ncpuonline -rIl . ./kern/kern_pledge.c ./kern/kern_sched.c ./kern/kern_sysctl.c ./sys/sched.h ./sys/sysctl.h
kern_sched.c
struct cpu_info * sched_choosecpu_fork(struct proc *parent, int flags) { #ifdef MULTIPROCESSOR /* * Look at all cpus that are currently idle and have nothing queued. * If there are none, pick the one with least queued procs first, * then the one with lowest load average. */ /* * Peg a proc to a cpu. */ void sched_peg_curproc(struct cpu_info *ci) { struct proc *p = curproc; int s; SCHED_LOCK(s); atomic_setbits_int(&p->p_flag, P_CPUPEG); setrunqueue(ci, p, p->p_usrpri); p->p_ru.ru_nvcsw++; mi_switch(); SCHED_UNLOCK(s); } #ifdef MULTIPROCESSOR
どうも、このあたりっぽい。コメント拾い読みで、一番単純な例だったけど、CPUが空いていない場合、新なプロセスをどのCPUに割当てるかの計算方法も説明されてた。深入りすると木を見て森を見ずになりそうなので、自重した次第。いくら簡潔なOpenBSDであろうとも、複雑な部分は複雑だ(日本語破綻してるな)。
やや、リナには面白いコマンドが有るなあ。でも、これってリナ特有だから、深入りしてもしょうがない。
xv6-public
そこでMITの教材ですよ。確か複数CPUをハンドリングしてたはず。qemuの-smpオプションで、複数CPUをアサイン出来るからね。
proc.h
// Per-CPU state struct cpu { uchar apicid; // Local APIC ID struct context *scheduler; // swtch() here to enter scheduler struct taskstate ts; // Used by x86 to find stack for interrupt struct segdesc gdt[NSEGS]; // x86 global descriptor table volatile uint started; // Has the CPU started? int ncli; // Depth of pushcli nesting. int intena; // Were interrupts enabled before pushcli? struct proc *proc; // The process running on this cpu or null };
名前からして、CPU毎に持ってる構造体だね。カーネル内部でのCPU識別は、apicdeって名前か、OpenBSDと名前の付けかたが似てるな。で、この構造体は、下記で使われている。
proc.c
// Per-CPU process scheduler. // Each CPU calls scheduler() after setting itself up. // Scheduler never returns. It loops, doing: // - choose a process to run // - swtch to start running that process // - eventually that process transfers control // via swtch back to the scheduler. void scheduler(void) { struct proc *p; struct cpu *c = mycpu(); c->proc = 0; for(;;){ // Enable interrupts on this processor. sti(); // Loop over process table looking for process to run. acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->state != RUNNABLE) continue; :
森を見るのに最適っぽい。じっくり読んでみるかな。