
Ubuntu日本語フォーラム

ログインしていません。
Ubunutu10.04-AMD64版上のGCC4.4.3なのですが
以下のコードを走らせると、結果が?になります。
----------------- test.c -------------------
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main(int argc, char** argv) {
int i;
float L2, val;
float *pV, *pV1;
int Ndim = 6000000;
//
pV = (float *)memalign( 16, sizeof(float)*Ndim );
pV1 = (float *)memalign( 16, sizeof(float)*Ndim );
//
for( i=0; i<Ndim; i++ ) {
pV[i] = 6.0f;
pV1[i] = 4.0f;
}
L2 = 0.0f;
for( i=0; i<Ndim; i++ ) {
L2 += pV[i] * pV1[i];
}
printf( "loop count : %d\n", i );
printf( "sum = %f, (the expected value : %f)\n", L2, 24.0 * 6000000.0 );
return (EXIT_SUCCESS);
}
----------------- ここまで ----------------------------------------------------
$ gcc --version
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3
$ gcc -m64 test.c -o test
$ ./test
loop count : 6000000
sum = 147260736.000000, (the expected value : 144000000.000000)
マシンは自作PCで、Q9450定格,RAM8GB,Mobo ASUS-P5Q-PROです。
上記コードのfor{}ループにprintf()を入れて調べたところ
ループカウント pV[i]*pV1[i]の値 L2の値 差
5592404 : 24.000000 134217720.000000 24.000000
5592405 : 24.000000 134217744.000000 24.000000 ..... ここまでO.K.
5592406 : 24.000000 134217760.000000 16.000000 ..... ?
5592407 : 24.000000 134217792.000000 32.000000 ..... ??
5592408 : 24.000000 134217824.000000 32.000000 ..... ???
以降
5999998 : 24.000000 147260704.000000 32.000000
5999999 : 24.000000 147260736.000000 32.000000 ... \(T_T)/
ubuntu10.04付属のmemtest86は1回しか調べてませんが、パスしてます。
ループ回数は何回、コンパイル&リンク、実行を繰り替えしても同じループカウント5592406から狂い始めます。
上のコード、何か変でしょうか?
オフライン
float(単精度浮動小数点数)型の変数がオーバーフローしたために、意図しない値になっていると思われます。
例えば、浮動小数点数で134217744を表すと、以下のようになります。仮数部は2進数で表しています。
1.00000000000000000000001 × 2^27
これに24を足した、134217768は以下のようになります
1.000000000000000000000101 × 2^27
単精度浮動小数点数型の仮数部は最大23ビットなので、134217768(以降)ではこの制限を超えてしまいます。
float型ではなく、double(倍精度浮動小数点数)型を使うことで解決します。
オフライン
オーバーフロー回避の後も気を緩めずに、演算に誤差(狂い)が生じる事を意識して置かないと忘れたころに「アレ?」ってなるよ。
オフライン
同じ内積を以下のinline_asm文で計算すると正しい答えが帰ってきます。
------------------ test.c -------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
//
// inner product L^2 = <V1,V2>
//
void vector_ip( float *L2, float *pV1, float *pV2, int len )
{ // rdi rsi rdx rcx
__asm__ __volatile__ (
"pxor %%xmm1, %%xmm1 \n"
"movq %%rcx, %%rax \n"
"shrq $2, %%rax \n" // rax = len / 4
"jz L_1%= \n"
"L_0%=:\n"
"movaps (%%rsi), %%xmm0 \n"
"mulps (%%rdx), %%xmm0 \n"
"addps %%xmm0, %%xmm1 \n"
"addq $16, %%rsi \n"
"addq $16, %%rdx \n"
"subq $1, %%rax \n"
"jnz L_0%= \n"
"L_1%=:\n"
"andb $0b11, %%cl \n" // rcx = mod( len / 4 )
"jz L_3%= \n"
"L_2%=:\n"
"movss (%%rsi), %%xmm0 \n"
"mulss (%%rdx), %%xmm0 \n"
"addss %%xmm0, %%xmm1 \n"
"addq $4, %%rsi \n"
"addq $4, %%rdx \n"
"subb $1, %%cl \n"
"jnz L_2%= \n"
"L_3%=:\n"
"haddps %%xmm1, %%xmm1 \n"
"haddps %%xmm1, %%xmm1 \n"
"movss %%xmm1, (%%rdi) \n"
:
:
: "rdi", "rsi", "rcx", "rdx", "rax", "xmm0", "xmm1"
);
}
int main(int argc, char** argv) {
int i;
float L2, val;
float *pV, *pV1;
int Ndim = 6000000;
//
pV = (float *)memalign( 16, sizeof(float)*Ndim );
pV1 = (float *)memalign( 16, sizeof(float)*Ndim );
//
for( i=0; i<Ndim; i++ ) {
pV[i] = 6.0f;
pV1[i] = 4.0f;
}
// L2 = 0.0;
// for( i=0; i<Ndim; i++ ) {
// L2 += pV[i] * pV1[i];
// }
vector_ip( &L2, pV, pV1, Ndim );
printf( "loop count : %d\n", i );
printf( "sum = %f, (the expected value : %f)\n", L2, 24.0 * 6000000.0 );
return (EXIT_SUCCESS);
}
-------------------------- ここまで ------------------------------------------------------
$ gcc -m64 test.c -o test
$ ./test
loop count : 6000000
sum = 144000000.000000, (the expected value : 144000000.000000)
xmmレジスタを使った単精度計算をasm文で行っているので、内部的に拡張倍精度(80ビット)を
使うfpu計算より精度が落ちるのかと思いましたが、意に反して正しい答えが求まっています。
GCCが1演算(asmレベルで)毎に単精度でメモリへ吐き出す(80ビットー>32ビット)したとしても
xmmレジスタは32ビットため同じ結果になると思うのですが。
xmmレジスタにもガードビットがついていて、実際には32ビット以上で計算されていたと言うことですかね?
vbkさん、hir0さん回答ありがとうございました。
オフライン
上の件で、「実際には32ビット以上で計算されていた」は
IEEE 754 形式の単精度(符号部 1 ビット ・ 指数部 8 ビット ・ 仮数部 23 ビット)に於いて
仮数部のビット数が23ビット+ガードビットの意味です。
実際には、上記inline_asm文の作成が先行しており、答え合わせのつもりで、後で C で計算を行いました。
xmmレジスタの単精度計算は、メモリ上でのIEEE 754 形式の単精度表現と同じである旨の記載があった為
桁落ちが C 側で発生していることに気がつきませんでした。
オフライン
初心者のトンチンカンな質問です。
諸兄の御投稿を興味深く思い、不心得とは存じますが、i686環境で上掲の2つのプログラムをコンパイルし、実行を試みました。
はじめのプログラムの方は、
$gcc test.c -o test
$./test
で実行したところ、omameさんと全く同じ結果が得られました。
次のinline-asm付きのプログラムは、Synapticマネージャで、libc6-dev-amd64をインストールしたところ、
$gcc -m64 test.c -o test
で、エラーや警告なく、testが生成されましたが、
$./test
bash:./test:バイナリファイルを実行できません。
と出ました。(当然と私も思います...)
ちなみに、
$exec ./test では、
bash:フルパス/test:バイナリファイルを実行できません。
bash:フルパス/test:Success
と出ます。
(初心者で、2行目は何がSuccessなのかも分かりません。)
ここで質問なのですが、libc6-dev-amd64環境では、gccで64ビットのコンパイルが出来ると考えて良いのでしょうか?
そして、もし64ビットのバイナリが正常に生成されるのであれば、実行をエミュレートできるようなソフト(デバッガーなどでも)や方法は存在するのでしょうか?
ちなみに、私のCPUは、
Intel(R) Celeron(R) M CPU 430 @ 1.73GHz
cache size : 1024 KB
cpuid level : 10
wp : yes
clflush size : 64
cache_alignment : 64
address sizes : 32 bits physical, 32 bits virtual
で、Ubuntu10.04-LTS-amd64のインストールも試みましたが、x86-64でなくi686 onlyなのでダメとインストール初期画面ではじかれました。
お答えいただけましたらありがたく思います。宜しくお願い致します。
オフライン
謹賀新年
前回の質問で、32ビットマシンで、64ビットのアーキテクチャを模擬できるかどうかというのは、本当に的外れな質問のようでした。
運良く、64ビットマシンを購入できましたので、早速、Ubuntu10.04LTS-amd64をインストールし、
本日、32ビットマシンで、上掲のインラインアセンブラ付きのプログラムを、libc6-dev-amd64により、gccに-m64を付けてコンパイルしたものを、
64ビットマシン上で、実行できることを確認いたしました。
お騒がせいたしました。
今後とも宜しくお願い申し上げます。
2010年1月1日 ua6ta123
オフライン