● 実行速度について
C/C++標準の変数
fixed X の関数は 32bit から扱え、C/C++が直接持っていない機能もありますので、int型等の変数に fixed X を利用するとプログラミングが楽になる事があります。しかし、速度効率を重視する場合は、fixed X を使わず独自の処理を用意した方が良いでしょう(一部の関数を除く)。
例えば、long型の a += b を行い、オーバーフローも検出する場合、
long a,b;
int of;
・
・
・
of = fixed_signed_add((PFIXEDVAL )&a,(PFIXEDVAL )&b,sizeof(long ));
または
of = fixed_signed_add32((PFIXEDVAL )&a,b,sizeof(long ));
fixed X の関数は 32bit でも使う事ができますが、元々は大きな数値幅の処理を目的としているので、内部ループのオーバーヘッドにより効率が悪くなります。標準の変数型で加算を行う場合は、普通に a += b を行った方が効率的です。
オーバーフローの検出をCで作成する事もできますが、演算直後はフラグレジスタに状態が保存されているので、それを利用した方が簡単で見易く、尚且つ効率的です。
参考.オーバーフロー検出付き long型加算の一例
unsigned long temp,c1,c2;
temp = (a & 0x7fffffff) + (b & 0x7fffffff);
c2 = ((unsigned long)a >> 31) + ((unsigned long)b >> 31) + (c1 = (temp >> 31));
a = (temp & 0x7fffffff) | (c2 << 31);
of = c1 ^ (c2 >> 1);
最初に fixed X を使おうとしていたのであれば、移植性は重要視していないはずですから、積極的にフラグレジスタを使用しても問題は無いでしょう。
a += b;
_asm seto byte ptr of
of &= 1;
set* 命令が計算の直後に展開されている必要がありますが、これはコンパイル時にアセンブリコードのリストを出力すれば確認する事ができます(/FA または /Fa)。
実装時にはコンパイラの最適化に注意する必要があります。
次のように、変数に定数を代入し、計算を行うプログラムを /O2 で作成した場合、赤字の部分がコンパイル時に計算されてしまうので、フラグレジスタの内容が計算に対するものではなくなってしまいます(代入と計算部分が省かれ、printf へ直に 0x80000000L を指定した状態のコードを生成してしまう)。
テスト.
#include <stdio.h>
void main(void)
{
long a,b;
int of;
a = 0x7fffffffL;
b = 1;
a += b;
_asm seto byte ptr of
of &= 1;
printf("%ld %d\n",a,of);
}
これを回避するには、最適化を無効にしたり、変数 a 、b を volatile修飾する等の方法がありますが、次のように加算を asm ブロックに入れてしまう事も有効です。
#include <stdio.h>
void main(void)
{
long a,b;
int of;
a = 0x7fffffffL;
b = 1;
/*
asm ブロックに加算を入れる事で、
・set* 命令の直前が確実に演算命令になるようにする
・a 、b に関する最適化を抑制する
*/
_asm{
mov eax,b
add a,eax ; a += b
seto byte ptr of
}
of &= 1;
printf("%ld %d\n",a,of);
}
レジスタ割り当て
fixed X 関数の多くは引数が値渡しではなく参照であり、指定する変数はアドレスで指す事のできる実体を持っている必要があります。このため、fixed X の関数に指定した標準型の変数に、レジスタは割り当てられません(レジスタにはアドレスが存在しないので、コンパイラはアドレス指定されている変数にレジスタを割り当てる事ができない)。ANSI C 規格でも register変数のアドレス指定は許されていません。
int parameter;
・
・
・
/* parameter にはレジスタが割り当てられない */
if (fixed_signed_add32((PFIXEDVAL )¶meter , 1L , sizeof(int))){
}
変数にレジスタが割り当てられないだけであり、論理的には正しい流れなので、上記のような使い方が悪いという訳ではありません。しかし、効率性を考えるのであれば、少なくともレジスタが割り当てられる事を期待している変数(頻繁に処理される変数)については、fixed X の関数に直接指定するべきではありません。
例.
#include <stdlib.h>
#include <ctype.h>
#define MAX_NUM 10 /* UL最大 : "4294967295"(10文字) */
/* 数値文字列内の空白を詰め、unsigned long値に変換する */
unsigned long cnv(char *psrc)
{
char *ptr,buf[MAX_NUM+1];
unsigned long ulval;
int i;
for(ptr = buf,i = MAX_NUM;*psrc;psrc++){
if (isspace(*psrc)) continue;
*ptr++ = *psrc;
if (!--i) break;
}
*ptr = 0;
ulval = strtoul(buf,&ptr,10);
return (!*ptr)? ulval : 0UL;
}
この例は標準ライブラリの場合ですが、fixed X の場合も同様の考え方です。
数値文字列を抽出する上側のパートと、関数strtoul により文字列を数値に変換する下側のパートに分けられます。変数ptr は上側のパートだけで使用されるので、下側のパートではスキャン停止位置の取得に使い回しています。
下側のパートで ptr 自体のアドレスを関数strtoul に渡しているため、ptr にはレジスタが割り当てられずスタックが使用されます。このため、上側のパートの効率が良くなりません。
この場合、使い回しを止めて、上側と下側のパートで別々の変数を使用する事でこの問題を解決できます。
unsigned long cnv(char *psrc)
{
char *ptr,*pscan,buf[MAX_NUM+1];
unsigned long ulval;
int i;
for(ptr = buf,i = MAX_NUM;*psrc;psrc++){
if (isspace(*psrc)) continue;
*ptr++ = *psrc;
if (!--i) break;
}
*ptr = 0;
ulval = strtoul(buf,&pscan,10);
return (!*pscan)? ulval : 0UL;
}
ptr にはレジスタが割り当てられ、pscan にはスタックが使用される