Innovating@Sun コミュニティ ご購入について (0120-33-9096) マイ・アカウント 日本 [Change] 日本語

SPARCとx86のアーキテクチャの相違
データ
   はじめに
   一般的な移植上の問題
データ
   呼び出し規約
   その他の問題点
   よく起こる誤り
   参考資料
   用語説明
 
 

3.データ

この章では、次の問題について説明します。

  • データ型の相違
  • データのアラインメント
  • バイト・スワップ
  • 浮動小数点演算

3.1 データ型の相違

次の2つの表に、Sunの、C、C++、およびFORTRANコンパイラによって実装されているマシンとスカラー型の対応をSPARCとx86システムについて示します。この対応は、UNIX System Vアプリケーション・バイナリ・インターフェース(ABI)仕様に準拠して実装されています。


注記――この相違は、符号付きおよび符号なし64ビット整数型とすべての浮動小数点型に影響します。

この章では、これらの相違の影響について説明し、若干の例を示します。

表1:x86のスカラー型
C++ F77 サイズ アライン
メント
メモリ表現 演算精度
整数 char
signed char
char
signed char
character
logical*1
符号付きバイト 符号付きバイト
unsigned char unsigned char 符号なしバイト 符号なしバイト
short
signed short
short
signed short
integer*2
logical*2
符号付き
ハーフワード
符号付き
ハーフワード
unsigned short unsigned short 符号なし
ハーフワード
符号なし
ハーフワード
int
signed int
long
signed long
enum
各種ポインタ型
int
signed int
long
signed long
enum
各種ポインタ型
integer
integer*4
logical
logical*4
符号付きワード 符号付きワード
unsigned int
unsigned long
unsigned int
unsigned long
符号なしワード 符号なしワード
long long
signed long long
符号付き
ダブルワード
符号付き
ダブルワード
unsigned long long 符号なし
ダブルワード
符号なし
ダブルワード
浮動小数点 float float real
real*4
単精度(IEEE) 拡張精度
double double real*8 倍精度(IEEE) 拡張精度
long double long double 12 拡張精度(IEEE) 拡張精度

表2:SPARCのスカラー型
C++ F77 サイズ wッwユwーwロ
メント
メモリ表現 演算精度
整数 char
signed char
char
signed char
character
logical*1
符号付きバイト 符号付きバイト
unsigned char unsigned char 符号なしバイト 符号なしバイト
short
signed short
short
signed short
integer*2
logical*2
符号付き
ハーフワード
符号付き
ハーフワード
unsigned short unsigned short 符号なし
ハーフワード
符号なし
ハーフワード
int
signed int
long
signed long
enum
各種ポインタ型
int
signed int
long
signed long
enum
各種ポインタ型
integer
integer*4
logical
logical*4
符号付きワード 符号付きワード
unsigned int
unsigned long
unsigned int
unsigned long
符号なしワード 符号なしワード
long long
signed long long
符号付き
ダブルワード
符号付き
ダブルワード
unsigned long long 符号なし
ダブルワード
符号なし
ダブルワード
浮動小数点 float float real
real*4
単精度(IEEE) 単精度
double double real*8 倍精度(IEEE) 倍精度
long double long double real*16 16 4倍精度(IEEE) 4倍精度

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)や三角関数の領域に存在します。これはユーザからは認識されない相違であると思われます。


[ 前の頁へ | 目次 | 次の頁へ]