|
3.データこの章では、次の問題について説明します。
3.1 データ型の相違次の2つの表に、Sunの、C、C++、およびFORTRANコンパイラによって実装されているマシンとスカラー型の対応をSPARCとx86システムについて示します。この対応は、UNIX System Vアプリケーション・バイナリ・インターフェース(ABI)仕様に準拠して実装されています。 注記――この相違は、符号付きおよび符号なし64ビット整数型とすべての浮動小数点型に影響します。 この章では、これらの相違の影響について説明し、若干の例を示します。
3.2 データのアラインメントSPARCとは異なり、x86プロセッサ・ファミリは、特定のデータ・アラインメントを必要としていません。しかし、SunコンパイラはUNIXSystem V ABIの要件に従った形でデータ・アラインメントを生成します。スタック上にプッシュされたデータは、常にワード境界に合わせて配置されます。これは処理効率を高めるためです。データ・アラインメントの要件がSPARCプロセッサとx86プロセッサで異なるという事実は、データ構造の交換性に影響します。 次に例を示します。 例:struct1.c 次のプログラムstruct1.Cを考えてみます。
main() {
struct {
int i;
double d;
} s;
printf("sizeof(s) = %d\n", sizeof(s));
}
Solaris(SPARC版)での結果まず、Solaris(SPARC版)システム上でコンパイルして実行します。 次がその出力です。 sparc % cc struct1.c sparc % a.out sizeof(s) = 16 sparc % 図1はSPARCのメモリ・レイアウトを示します。
図1:SPARCのレイアウト Solaris(Intel版)での結果次に、同じことをSolaris(Intel版)システム上で実行し、2つの出力結果を比較します。 x86 % cc struct1.c x86 % a.out sizeof(s) = 12 x86 % 図2はx86のメモリ・レイアウトを示します。
図2:x86のレイアウト*1 この2つの間のサイズの違いは、SPARCの場合には必要なアラインメントを実現するために余分なパディングを追加していることが原因です。 両方のプラットフォームで同じコードを使用する場合 アプリケーションが特定のデータ構造のサイズを認識する場合の例の1つは、データ構造にバイト単位でアクセスする場合です。例えば、上記の構造体の場合に、両方のプラットフォーム上でアプリケーションにサイズを認識させるには、次のようなコードを書くことができます。
#define size 12
...
union {
struct strct {
int i;
double d;
};
char ch[size];
}
明らかに、SPARCとx86の間でアプリケーションを移動するときは、#define sizeを変更する必要があります。サイズが共用体定義の中に埋め込まれた数である場合なら、問題はもっと困難になるでしょう。 *1 プラグマのalignやpackの指定により変化します。 3.3 バイト・スワップx86プロセッサ・ファミリが下位バイト先行型(little-endian)のCPUであるのに対して、SPARCは上位バイト先行型(big-endian)のプロセッサです。この定義はプロセッサがメモリ内のワードに対して適用するレイアウトの相違に基づいており、これら2つの異なるCPUに基づくマシン間でデータを交換できるかどうかに影響します。 下位および上位バイト先行型マシンでのバイト割り当て下位バイト先行型のマシン上では、1ワード内の4つのバイトが下位から上位への順でメモリ内に割り当てられますが、上位バイト先行型のマシン上では、割り当てが逆の順で、すなわち上位から下位の順で行われます。 これらの割り当て方式の例を次に示します。 int i=1546; の表現は、x86マシンのメモリ内では次のようになります。
ただし、&i = = n です。 一方、SPARC(上位バイト先行型プロセッサ)上では同じ宣言の表現が次のようになります。
ただし、&i = = n です。 このバイト・スワップは、Cのunion(共用体)を処理するときに問題を引き起こすことがあります。次のプログラムunion1.cを考えます。
main() {
int i;
union {
char a[4];
int i;
} u;
for (i=0; i<=3; i++) u.a[i]='a'+i;
printf("%s\n", u.a);
printf("%x\n", u.i);
}
Solaris(SPARC版)での結果このプログラムをSPARC上で実行した結果は次のようになります。 sparc % cc union1.c sparc % a.out abcd 61626364 sparc % Solaris(Intel版)での結果同じことをx86マシン上で実行すると次のようになります。 x86 % cc union1.c x86 % a.out abcd 64636261 x86 % バイト・スワップによるアプリケーション・コードへの影響バイト・スワップがどのような形でアプリケーション・コードに影響するかを示すもう1つの興味深い例を考えます。次に示すのは、intとdoubleの間に人為的にエイリアスを作成し、負荷の大きい浮動小数点の比較を負荷の小さい整数の比較に置き換えて、処理効率を向上させているプログラムです。
/* alias.c */
double dtwo(void) { return((double) -2); }
main() {
double d;
const int *ip = (const int *)(&d);
d = dtwo();
if(ip[1] & ()x80000000)
printf("d < 0");
else
printf("d >= 0");
printf("\n");
}
このプログラムは、明らかに下位バイト先行型プロセッサに基づいて書かれているので、x86上ではd < 0という正しい結果を出力しますが、SPARCシステム上ではd >= 0という間違った答えになります。これを修正する方法は2つあります。1つは、負荷の大きい浮動小数点の比較を使用する方法(例1)、もう1つは処理効率を犠牲にできない場合に、条件付きコードを使用する方法(例2)です。 例1
/* alias.c */
double dtwo(void) { return((double)-2); }
main() {
double d;
const int *ip = (const int *)(&d);
d = dtwo();
if(d > 0.)
printf("d < 0");
else
printf("d >= 0");
printf("\n");
}
例2
/* alias.c */
double dtwo(void) { return((double)-2); }
main() {
double d;
const int *ip = (const int *)(&d);
d = dtwo();
#ifdef i386
if(ip[1] & ()x80000000)
#endif
#ifdef sparc
if(ip[0] & 0x80000000)
#endif
print("d < 0");
else
printf("d > =0");
print("\n");
}
3.4 浮動小数点演算表1と2から、浮動小数点型のいくつかに違いがあることが分かりますが、その中で最も重要な相違は演算の精度です。具体的には、式を評価するときに、演算と中間結果の保持に何ビットを使用するかという問題です。 SPARCの場合、精度は式のオペランドの型に従って決まるのに対して、x86プロセッサでは、常に拡張精度が使用されます(特別なフラグが指定された場合を除く)。 ベクトルの乗算以下に実例を示します。 次のプログラムは、線形代数で非常によく見られるベクトルの乗算の例を示します。
/* scalar.c */
main() {
int i;
double a[8], s;
a[0] = 3.1;
a[1] = 5.2;
a[2] = 7.3;
a[3] = 9.4;
a[4] = 8.5;
a[5] = 6.6;
a[6] = 4.7;
a[7] = 2.8;
s = 0.0;
for(i=0; i<8; i++) s = s + a[i]*a[i];
printf("s = %.16e\n",s);
}
Solaris(SPARC版)での結果前の場合と同じように、まずプログラムをSolaris(SPARC版)上でコンパイルして実行します。 sparc % cc scalar.c -1m sparc % a.out s = 3.2403999999999996e+02 sparc % Solaris(Intel版)での結果次に、Solaris(Intel版)システム上でコンパイルした場合を示します。 x86 % cc scalar.c -1m x86 % a.out s = 3.2404000000000002e+02 x86 % 両方のプラットフォーム上で同じ浮動小数点コードを実行する場合上記の差異は、かなり回数が少ない演算の結果として生じたものですが、演算数が増加するにしたがって相違も大きくなる可能性があることに考慮する必要があります(例えば、大きな行列の逆行列の計算など)。これは、古典的な数値解析の問題であり、浮動小数点の計算を離散域で、つまり近似域で表現しているという事実に関係しています。数学者は、反復アルゴリズムなどの技法を開発することで、クリティカルなアプリケーションの結果に誤差が影響するのを防いでいます。そのようなアルゴリズムを実装したプログラムは、コードを変更しなくてもSPARCとx86のどちらかのシステムでも同じ結果が生成される可能性が高いでしょう。 その他の浮動小数点の問題データ型の表(表1および2)からは必ずしも明らかでない別の問題として、浮動小数点値の限界値の範囲の問題があります。SPARC上で表現可能な範囲rは次のとおりです。 3.362103143112093506262677817321752603E-4932L <=r <= 1.1897314953572317650857593266228007016E+4932L x86上では同じ値の範囲が次のようになります。 3.3621031431120935062627E-4932L <=r <= 1.1897314953572317650213E+4932L Solaris(SPARC版)用のコンパイルが提供している128ビットの浮動少数点は、Solaris(Intel版)用のコンパイラには用意されていませんが、これによる影響は演算の精度だけに生じます。上記のように、浮動小数点域の範囲の大きさはSPARCとx86の両方で同一です。 そのほか、さらに難解な浮動少数点関連の相違が、NaN(Not a Number)や三角関数の領域に存在します。これはユーザからは認識されない相違であると思われます。 [ 前の頁へ | 目次 | 次の頁へ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||