NetBSDのマルチスレッド機能を使ってみた話

これはNetBSD Advent Calendar 2014の21日めの記事です。

はじめに

最近のパソコンはもうあたりまえにマルチコアなCPUが使われています。 2つとか4つとかそれ以上とか。 それどころか携帯電話というかスマートフォンでさえマルチコアが当たり前です。 一方で「マルチコアを効率良く使うにはアプリケーションがマルチスレッドで動作する必要がある」なんて話もよく聞きます。

気づけば私が普段使っているNetBSD環境もマルチコアのCPUが使われています。 そんなわけでこの環境でマルチスレッド動作しマルチコアで動くプログラムを作って確認してみようと思った話です。

バリバリにプログラム書いてる人には退屈で当たり前な話ですがご容赦くださいまし。

NetBSDのマルチスレッド

NetBSDはいわゆるUNIX系のOSです。この世界ではPOSIX Thread libray - pthreadという標準仕様があります。 pthreadはアプリケーションを作る時の(主にC言語用)プログラムライブラリの仕様です。もちろんNetBSDでもこの仕様に対応しています。

NetBSDのman pageでpthread(3)という項目や、市販の書籍を見たりするとこのライブラリを使うとマルチスレッドで動作するアプリケーションが作れるみたいです。

私の手元にはO'REILLYのPthreadsプログラミングという書籍があります(この本は1998年と15年以上も前に出たものだったりします。最近はもっといい本があるかもしれません)。この本をチラ見しつつ、man pageも読みつつ実際にマルチスレッドを動かしてみます。

サンプルを作ってみる

テストに使う環境は以下の環境です。

無駄にメモリ多い感じですがそれはそれとして、低価格だけど4コアなやつです。 NetBSD起動時には以下の様に表示され、4コアあることがわかります。

cpu0 at mainbus0 apid 0: AMD Athlon(tm) 5350 APU with Radeon(tm) R3     , id 0x700f01
cpu1 at mainbus0 apid 1: AMD Athlon(tm) 5350 APU with Radeon(tm) R3     , id 0x700f01
cpu2 at mainbus0 apid 2: AMD Athlon(tm) 5350 APU with Radeon(tm) R3     , id 0x700f01
cpu3 at mainbus0 apid 3: AMD Athlon(tm) 5350 APU with Radeon(tm) R3     , id 0x700f01

とりあえず先のPthreads本を見ながらこれ以上無いすげぇ簡単なプログラムを作ってみます。

#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#include <pthread.h>

#define NUMTHREAD 4

unsigned int count[NUMTHREAD];

void *thread_proc(void *p) {
        int num = *(int *)p;
        while(1) {
                if (count[num]==0xffffffff)
                        printf("%d thread:overflow\n",num);
                count[num]++ ;
        }
}

int main() {
        int ret;
        int i;
        int tnum[NUMTHREAD];
        pthread_t pt[NUMTHREAD];

        for (i=0;i<NUMTHREAD;i++) {
                tnum[i]=i;
                count[i]=0;
                ret = pthread_create(&pt[i], NULL, thread_proc, &tnum[i]);
                if (ret !=0)
                        err(1, "pthread_create");
        }
        for (i=0;i< NUMTHREAD;i++ )
                pthread_join(pt[i],NULL);
}

こんな処理普通はあるわけないのですが、thread_proc()という関数で単に無限ループでカウントアップするルーチンがあり、これをmain関数内でNUMTHREAD(=4)回ループしてスレッドを4個生成して動かしています。 真っ当な処理ではスレッド間でデータのやりとりや、そのための同期処理とかも必要になるはずなのですがここではとにかくスレッドを動かすだけにしてます。

ではこれをコンパイルしてみましょう。普通にccでコンパイルします。

% cc -o threadtest trehadtest.c
/var/tmp//ccoB22ww.o: In function `main':
trehadtest.c:(.text+0xb7): undefined reference to `pthread_create'
%

リンク時にpthread_createが見つからないというエラーが出てしまいました。

実はpthreadを使用するプログラムをコンパイルする場合はコンパイルオプションに-pthreadを付けなければならないようです。

% cc -o threadtest -pthread trehadtest.c
%

今度は正しくできたようなので実行してみます。

% ./threadtest
このプログラムは単純に無限ループで終了条件がありませんので、終わる場合はキーボードからControl-Cを入力する必要があります。

プログラム実行中の様子を他のセッションから確認してみます。別のセッションからtop(1)コマンドを実行します。

% top -1 -t
load averages:  3.99,  3.46,  2.18;               up 0+16:21:57        01:47:47
86 threads: 20 idle, 1 runnable, 60 sleeping, 1 zombie, 4 on CPU
CPU0 states:  100% user,  0.0% nice,  0.0% system,  0.0% interrupt,  0.0% idle
CPU1 states: 92.1% user,  0.0% nice,  0.0% system,  0.0% interrupt,  7.9% idle
CPU2 states:  100% user,  0.0% nice,  0.0% system,  0.0% interrupt,  0.0% idle
CPU3 states:  100% user,  0.0% nice,  0.0% system,  0.0% interrupt,  0.0% idle
Memory: 4496M Act, 164K Inact, 2188K Wired, 8948K Exec, 4475M File, 11G Free
Swap: 16G Total, 16G Free

  PID   LID USERNAME PRI STATE      TIME   WCPU    CPU NAME      COMMAND
 3885     5 oshima    27 CPU/3      8:38 93.55% 93.55% -         threadtest
 3885     4 oshima    27 CPU/2      8:41 92.63% 92.63% -         threadtest
 3885     3 oshima    27 CPU/0      8:36 81.88% 81.88% -         threadtest
 3885     2 oshima    27 RUN/0      8:31 79.98% 79.98% -         threadtest
(略)

threadtestのプロセスはLIDが1から5まであり、そのうち2から5の4つがほぼ全てのCPUを使ってます。 これは作成した無限ループするスレッドと一致します。 タイミングによってCPUも時々4つとも100%になったり、ガクッと一つが減ったりもするようですが、ちゃんと複数のCPUにわかれて動いているようです。

4つのCPUで4つのスレッドが動いているので都合がいいのですが、ではもっとたくさんにするとどうなるか試してみます。 先のプログラムのNUMTHREADを4から10にしてみます。

load averages:  5.64,  4.23,  3.04;               up 0+16:29:36        01:55:26
92 threads: 20 idle, 7 runnable, 60 sleeping, 1 zombie, 4 on CPU
CPU0 states: 99.8% user,  0.0% nice,  0.0% system,  0.0% interrupt,  0.2% idle
CPU1 states: 99.8% user,  0.0% nice,  0.2% system,  0.0% interrupt,  0.0% idle
CPU2 states:  100% user,  0.0% nice,  0.0% system,  0.0% interrupt,  0.0% idle
CPU3 states:  100% user,  0.0% nice,  0.0% system,  0.0% interrupt,  0.0% idle
Memory: 4496M Act, 164K Inact, 2188K Wired, 8948K Exec, 4475M File, 11G Free
Swap: 16G Total, 16G Free

  PID   LID USERNAME PRI STATE      TIME   WCPU    CPU NAME      COMMAND
21950     9 oshima    26 RUN/2      0:12 48.90% 35.60% -         threadtest
21950     4 oshima    26 CPU/2      0:11 44.14% 32.13% -         threadtest
21950     6 oshima    25 RUN/1      0:10 42.06% 30.62% -         threadtest
21950    11 oshima    26 RUN/0      0:11 40.79% 29.69% -         threadtest
21950     8 oshima    27 RUN/3      0:09 39.71% 28.91% -         threadtest
21950     5 oshima    27 RUN/3      0:10 37.57% 27.34% -         threadtest
21950    10 oshima    25 CPU/3      0:09 35.22% 25.63% -         threadtest
21950     3 oshima    27 RUN/0      0:09 34.68% 25.24% -         threadtest
21950     2 oshima    26 CPU/0      0:08 30.79% 22.41% -         threadtest
21950     7 oshima    27 RUN/3      0:08 29.72% 21.63% -         threadtest
(略)

LIDが11まで増えました。CPUコア数以上のスレッドを作るとちょっと使用量はバラつきがあるみたいですが、とりあえずはすべてのスレッドが平行して動いているようです。また、CPU毎の使用率は常にほぼ100%となり、マルチコアがちゃんと使われているようです。

他の環境(アーキテクチャ)で試してみる

さて、せっかくのNetBSDで動かすのですから、同じプログラムを別のCPUを使ったマシンで動かしてみましょう。

残念ながらamd64/i386以外にマルチプロセッサな環境を持ち合わせていませんので、試したのは以下のものです。

もちろん1コアの環境です。しかも超マイナーなNetBSD/sh3アーキテクチャです。さらに事情により1年前のNetBSD-currentとOSが微妙な感じですが、とりあえず気にしないことにしておきます。

さっさとコンパイルして実行してみました。topの出力は以下の様になりました。

load averages:  7.96,  2.78,  1.12;               up 0+16:27:50      02:31:03
57 threads: 5 idle, 10 runnable, 41 sleeping, 1 on CPU
CPU states: 99.7% user,  0.0% nice,  0.3% system,  0.0% interrupt,  0.0% idle
Memory: 22M Act, 11M Inact, 5148K Wired, 5956K Exec, 21M File, 1456K Free
Swap: 1007M Total, 1007M Free

  PID   LID USERNAME PRI STATE      TIME   WCPU    CPU NAME      COMMAND
 1983     8 oshima    32 RUN        0:09  9.95%  9.86% -         threadtest
 1983     9 oshima    30 RUN        0:08  9.85%  9.77% -         threadtest
 1983    10 oshima    32 RUN        0:09  9.65%  9.57% -         threadtest
 1983     2 oshima    32 RUN        0:09  9.51%  9.42% -         threadtest
 1983     5 oshima    32 RUN        0:09  9.36%  9.28% -         threadtest
 1983     3 oshima    32 RUN        0:09  9.26%  9.18% -         threadtest
 1983     6 oshima    32 RUN        0:09  9.21%  9.13% -         threadtest
 1983    11 oshima    31 RUN        0:09  8.77%  8.69% -         threadtest
 1983     7 oshima    32 RUN        0:09  8.57%  8.50% -         threadtest
 1983     4 oshima    31 RUN        0:09  8.52%  8.45% -         threadtest
(略)

さすがに1コアしかないので、10スレッド動かすとそれぞれのスレッドのCPU使用率は10%未満になりますが、それでもちゃんと動いているようです。

さらに別のアーキテクチャ(m68kのNetBSD/x68kとか)でも試してみましたが、ほとんど同じなので出力結果は割愛します(全体として実効速度がとても遅いだけ)。

試した結果

NetBSDでpthreadを使ったプログラムは、マルチコアな環境で動かすとちゃんとマルチコアで実行されるみたいです。 なので、今時のマルチコアマシンでは有効に機能しそうです。 また、シングルコアな環境でも動きますがその場合は1つのCPUコアを適当に時分割して平行して動いてくれるようです。

またamd64といった通常よく使う一般的なアーキテクチャだけじゃなく、謎マシンと呼ばれるようなマイナーアーキテクチャマシンでもそのまま動くことがわかりました。

もっとスレッド

CPUコア以上のスレッドが全力で動いている場合にも適当に実行を切り替えてくれるみたいですが、なんとなく実行頻度にバラつきがあるような気がします。このあたりどうやって切り替わっているのか気になります。切り替えの頻度とか、そのオーバーヘッドとか確認してみたかったりします。

また、実際のプログラムでは複数のスレッド間で同期処理やデータの共有/やりとりをする必要があります。そのためのpthread_mutex(4)とかpthread_cond(4)とかさらにはmutex_spin(4)やらsem_wait(4)やらを色々使うことになるのですか、こういうの使った場合にもどんな動きになるのか確認してみたいところです。どういう確認方法がいいのかよくわかってませんが。

マルチスレッドの効能はマルチコアだけじゃなくDISK I/OやNetwork I/O のように「CPUの実行速度に比較して遅い、または待ちが必要」な処理と他の処理を同時に動かす必要がある場合にも有効と言われています。このあたりもわかりやすく確認してみたいです。これで効率よく実行できればシングルコアな謎マシンでも恩恵を受けられるのでちょっと期待したいところです。

おまけ1: コンパイルオプション

コンパイル時に-pthreadというオプションを付けていましたが、実はmanpageには、-lpthreadと書いてあったりします。これはライブラリであるlibpthread.soをリンクするという意味になります。-pthreadオプションをつけた場合には内部的に-lpthreadも自動的に付けてくれているのだと思います。 が、この2つ、1文字多いだけですが他に違いはないのでしょうか。

ということで両者のコンパイル時に-vを付けて確認してみます。

まず-pthreadの場合です。

Using built-in specs.
COLLECT_GCC=cc
Target: x86_64--netbsd
Configured with:
(略)
Thread model: posix
gcc version 4.8.3 (nb1 20140527) 
COLLECT_GCC_OPTIONS='-v' '-o' 'threadtest' '-pthread' '-mtune=nocona' '-march=x86-64'
 /usr/libexec/cc1 -quiet -v -D_REENTRANT -D_PTHREADS threadtest.c -quiet -dumpbase threadtest.c -mtune=nocona -march=x86-64 -auxbase threadtest -version -o /var/tmp//cc9Yv16W.s
GNU C (nb1 20140527) version 4.8.3 (x86_64--netbsd)
        compiled by GNU C version 4.8.4 20141009 (prerelease), GMP version 5.1.3, MPFR version 3.1.2, MPC version 1.0.1
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
#include "..." search starts here:
#include <...> search starts here:
 /usr/include/gcc-4.8
 /usr/include
End of search list.
GNU C (nb1 20140527) version 4.8.3 (x86_64--netbsd)
        compiled by GNU C version 4.8.4 20141009 (prerelease), GMP version 5.1.3, MPFR version 3.1.2, MPC version 1.0.1
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 6c545c72a86eb617b8e6548b191c9444
COLLECT_GCC_OPTIONS='-v' '-o' 'threadtest' '-pthread' '-mtune=nocona' '-march=x86-64'
 as -v -o /var/tmp//ccEE9PT7.o /var/tmp//cc9Yv16W.s
GNU assembler version 2.23.2 (x86_64--netbsd) using BFD version (NetBSD Binutils nb1) 2.23.2
COMPILER_PATH=/usr/libexec/
LIBRARY_PATH=/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-o' 'threadtest' '-pthread' '-mtune=nocona' '-march=x86-64'
 ld --eh-frame-hdr -dc -dp -e _start -dynamic-linker /usr/libexec/ld.elf_so -o threadtest /usr/lib/crt0.o /usr/lib/crti.o /usr/lib/crtbegin.o /var/tmp//ccEE9PT7.o -lgcc --as-needed -lgcc_s --no-as-needed -lpthread -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/crtend.o /usr/lib/crtn.o

次に-lpthreadだけの場合です。

Using built-in specs.
COLLECT_GCC=cc
Target: x86_64--netbsd
Configured with:
(略)
Thread model: posix
gcc version 4.8.3 (nb1 20140527) 
COLLECT_GCC_OPTIONS='-v' '-o' 'threadtest' '-mtune=nocona' '-march=x86-64'
 /usr/libexec/cc1 -quiet -v threadtest.c -quiet -dumpbase threadtest.c -mtune=nocona -march=x86-64 -auxbase threadtest -version -o /var/tmp//cczUfETT.s
GNU C (nb1 20140527) version 4.8.3 (x86_64--netbsd)
        compiled by GNU C version 4.8.4 20141009 (prerelease), GMP version 5.1.3, MPFR version 3.1.2, MPC version 1.0.1
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
#include "..." search starts here:
#include <...> search starts here:
 /usr/include/gcc-4.8
 /usr/include
End of search list.
GNU C (nb1 20140527) version 4.8.3 (x86_64--netbsd)
        compiled by GNU C version 4.8.4 20141009 (prerelease), GMP version 5.1.3, MPFR version 3.1.2, MPC version 1.0.1
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 6c545c72a86eb617b8e6548b191c9444
COLLECT_GCC_OPTIONS='-v' '-o' 'threadtest' '-mtune=nocona' '-march=x86-64'
 as -v -o /var/tmp//ccX49pjn.o /var/tmp//cczUfETT.s
GNU assembler version 2.23.2 (x86_64--netbsd) using BFD version (NetBSD Binutils nb1) 2.23.2
COMPILER_PATH=/usr/libexec/
LIBRARY_PATH=/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-o' 'threadtest' '-mtune=nocona' '-march=x86-64'
 ld --eh-frame-hdr -dc -dp -e _start -dynamic-linker /usr/libexec/ld.elf_so -o threadtest /usr/lib/crt0.o /usr/lib/crti.o /usr/lib/crtbegin.o /var/tmp//ccX49pjn.o -lpthread -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/crtend.o /usr/lib/crtn.o

-pthreadの場合、リンク時のldで-lpthreadが付いている以外に、コンパイル時のcc1に-D_REENTRANT -D_PTHREADSという2つのdefineが追加されています。/usr/include/以下のヘッダをgrepで検索するとstdlo.hやstdlib.hでも参照しているところがあるようです。 このことからpthreadライブラリをリンクするのではなくコンパイル時に-pthreadオプションを付けたほうが良さげです。

おまけ2: NetBSDのpthreadの歴史?

もう6年近く前になりますが、2009年4月のNetBSD 5.0のリリース時、リリースアナウンスに主な変更点としてマルチプロセッサ対応の劇的な改善、というのがありました。

NetBSD 5.0 features greatly improved performance and scalability on modern multiprocessor (SMP) and multi-core systems. Multi-threaded applications can now efficiently make use of more than one CPU or core, and system performance is much better under I/O and network load, benefiting, for example, server, scientific, and software development workloads.

[以下適当俺様訳]

NetBSD 5.0は現代的なマルチプロセッサ(SMP)/マルチコアシステムにおいて劇的なパフォーマンスとスケーラビリティの改善を行いました。マルチスレッドアプリケーションは複数のCPU/コアを効率的に使用可能になり、I/Oやネットワーク負荷でのシステムパフォーマンスも向上し、例えばサーバや技術計算、ソフトウェア開発時作業に有効です。

ということでせっかくなのでNetBSD 4.xとNetBSD 5.xでテストプログラムを動かして比べてみました。実行したのは同じ4コアのAthlon 5350マシンです。

本題と関係ありませんがUSBメモリにインストールして差し替えているのでハードディスクにある環境こわさないので便利ですね

NetBSD4系はすでにサポート切れですが、その最終リリースは2008/10/14にリリースされた4.0.1らしいのでこれで動かしてみます。その結果は以下

load averages:  0.69,  0.51,  0.26    up 0 days, 10:00   08:59:01
31 processes:  29 sleeping, 2 on processor
CPU0 states:  0.0% user,  0.0% nice,  0.0% system,  0.0% interrupt,  100% idle
CPU1 states:  100% user,  0.0% nice,  0.0% system,  0.0% interrupt,  0.0% idle
CPU2 states:  0.0% user,  0.0% nice,  0.0% system,  0.0% interrupt,  100% idle
CPU3 states:  0.0% user,  0.0% nice,  0.0% system,  0.0% interrupt,  100% idle
Memory: 16M Act, 972K Wired, 3872K Exec, 9284K File, 15G Free
Swap: 129M Total, 129M Free

  PID USERNAME PRI NICE   SIZE   RES STATE      TIME   WCPU    CPU COMMAND
  601 oshima    53    0  2128K 1132K CPU/1      0:43 99.39% 87.84% threadtest
   14 root      18    0     0K   19M syncer/0   0:02  0.00%  0.00% [ioflush]
(略)

CPU4個あるのに1個しか使ってくれません。また、NetBSD 4.0.1のtop(1)はスレッド表示に対応していないようなので、ps(1)の-sオプションを使ってみます。

% ps -as
UID PID PPID   CPU LID NLWP PRI NI  VSZ  RSS WCHAN STAT TTY      TIME COMMAND
  0 598    1     0   1    1  10  0  152 2316 wait  I    ttyE0 0:00.01 login 
100 660  598     0   1    1  18  0  232 1068 pause S    ttyE0 0:00.00 -csh 
100 782  660     0   1    1  28  0  100  756 -     R    ttyE0 0:00.00 ps -as 
100 601  702 52333   1    1  53  0 2128 1132 -     R    ttyE1 0:53.89 ./threadt
  0 632    1     0   1    1  10  0  152 2320 wait  I    ttyE1 0:00.00 login 
100 702  632     0   1    1  18  0  232 1064 pause I    ttyE1 0:00.00 -csh 
  0 657    1    44   1    1   3  0   60  944 ttyin I    ttyE2 0:00.00 /usr/libe
  0 637    1    44   1    1   3  0   60  944 ttyin I    ttyE3 0:00.00 /usr/libe

なんかNLWPの数は1で、スレッド1個しかないように見えます。うーーん。テストプログラムを少し変えてみて待たせるようにしてみましたが変わりません。ちゃんとスレッド自体はバラバラに動いているようですが、外から確認する方法がよくわからない。 少なくともCPUは1コア分しか使っていません。これではかなり悲しい感じです。

次にNetBSD 5で試してみます。現在の最新はつい先日2014/11/15にリリースされたNetBSD 5.2.3です。

% top -t
load averages:  9.73,  6.51,  3.21;               up 0+00:10:13        09:25:11
74 threads: 20 idle, 7 runnable, 43 sleeping, 4 on CPU
CPU0 states:  100% user,  0.0% nice,  0.0% system,  0.0% interrupt,  0.0% idle
CPU1 states:  100% user,  0.0% nice,  0.0% system,  0.0% interrupt,  0.0% idle
CPU2 states:  100% user,  0.0% nice,  0.0% system,  0.0% interrupt,  0.0% idle
CPU3 states:  100% user,  0.0% nice,  0.0% system,  0.0% interrupt,  0.0% idle
Memory: 20M Act, 4048K Inact, 1024K Wired, 5416K Exec, 13M File, 15G Free
Swap: 406M Total, 406M Free

  PID   LID USERNAME PRI STATE      TIME   WCPU    CPU COMMAND      NAME
  420     4 oshima    26 RUN/2      1:15 43.61% 43.60% threadtest -
  420    10 oshima    25 RUN/1      1:20 43.17% 43.16% threadtest -
  420    11 oshima    25 RUN/2      1:17 40.82% 40.82% threadtest -
  420     6 oshima    25 CPU/2      1:18 40.38% 40.38% threadtest -
  420     2 oshima    25 RUN/1      1:09 39.94% 39.94% threadtest -
  420     5 oshima    25 CPU/3      1:13 39.02% 39.01% threadtest -
  420     9 oshima    25 RUN/0      1:19 38.63% 38.62% threadtest -
  420     7 oshima    25 CPU/1      1:16 35.84% 35.84% threadtest -
  420     8 oshima    25 RUN/2      1:15 32.38% 32.37% threadtest -
  420     3 oshima    26 RUN/2      1:21 31.79% 31.79% threadtest -
(略)
% ps -as
UID PID PPID    CPU LID NLWP PRI NI   VSZ  RSS WCHAN  STAT TTY      TIME COMMAND
100 359  383      0   1    1  43  0  6556  900 -      R    ttyE0 0:00.00 ps -as
  0 373    1      0   1    1  85  0 29272 2872 wait   I    ttyE0 0:00.01 login 
100 383  373      0   1    1  85  0  4508 1144 pause  S    ttyE0 0:00.00 -csh 
  0 377    1      0   1    1  85  0 29272 2916 wait   I    ttyE1 0:00.01 login 
100 404  377      0   1    1  85  0  4508 1132 pause  I    ttyE1 0:00.00 -csh 
100 420  404 332214  11   11  28  0 29032 1100 -      R-   ttyE1 1:51.36 ./thre
100 420  404 332214  10   11  28  0 29032 1100 -      R-   ttyE1 1:51.36 ./thre
100 420  404 332214   9   11  26  0 29032 1100 -      R-   ttyE1 1:51.36 ./thre
100 420  404 332214   8   11  26  0 29032 1100 -      R-   ttyE1 1:51.36 ./thre
100 420  404 332214   7   11  26  0 29032 1100 -      R-   ttyE1 1:51.36 ./thre
100 420  404 332214   6   11  28  0 29032 1100 -      R-   ttyE1 1:51.36 ./thre
100 420  404 332214   5   11  27  0 29032 1100 -      R-   ttyE1 1:51.36 ./thre
100 420  404 332214   4   11  28  0 29032 1100 -      R-   ttyE1 1:51.36 ./thre
100 420  404 332214   3   11  28  0 29032 1100 -      R-   ttyE1 1:51.36 ./thre
100 420  404 332214   2   11  28  0 29032 1100 -      R-   ttyE1 1:51.36 ./thre
100 420  404 332214   1   11  43  0 29032 1100 parked S    ttyE1 1:51.36 ./thre
  0 392    1     18   1    1  85  0  6508 1076 ttyraw I    ttyE2 0:00.00 /usr/l
  0 334    1     18   1    1  85  0  6508 1076 ttyraw I    ttyE3 0:00.00 /usr/l

こちらではCPU4個をすべて使用し、実際に11個のスレッドを確認することができました。最初に使ったNetBSD 7.0_BETAと同じ動きです。

わざわざ古いOS使うことは普通ありえないと思いますが、NetBSD 5のリリースアナウンスにかかれているとおり、マルチプロセッサでのマルチスレッドプログラム実行パフォーマンスは劇的に上がっているといっていいみたいです(前が酷すぎただけという話もありますが)。

終わりに

NetBSDというとどうしてもカーネルの中のドライバとか、謎マシンへの移植とか、そういう話が多くなりがちで、それは大変興味深いのですが、なかなか縁遠い人も多いんじゃないかと思います。でももうちょっとユーザ寄りのところ、でも既存の何かを動かすだけじゃないところ、アプリケーション作るときに関係するようなことって何か無いかな、とか思って書いてみました。

OSの重要な役割としてアプリケーションを作成・動作させるため簡便な手段を提供しハードウェアの性能/機能を自動的に引き出してくれるということもあります。 今回のネタであるpthreadを使ってちゃんとマルチコアの性能をいかせるというのは地味で当たり前のことですが、それがちゃんとできるようになっているのは嬉しいことでもあります。 この話は別にNetBSDでなくても良いし、他のOSでも同じプログラムが書けて動かせるわけですが、そういうあたりまえのプログラムも、ちゃんとNetBSD上で動かせるんだよ、ということも大事ではないかと思います。

今時はC言語レベルでpthreadを生で使うってことはあんまりないのかもしれません。より簡易な言語環境/実行環境/ライブラリなどを使うのが普通かもしれません。
NetBSD上でも、pkgsrcを使ってPerl, Ruby, Pythonなんかも普通に動きますし、使えるアーキテクチャは限られますがOpenJDKだってあるのでjavaでマルチスレッドは当たり前に動きます。一部で話題のgo言語も、同様に使えるアーキテクチャは限られますが手元のamd64環境ではマルチスレッドで動かすこともできました。この話も書こうかと思いましたがまとまってないのととっても長くなりそうなのでいずれまたの機会にでも。


この文書はおおしまやすしが2014年12月21日に書きました