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であろうとも、複雑な部分は複雑だ(日本語破綻してるな)。

tasksetコマンドの使い方

やや、リナには面白いコマンドが有るなあ。でも、これってリナ特有だから、深入りしてもしょうがない。

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;
    :

森を見るのに最適っぽい。じっくり読んでみるかな。


This year's Index

Home