[インデックス 13319] ファイルの概要
このコミットでは、以下のファイルが変更されました。
test/bench/shootout/binary-tree.ctest/bench/shootout/k-nucleotide.ctest/bench/shootout/threadring.ctest/bench/shootout/timing.sh
コミット
コミットハッシュ: 97300640ca510f1db2c38bdf48e9e82ee4bae983
作者: Shenghou Ma minux.ma@gmail.com
日付: 2012年6月8日 金曜日 02:56:23 +0800
概要: test/bench/shoutout: fix compliation
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/97300640ca510f1db2c38bdf48e9e82ee4bae983
元コミット内容
test/bench/shoutout: fix compliation
-lm must come after the source file, versions of gcc insist this strict order.
On standard compliant systems, we no longer need malloc.h for malloc.
Use pkg-config(1) to get correct glib cflags and libs.
Fix compiler warning in threadring.c and k-nucleotide.c.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6198076
変更の背景
このコミットは、Go言語プロジェクトのベンチマークスイート(test/bench/shootoutディレクトリ配下)におけるC言語プログラムのコンパイルに関する複数の問題を修正することを目的としています。具体的には、以下の問題に対処しています。
- リンカオプションの順序問題:
gccにおいて、数学ライブラリ(-lm)の指定がソースファイルよりも前に来ると、一部のgccバージョンでコンパイルエラーが発生する問題。これは、リンカがライブラリを解決する際に、そのライブラリが提供するシンボルが使用されるオブジェクトファイルよりも後に指定される必要があるという、リンカの動作に起因します。 malloc.hの不要性: 標準に準拠したシステムでは、malloc関数を使用するためにmalloc.hを明示的にインクルードする必要がなくなっているため、冗長なインクルードを削除する。これは、C標準ライブラリの進化と、stdlib.hがmallocの宣言を含むようになったことによるものです。glibのコンパイルフラグとライブラリの取得:glibライブラリを使用する際に、手動でインクルードパス(-I)やライブラリパス(-L)、ライブラリ名(-l)を指定するのではなく、pkg-configユーティリティを使用して、システムに応じた正しいコンパイルフラグとライブラリ情報を動的に取得するように変更する。これにより、ビルドの移植性と堅牢性が向上します。- コンパイラ警告の修正:
threadring.cとk-nucleotide.cにおけるコンパイラ警告を修正し、コードの品質と移植性を向上させる。特に、ポインタと整数の間のキャストに関する警告や、関数ポインタの型不一致に関する警告が対象です。
これらの修正は、ベンチマークの正確な実行と、異なる環境でのコンパイルの成功を保証するために重要です。
前提知識の解説
1. Computer Language Benchmarks Game (CLBG)
test/bench/shootoutディレクトリは、Computer Language Benchmarks Game (CLBG) のベンチマークプログラムを含んでいます。CLBGは、様々なプログラミング言語で書かれた共通のアルゴリズムの実装を比較し、実行速度やメモリ使用量などのパフォーマンスを評価するプロジェクトです。Go言語のベンチマークスイートには、他の言語(この場合はC言語)で書かれた参照実装が含まれており、Go言語の実装と比較するために使用されます。
2. GCC (GNU Compiler Collection)
GCCは、GNUプロジェクトによって開発されたコンパイラシステムです。C、C++、Objective-C、Fortran、Ada、Goなど、多くのプログラミング言語をサポートしています。このコミットでは、C言語のコンパイルとリンクの挙動が問題となっています。
3. リンカとライブラリの順序 (-lm)
C/C++プログラムをコンパイルする際、コンパイラはソースコードをオブジェクトファイルに変換し、その後リンカがこれらのオブジェクトファイルと必要なライブラリを結合して実行可能ファイルを生成します。
gccコマンドで-lオプションを使ってライブラリをリンクする場合、その順序は重要です。リンカは通常、コマンドラインで指定された順序でファイルを処理し、未解決のシンボルを解決しようとします。もしライブラリが、そのライブラリ内の関数を使用するオブジェクトファイルよりも前に指定された場合、リンカはその関数がまだ必要とされていないと判断し、ライブラリを適切にリンクしないことがあります。
-lmは数学ライブラリ(libm.aまたはlibm.so)をリンクするためのオプションで、sqrt()やsin()などの数学関数が含まれています。このコミットの修正は、nbody.cがlibmの関数を使用しているため、nbody.cのオブジェクトファイルが-lmよりも前に来るように順序を変更しています。
4. malloc.hとstdlib.h
malloc.hは、C言語で動的メモリ割り当てを行う関数(malloc, free, calloc, reallocなど)の宣言を含むヘッダファイルです。しかし、C89標準以降、これらの関数はstdlib.hにも宣言されるようになりました。現代の標準に準拠したCコンパイラでは、mallocを使用するためにstdlib.hをインクルードするだけで十分であり、malloc.hは冗長または非標準と見なされることがあります。このコミットは、この現代的な慣習に従い、不要なmalloc.hのインクルードを削除しています。
5. pkg-config
pkg-configは、ソフトウェアのコンパイル時に必要なライブラリのコンパイルフラグ(-Iで指定するインクルードパスなど)やリンクフラグ(-Lで指定するライブラリパス、-lで指定するライブラリ名など)を自動的に取得するためのコマンドラインユーティリティです。これにより、異なるシステムやライブラリのバージョンに依存するビルドスクリプトの移植性が向上します。
例えば、pkg-config glib-2.0 --cflags --libsと実行すると、glib-2.0ライブラリをコンパイル・リンクするために必要なすべてのフラグが出力されます。
6. glib
GLibは、GTK+やGNOMEプロジェクトで広く使用されている、汎用的なユーティリティ関数とデータ構造を提供するC言語ライブラリです。このコミットでは、k-nucleotide.cがglibのGPtrArray(ポインタの配列)を使用しており、その要素を解放するためにg_ptr_array_foreach関数を使用しています。
7. GFunc型と関数ポインタのキャスト
GFuncはGLibで定義されている関数ポインタの型で、g_ptr_array_foreachのような関数でコールバックとして使用されます。C言語では、異なる型の関数ポインタを直接渡すとコンパイラ警告や未定義動作を引き起こす可能性があります。このコミットでは、free関数(void (*)(void*)のようなシグネチャを持つ)をGFunc型(void (*)(gpointer data, gpointer user_data)のようなシグネチャを持つ)に明示的にキャストすることで、型安全性を確保し、コンパイラ警告を解消しています。
8. uintptr_tとポインタ/整数のキャスト
uintptr_tは、ポインタを保持できる十分な大きさを持つ符号なし整数型です。これは、ポインタを整数に変換したり、整数をポインタに変換したりする際に、ポインタのサイズがシステムによって異なる場合でも、移植性のある方法で安全に変換するために使用されます。
pthread_createのような関数は、スレッド関数にvoid*型の引数を渡します。このコミットでは、スレッドID(整数)をvoid*として渡す際に、直接intからvoid*にキャストするのではなく、一度uintptr_tを介することで、ポインタのサイズと整数のサイズが異なるアーキテクチャでの潜在的な問題を回避し、コンパイラ警告を解消しています。
技術的詳細
このコミットは、C言語のコンパイルとリンクにおけるいくつかの一般的な落とし穴と、それらを回避するためのベストプラクティスを示しています。
-
リンカの順序依存性:
timing.shスクリプト内のnbodyベンチマークのコンパイルコマンドが、gcc -O2 -lm nbody.cからgcc -O2 nbody.c -lmに変更されました。これは、gcc(特に古いバージョンや特定の構成)が、ライブラリを解決する際に、そのライブラリが提供するシンボルを使用するオブジェクトファイルがコマンドライン上でライブラリよりも前に現れることを要求するためです。nbody.cがlibmの関数(例:sqrt,sin,cosなど)を使用している場合、nbody.oが先に処理され、その後に-lmが指定されることで、リンカはnbody.oで参照されている数学関数をlibmから正しく解決できるようになります。 -
malloc.hの削除:binary-tree.cから#include <malloc.h>が削除されました。これは、C89標準以降、mallocやfreeなどの動的メモリ割り当て関数がstdlib.hで宣言されるようになったためです。現代のCコンパイラ環境では、stdlib.hをインクルードするだけでこれらの関数は利用可能であり、malloc.hは非標準または冗長なインクルードと見なされます。この変更は、コードの標準準拠性を高め、不要な依存関係を排除します。 -
pkg-configによるglibのリンク:timing.shスクリプト内のk-nucleotideベンチマークのコンパイルコマンドが、手動でインクルードパスとライブラリを指定するgcc -O2 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include k-nucleotide.c -lglib-2.0から、pkg-configを使用するgcc -O2 k-nucleotide.c $(pkg-config glib-2.0 --cflags --libs)に変更されました。pkg-configは、システムにインストールされているライブラリのバージョンやパスに依存せず、正しいコンパイルフラグ(--cflags)とリンクフラグ(--libs)を動的に取得できるため、ビルドスクリプトの移植性とメンテナンス性が大幅に向上します。これにより、異なるLinuxディストリビューションやglibのインストールパスが異なる環境でも、k-nucleotide.cが正しくコンパイル・リンクされるようになります。 -
GFuncへの明示的なキャスト:k-nucleotide.cでは、g_ptr_array_foreach関数にfree関数をコールバックとして渡す際に、g_ptr_array_foreach(roots, free, NULL);からg_ptr_array_foreach(roots, (GFunc)free, NULL);へと変更されました。g_ptr_array_foreachは、第2引数としてGFunc型の関数ポインタを期待します。free関数のシグネチャは通常void free(void *ptr)であり、GFuncのシグネチャ(void (*)(gpointer data, gpointer user_data))とは異なります。C言語では、異なる型の関数ポインタ間の暗黙的な変換は未定義動作を引き起こす可能性があり、コンパイラ警告の一般的な原因となります。明示的に(GFunc)freeとキャストすることで、コンパイラに意図を伝え、型不一致の警告を解消し、コードの堅牢性を高めます。 -
uintptr_tを用いたポインタ/整数の安全なキャスト:threadring.cでは、pthread_createにスレッドID(整数)をvoid*として渡す際、およびスレッド関数内でvoid*引数を整数にキャストする際に、int l = (int)num;とpthread_create(&cthread, &stack_attr, thread, (void*)i);がそれぞれint l = (int)(uintptr_t)num;とpthread_create(&cthread, &stack_attr, thread, (void*)(uintptr_t)i);に変更されました。 また、この変更に伴い、#include <stdint.h>が追加されました。stdint.hは、uintptr_tのような固定幅整数型を定義しています。void*は汎用ポインタ型ですが、そのサイズはシステムによって異なります(32ビットシステムでは4バイト、64ビットシステムでは8バイトなど)。intも同様にサイズが異なる場合があります。intからvoid*への直接キャスト、またはその逆のキャストは、ポインタのサイズがintのサイズよりも大きいシステムでは情報が失われる可能性があり、コンパイラ警告や実行時エラーの原因となります。uintptr_tは、ポインタを保持できることが保証されている整数型であるため、int->uintptr_t->void*、またはvoid*->uintptr_t->intという中間ステップを踏むことで、ポインタと整数の間の変換を安全かつ移植性のある方法で行うことができます。これにより、コンパイラ警告が解消され、異なるアーキテクチャでの潜在的なバグが回避されます。
コアとなるコードの変更箇所
test/bench/shootout/binary-tree.c
--- a/test/bench/shootout/binary-tree.c
+++ b/test/bench/shootout/binary-tree.c
@@ -36,7 +36,6 @@ POSSIBILITY OF SUCH DAMAGE.\n icc -O3 -ip -unroll -static binary-trees.c -lm\n */\n \n-#include <malloc.h>\n #include <math.h>\n #include <stdio.h>\n #include <stdlib.h>\
test/bench/shootout/k-nucleotide.c
--- a/test/bench/shootout/k-nucleotide.c
+++ b/test/bench/shootout/k-nucleotide.c
@@ -221,7 +221,7 @@ main ()\n \n free(s);\n \n- g_ptr_array_foreach(roots, free, NULL);\n+ g_ptr_array_foreach(roots, (GFunc)free, NULL);\n g_ptr_array_free(roots, TRUE);\n \n return 0;\
test/bench/shootout/threadring.c
--- a/test/bench/shootout/threadring.c
+++ b/test/bench/shootout/threadring.c
@@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE.\n * contributed by Premysl Hruby\n */\n \n+#include <stdint.h>\n #include <stdio.h>\n #include <stdlib.h>\n #include <pthread.h>\
@@ -57,7 +58,7 @@ static struct stack stacks[THREADS];\n \n static void* thread(void *num)\n {\n- int l = (int)num;\n+ int l = (int)(uintptr_t)num;\n int r = (l+1) % THREADS;\n int token;\n \
@@ -94,7 +95,7 @@ int main(int argc, char **argv)\n pthread_mutex_lock(mutex + i);\n \n pthread_attr_setstack(&stack_attr, &stacks[i], sizeof(struct stack));\n- pthread_create(&cthread, &stack_attr, thread, (void*)i);\n+ pthread_create(&cthread, &stack_attr, thread, (void*)(uintptr_t)i);\n }\n \n pthread_mutex_unlock(mutex + 0);\
test/bench/shootout/timing.sh
--- a/test/bench/shootout/timing.sh
+++ b/test/bench/shootout/timing.sh
@@ -97,7 +97,7 @@ revcomp() {\n \n nbody() {\n \trunonly echo \'nbody -n 50000000\'\n-\trun \'gcc -O2 -lm nbody.c\' a.out 50000000\n+\trun \'gcc -O2 nbody.c -lm\' a.out 50000000\n \trun \'gccgo -O2 nbody.go\' a.out -n 50000000\n \trun \'gc nbody\' $O.out -n 50000000\n \trun \'gc_B nbody\' $O.out -n 50000000\n@@ -147,7 +147,7 @@ knucleotide() {\n \trunonly gcc -O2 fasta.c\n \trunonly a.out 1000000 > x # should be using 25000000\n \trunonly echo \'k-nucleotide 1000000\'\n-\trun \'gcc -O2 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include k-nucleotide.c -lglib-2.0\' a.out <x\n+\trun \"gcc -O2 k-nucleotide.c $(pkg-config glib-2.0 --cflags --libs)\" a.out <x\n \trun \'gccgo -O2 k-nucleotide.go\' a.out <x\n \trun \'gccgo -O2 k-nucleotide-parallel.go\' a.out <x\n \trun \'gc k-nucleotide\' $O.out <x\
コアとなるコードの解説
binary-tree.cにおけるmalloc.hの削除
- 変更前:
#include <malloc.h> - 変更後: 行が削除
この変更は、malloc.hのインクルードを削除しています。現代のC標準(C89以降)では、mallocやfreeといった動的メモリ割り当て関数はstdlib.hで宣言されています。したがって、stdlib.hが既にインクルードされている場合、malloc.hは冗長であり、一部の環境では非標準のヘッダとして警告やエラーの原因となる可能性があります。この修正により、コードの標準準拠性が向上し、よりクリーンになります。
k-nucleotide.cにおけるGFuncへの明示的なキャスト
- 変更前:
g_ptr_array_foreach(roots, free, NULL); - 変更後:
g_ptr_array_foreach(roots, (GFunc)free, NULL);
g_ptr_array_foreach関数は、配列の各要素に対してコールバック関数を適用します。このコールバック関数はGFunc型である必要があります。free関数のシグネチャは通常void free(void *ptr)であり、GFuncの期待するシグネチャ(void (*)(gpointer data, gpointer user_data))とは異なります。C言語では、異なる型の関数ポインタを直接渡すとコンパイラ警告が発生することがあります。(GFunc)freeと明示的にキャストすることで、コンパイラに型変換の意図を伝え、警告を解消し、コードの型安全性を向上させています。
threadring.cにおけるuintptr_tの使用とstdint.hの追加
- 変更前:
int l = (int)num; pthread_create(&cthread, &stack_attr, thread, (void*)i); - 変更後:
#include <stdint.h> // 追加 // ... int l = (int)(uintptr_t)num; // ... pthread_create(&cthread, &stack_attr, thread, (void*)(uintptr_t)i);
この変更は、ポインタと整数の間のキャストをより安全かつ移植性のある方法で行うためにuintptr_tを使用しています。
pthread_createは、スレッド関数にvoid*型の引数を渡します。ここでは、スレッドの識別子(整数i)をvoid*として渡しています。また、スレッド関数thread内では、そのvoid*引数numを整数lにキャストして使用しています。
intとvoid*のサイズはシステムによって異なる可能性があり、直接キャストすると情報が失われたり、未定義動作を引き起こしたりする可能性があります。uintptr_tは、ポインタを保持できることが保証されている符号なし整数型です。
int -> uintptr_t -> void*、およびvoid* -> uintptr_t -> intという中間キャストを行うことで、ポインタのサイズと整数のサイズが異なるアーキテクチャでも、データが正しく保持され、コンパイラ警告が解消されます。このために、uintptr_tが定義されている<stdint.h>がインクルードされています。
timing.shにおけるgccリンカ順序とpkg-configの使用
-
nbodyベンチマークの変更:- 変更前:
run 'gcc -O2 -lm nbody.c' a.out 50000000 - 変更後:
run 'gcc -O2 nbody.c -lm' a.out 50000000この変更は、gccのリンカにおけるライブラリの順序問題を修正しています。数学ライブラリ-lmは、それを使用するソースファイル(nbody.c)の後に指定される必要があります。これにより、リンカがnbody.cで参照される数学関数を正しく解決できるようになります。
- 変更前:
-
k-nucleotideベンチマークの変更:- 変更前:
run 'gcc -O2 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include k-nucleotide.c -lglib-2.0' a.out <x - 変更後:
run "gcc -O2 k-nucleotide.c $(pkg-config glib-2.0 --cflags --libs)" a.out <xこの変更は、glibライブラリのコンパイルとリンクにpkg-configユーティリティを使用するように切り替えています。以前は手動でインクルードパス(-I)とライブラリ名(-l)を指定していましたが、これはシステムやglibのバージョンによってパスが異なる場合に問題を引き起こす可能性がありました。$(pkg-config glib-2.0 --cflags --libs)は、glib-2.0ライブラリをコンパイル・リンクするために必要なすべてのフラグ(インクルードパス、ライブラリパス、ライブラリ名など)を自動的に出力します。これにより、ビルドスクリプトの移植性と堅牢性が大幅に向上し、異なる環境でのコンパイルが容易になります。
- 変更前:
関連リンク
- Computer Language Benchmarks Game: https://benchmarksgame-team.pages.debian.net/benchmarksgame/
- GCC Documentation: https://gcc.gnu.org/onlinedocs/
- GLib Reference Manual: https://docs.gtk.org/glib/
pkg-configman page: https://linux.die.net/man/1/pkg-configstdint.h(C Standard Library): https://en.cppreference.com/w/c/types/integer
参考にした情報源リンク
- GCC Linker Order: https://stackoverflow.com/questions/45135/why-does-the-order-of-libraries-in-gcc-matter
malloc.hvsstdlib.h: https://stackoverflow.com/questions/1005081/malloc-h-vs-stdlib-hpkg-configusage: https://www.freedesktop.org/wiki/Software/pkg-config/- Casting function pointers: https://stackoverflow.com/questions/1005081/malloc-h-vs-stdlib-h (Note: This link was used for
malloc.hbut the general concept of casting applies to function pointers as well) uintptr_tusage: https://stackoverflow.com/questions/1845482/what-is-uintptr-t-in-cpthread_createarguments: https://man7.org/linux/man-pages/man3/pthread_create.3.html