結論
残念、あきらめろ。
気が短い人向けに結論から書きました。
以下詳細
結論だけでも技術者にとって有用な気もしないでもないですが、これだけでは何なので、以下どうしてこのような結論に至ったのかを書きます。
結論を厳密に表現してみる
macOSは色々あってUnixのプログラムも(あまり変更しなくてもまあ大体は)動作するのですが、UnixBenchが全く問題なく動くほどの完全互換ではありません。でもね、そこまでの互換性を要求するのもどうかと思いますよ。
何が問題か
例えば、QiitaにもUnixBench計測したのMacで動かすにあるようにパッチを当てればコンパイルはできます1。ですが、実行してみると途中で以下のようなエラーが出て止まります。
Trace/BPT trap: 5
それにこのエラー、どうやらいつ出るかは決まってないみたいです。デバッグで苦労したことのある皆さんなら分かると思いますが、沼の予感しかしませんよね。
原因は
調査
"Trace/BPT trap: 5"でググると、同じエラーが出て困ったという愚痴や悲鳴のようなサイトが複数引っ掛かりますが、どうやら別のプログラムでの出来事であってUnixBenchそのもの関しては有用な情報は見つかりませんでした。
こういう時は本家を確認するといいので(本来は逆!まず本家から見るべきでしょう)本家を見ると、「macOSでは動かないようだ。では、その部分はmacOSでコンパイルされないようにしよう」みたいな対処があるだけ、、、
さらに調査
こうなったら仕方ありません。自力で調査です。UnixBenchのソースコードはsrcディレクトリにあります。"Trace/BPT trap: 5"となるプログラム(の1つ)はspawn.cですが、80行のプログラムなので読んでみましょう。が、"Trace/BPT trap: 5"なんて文字列はどこにも見当たりません。この文字列を出しているのはどうやらOS側のようです。
デバッグ出力を有効にしてプログラムを実行すると"Trace/BPT trap: 5"が表示される(場合がある)のは、実際にベンチマークを取りたいものが動作している時ではなく、最終的に結果を表示する時っぽいことが分かります。そこでは何をやっているかというと、report()という関数がシグナルの機構を使って呼び出されています。なので
man signal
です。するとDESCRIPTIONに
This signal() facility is a simplified interface to the more general sigaction(2) facility.
なんて書いてあるわけです。ということで芋蔓式に
man sigaction
です。すると、NOTEにこんなことが書いてあります。
All functions not in the above lists are considered to be unsafe with
respect to signals. That is to say, the behaviour of such functions when
called from a signal handler is undefined. In general though, signal
handlers should do little more than set a flag; most other actions are
not safe.
(参考訳のようなもの)上記一覧にない関数に関してはシグナルの観点からは安全でないと考えてほしい。すなわち、そのような関数がシグナルハンドラから呼び出された場合の動作は不定である。一般的に言って、シグナルハンドラではフラグの設定かそれに毛の生えた程度のことだけをやるべきである。それ以上のほとんどの処理は安全ではない。
ええと、report()にはfprintfとexitがありましたよね。どちらも「上記一覧」にはないです。(それにfprintfって結構色々処理していたような、、、)
ということで、UnixBenchがかなり際どいプログラムをしていた、というのが原因です。「いや、そこはmacOSが頑張って互換性を確保すべきでしょ」という意見もあるかもしれませんが、反論は認めません。
確認
ここまで来たら"man sigaction"の言っていることが本当か確かめてやりましょう。以下のように変更して、変更前のプログラムと変更後のプログラムを実行してみます。
--- src/spawn.c 2011-01-18 15:44:53
+++ src/spawn_new.c 2024-01-06 12:52:45
@@ -28,11 +28,11 @@
#include "timeit.c"
unsigned long iter;
+int flag;
void report()
{
- fprintf(stderr,"COUNT|%lu|1|lps\n", iter);
- exit(0);
+ flag = 0;
}
int main(argc, argv)
@@ -52,7 +52,8 @@
iter = 0;
wake_me(duration, report);
- while (1) {
+ flag = 1;
+ while (flag) {
if ((slave = fork()) == 0) {
/* slave .. boring */
#if debug
@@ -77,4 +78,5 @@
printf("Child %d done.\n", slave);
#endif
}
+ fprintf(stderr,"COUNT|%lu|1|lps\n", iter);
}
それぞれ20回ずつ実行してみたところ以下のようになりました。
正常終了 | "Trace/BPT trap: 5" | |
---|---|---|
変更前 | 13回 | 7回 |
変更後 | 20回 | 0回 |
ということで、どうやら上記の推測で正しかったようです。
え、「この変更後のプログラムでベンチマークを取ればいいのではないか」ですか。確かにベンチマークは取れますけど、そもそもプログラムが変わってしまっていますから、厳密な比較にはなりませんよね。それに問題の部分が同じ構造をしているプログラムはspawn.cだけではありません。ということで
残念、あきらめろ。
です。
おことわり
恒例のお断りですが、この文章の内容は、筆者が所属している会社・団体とは一切関わりがありません。いわゆる「自主的な研究の成果の発表」というものです。
-
こう書くと他の記事を批判しているように見えるかもしれませんが、そのような意図は全くありません。これ以外にもMacに言及している記事を見ましたが、どの記事も数年前のものなので、その当時はコンパイルできて実行もできたのだと思います。そういえば、私の古いMacでも実行はできたのでした。(CPU数の認識が自動ではうまくいかなかったような記憶がありますが、、、) ↩