本記事は情報処理学会第63回組込みシステム研究会の同名の発表の補足説明と詳細についてQiita記事にしたものです.
本研究の動機
三角関数は,自動運転車やVR機器に必要な3D処理や,画像処理・信号処理で広く用いられるFFTなどで広く使われています.その典型的な使い方の一つは,複数の角度に対する$\sin$関数と$\cos$関数の値を同時に求める使い方です.
$\sin$関数と$\cos$関数を同時に求めるアルゴリズムとしてCORDICアルゴリズムがあります.これは下記論文に示すように,非常に古くからあるアルゴリズムで,初稿が1956年に遡ります.
- Volder, J. E.: Binary Computation Algorithms for Coordinate Rotation and Function Generation (1956). (internal report), Convair, Aeroelectronics group, IAR-1.148.
- Volder, J. E.: The Birth of CORDIC, Journal of VLSI Signal Processing, Vol. 25, No. 2 (Special issue on CORDIC), pp. 101–105 (online), DOI: 10.1023/A:1008110704586 (2000).
本研究の動機としては,RISC-Vベクタ拡張(RVV)を用いて,複数の角度に対するCORDICアルゴリズムでの計算をベクトル化してみようというものです.RISC-Vはオープン標準である命令セットアーキテクチャ(ISA)であり,RVVはRISC-Vの目玉機能として規格の策定が進められて,2021年にバージョン1.0として批准されました.
組込みシステム研究会で発表しようと思い立ったのは,近年RISC-Vの組込みシステムでの採用実績が増えてきていることを踏まえてのことです.RISC-Vで標準的にサポートすべき拡張を定めているプロファイルの2023年版では,RVVを必須とする案が審議されている模様です.組み込みシステムにおいて,三角関数を扱う機会は比較的多く,前述のような自動運転車やVR機器は3D処理を行う組込みシステムですし,組込みシステムで信号処理や画像処理を行う機会は多くあります.したがって,表題のような研究は組込みシステム研究会の関心事なのではないかと考えております.
CORDICアルゴリズム
CORDICアルゴリズムは,三角関数,双曲線関数,平方根,乗算,除算,指数,対数を効率よく計算するアルゴリズムです.三角関数におけるアルゴリズムの導出については,Wikipediaが詳しいです(2023年7月現在)ので,参照ください.
三角関数を求めるCORDICアルゴリズムは1956年にJack E. Volderによって提案されました.CORDICには,固定小数点数表記で表したときに,加減算とシフト演算のみで実装できるという特徴があります.
C言語での実装
C言語で実装した固定小数点数表記のCORDICアルゴリズムに基づく$\sin$関数算出のアルゴリズムを次に示します.
#include <stdint.h>
#include <stdbool.h>
#include <math.h>
#define M 32
#define MAX_BIT 30
#define MAX (1 << MAX_BIT)
static bool initialized = false;
static int32_t k_value;
static int32_t angles[M];
void vector_sin_cos_init()
{
if(!initialized) {
k_value = MAX;
for(int i = 0; i < M; i++) {
angles[i] = (int32_t)(atanf(ldexpf(1.0, -i)) / M_PI * MAX);
k_value = (int32_t) (k_value / sqrt(1 + ldexp(1.0, -2.0 * i)));
}
initialized = true;
}
}
struct vector_float {
float x;
float y;
};
static struct vector_float cordic_cos_sin(float theta)
{
int32_t theta_i = (int32_t) (theta / M_PI * MAX);
int32_t x = k_value;
int32_t y = 0;
for (int j = 0; j < M; j++) {
int32_t xx, yy;
if (theta_i < 0) {
xx = x + (y >> j);
yy = y - (x >> j);
theta_i += angles[j];
} else {
xx = x - (y >> j);
yy = y + (x >> j);
theta_i -= angles[j];
}
x = xx;
y = yy;
}
struct vector_float v;
v.x = ((float)x / MAX);
v.y = ((float)y / MAX);
return v;
}
struct angle_sign_float {
float angle;
int sign;
};
static struct angle_sign_float adjust_angle(float theta)
{
int t = (int)(theta / (2.0 * M_PI));
theta -= (2.0 * M_PI) * t;
int sign;
if (theta < -M_PI * 0.5) {
theta += M_PI;
sign = -1;
} else if (theta <= M_PI * 0.5) {
sign = 1;
} else {
theta -= M_PI;
sign = -1;
}
struct angle_sign_float result;
result.angle = theta; // 予稿が間違っていました
result.sign = sign;
return result;
}
void vector_sin_float(int n, float *values)
{
vector_sin_cos_init();
for (int i = 0; i < n; i++) {
struct angle_sign_float a = adjust_angle(values[i]);
struct vector_float v = cordic_cos_sin(a.angle);
values[i] = v.y * a.sign;
}
}
関数は次の4つ定義しています.
- 初期化関数
vector_sin_cos_init
- CORDICアルゴリズム本体の関数
cordic_cos_sin
- 角度$\theta$を$-\pi \leq \theta \leq \pi$に収まるように変換する関数
adjust_angle
- ベクタのsin関数値を求めるドライバ関数
vector_sin_float
関数cordic_cos_sin
では$\cos$関数値と$\sin$関数値の両方を返すので,ドライバ関数を差し替えることで,$\cos$関数値を求めることもできますし,$\cos$関数と$\sin$関数の両方を求めることもできます.ここでは単純化のため,$\sin$関数値を求めるものとします.
関数cordic_cos_sin
では次のような手順で求めます.
- 浮動小数点数表記の
theta
を,πを最大値とする固定小数点数表記のtheta_i
に変換する. - 変数
x
,y
の初期値をそれぞれ$K$, $0$の固定小数点数表記の値とする. - CORDICアルゴリズムにしたがって,加減算とシフト演算によって三角関数値を求める.
-
x
には$\cos\theta$の固定小数点数値が,y
には$\sin\theta$の固定小数点数値が,それぞれ格納される. - 最後に浮動小数点数表記に改めて,構造体
struct vector_float
に格納し,戻り値として返す.
RVVでの実装
ではRVVでの実装を示します.この関数の引数は次のとおりです.
- レジスタ
a0
:n
(角度ベクトル長) - レジスタ
a1
:values
へのポインタ(浮動小数点数表記による角度ベクトル$\theta_n$)
結果として,values
のメモリの値が,$\sin\theta_n$に変換されます.
この実装でポイントになるのは,if
文をベクトルマスクレジスタを用いて実装する点が重要です.このように実装することで,ループ中に条件分岐が現れず,制御ハザードが起きにくくなり,実行速度で大きな利点となります.
詳しくは下記Qiita記事を参照ください.
プロローグ
プロローグでは定数をレジスタにロードしていきます.
-
li t0, const_f
- 浮動小数点数定数のメモリ領域の先頭アドレスを
t0
にロード
- 浮動小数点数定数のメモリ領域の先頭アドレスを
-
flw f0, (t0)
- 浮動小数点数レジスタ
f0
に単精度32ビット定数$2\pi$をロードする - (初期化関数によって
(const_f)
にあらかじめ$2\pi$が格納しておく.以下同様)
- 浮動小数点数レジスタ
-
flw f1, 4(t0)
- 浮動小数点数レジスタ
f1
に単精度32ビット定数2.0 * MAX
すなわち2.0 * (1 << 30)
をロードする. - この値は,浮動小数点数から固定小数点数への変換係数
- 浮動小数点数レジスタ
-
li t0, const_i
- 固定小数点数定数のメモリ領域の先頭アドレスを
t0
にロード
- 固定小数点数定数のメモリ領域の先頭アドレスを
-
lw t3, (t0)
- レジスタ
t3
に$K$(k_value
)の32ビット固定小数点数値をロードする
- レジスタ
-
lw t4, 4(t0)
- レジスタ
t4
に$\pi$の32ビット固定小数点数値をロードする
- レジスタ
-
lw t5, 8(t0)
- レジスタ
t5
に$0.5\pi$の32ビット固定小数点数値をロードする
- レジスタ
外側のループ
loop:
角度ベクトル読み込み
外側のループの冒頭で角度ベクトルを読み込みます.
-
vsetvli t0, a0, e32, m4
- ベクトルのコンフィギュレーション設定命令
-
e32
は1要素が32ビット長であることを示す -
m4
はストライピングにより,4つのベクトルレジスタをグループ化してまとめることを意味する- たとえば
v0, v1, v2, v3
をまとめて1グループでv0
として表す - ベクトルレジスタは32本あるので,
m4
により実質的に4倍の長さの8本のベクトルレジスタが備わっているかのように扱う
- たとえば
- レジスタ
a0
は残りのベクトル長が格納されている - これらの情報をもとにレジスタ
t0
に,外側のループ中で処理する要素数を格納する
-
vle32.v v0,(a1)
- 1要素32ビット長として,ベクトルレジスタ
v0
にレジスタa1
(values
へのポインタ,浮動小数点数表記による角度ベクトル$\theta_n$)で示されるメモリ領域を一括ロードする - ストライピングにより,実際には
v0, v1, v2, v3
の4つのベクトルレジスタにロードされる(以下同様)
- 1要素32ビット長として,ベクトルレジスタ
-
sub a0, a0, t0
- レジスタ
a0
(残りのベクトル長)からレジスタt0
(外側のループ中で処理する要素数)を引いた値をレジスタa0
に格納する - これにより,次のループに備える
- レジスタ
-
slli t0, t0, 2
- レジスタ
t0
(外側のループ中で処理する要素数)を左に2つシフトした値,すなわち4倍した値をレジスタt0
に格納する - 1要素が32ビット長であることから4倍となっている
- これによりレジスタ
t0
は,次のループのためにvalues
へのポインタに加算する値を格納していることになる
- レジスタ
角度の正規化と固定小数点数化
ここまでで次のように各レジスタに格納されています.
- ベクトルレジスタ
v0
: 外側のループで一度に処理する角度ベクトル$\theta_n$(浮動小数点数表記) - 浮動小数点数レジスタ
f0
: $2\pi$(単精度32ビット値) - 浮動小数点数レジスタ
f1
: 浮動小数点数から固定小数点数への変換係数2.0 * MAX
(2.0 * (1 << 30)
)(単精度32ビット値)
-
vfdiv.vf v0, v0, f0
- ベクトルレジスタ
v0
($\theta_n$)の各要素を浮動小数点数レジスタf0
($2\pi$)で割った値をベクトルレジスタv0
に格納する
- ベクトルレジスタ
- 次の3命令により,ベクトルレジスタ
v8
にベクトルレジスタv0
($\theta_n$)の小数部分が格納される-
vfcvt.x.f.v v8, v0
- ベクトルレジスタ
v0
の浮動小数点数値を符号付き整数に変換し,ベクトルレジスタv8
に格納する
- ベクトルレジスタ
-
vfcvt.f.x.v v8, v8
- ベクトルレジスタ
v8
の符号付き整数値を浮動小数点数に変換し,ベクトルレジスタv8
に格納する
- ベクトルレジスタ
-
vfsubvv v8, v0, v8
- ベクトルレジスタ
v0
の各要素についてベクトルレジスタv8
の各要素の値を引いて,ベクトルレジスタv8
に格納する
- ベクトルレジスタ
-
- 次の2命令により,ベクトルレジスタ
v8
に,$\frac{\theta_n}{2\pi}$の小数部分を固定小数点数表記したベクトルtheta_i
が格納される-
vfmul.vf v8, v8, f1
- ベクトルレジスタ
v8
の各要素に浮動小数点数レジスタf1
(浮動小数点数から固定小数点数への変換係数)を掛けた値をベクトルレジスタv8
に格納する
- ベクトルレジスタ
-
vfcvt.x.f.v v8, v8
- ベクトルレジスタ
v8
の浮動小数点数値を符号付き整数に変換し,ベクトルレジスタv8
に格納する
- ベクトルレジスタ
-
角度の正規化と結果符号sign
の生成
ここまでで次のように各レジスタに格納されています.
- レジスタ
t5
: $0.5\pi$(32ビット固定小数点数表記) - ベクトルレジスタ
v8
: 正規化した角度ベクトルtheta_i
(32ビット固定小数点数表記) - レジスタ
t4
: $\pi$(32ビット固定小数点数表記)
-
sub t5, x0, t5
- レジスタ
t5
の符号を反転させる - レジスタ
x0
は常に0 - 直訳するとレジスタ
x0
からレジスタt5
を引いた値をレジスタt5
に格納する - これによりレジスタ
t5
には$-0.5\pi$が格納される
- レジスタ
-
vmslt.vx v0, v8, t5
- ベクトルレジスタ
v8
(theta_i
)の各要素をレジスタt5
($-0.5\pi$)と比較し,より小さい場合には1,そうでない場合は0となるようなベクトル値を求めてベクトルレジスタv0
に格納する - これによりベクトルレジスタ
v0
はベクトルマスクレジスタとして機能するようになる
- ベクトルレジスタ
-
vmmv.m v16, v0
- ベクトルマスクレジスタ
v0
をベクトルレジスタv16
にコピーする
- ベクトルマスクレジスタ
-
vadd.vx v8, v8, t4, v0.t
- ベクトルマスクレジスタ
v0
の要素が1の要素だけ,ベクトルレジスタv8
(theta_i
)からレジスタt4
($\pi$)を足す
- ベクトルマスクレジスタ
-
sub t5, x0, t5
- レジスタ
t5
の符号を反転させる - これによりレジスタ
t5
には$0.5\pi$が格納される
- レジスタ
-
vmsgt.vx v0, v8, t5
- ベクトルレジスタ
v8
(theta_i
)の各要素をレジスタt5
($0.5\pi$)と比較し,より大きい場合には1,そうでない場合は0となるようなベクトル値を求めてベクトルレジスタv0
に格納する - これによりベクトルレジスタ
v0
はベクトルマスクレジスタとして機能するようになる
- ベクトルレジスタ
-
vsub.vx v8, v8, t4, v0.t
- ベクトルマスクレジスタ
v0
の要素が1の要素だけ,ベクトルレジスタv8
(theta_i
)からレジスタt4
($\pi$)を引く
- ベクトルマスクレジスタ
-
vmor.mm v16, v0, v16
- ベクトルレジスタ
v16
とv0
をベクトルマスクレジスタとみなしてOR演算を行い,ベクトルレジスタv16
に格納する - つまり,ベクトルレジスタ
v16
には,最後に$\cos$値と$\sin$値の符号を反転すべき場合には1,そうでない時には0になるようなベクトル値sign
が格納されている
- ベクトルレジスタ
CORDICアルゴリズム本体
ここまでで次のように各レジスタに格納されています.
- ベクトルレジスタ
v8
: 正規化した角度ベクトルtheta_i
(32ビット固定小数点数表記) - レジスタ
t3
: 定数$K$(k_value
)
-
vmv.v.x v24, t3
- レジスタ
t3
(定数$K$,k_value
)の値を各要素に格納したベクトルをベクトルレジスタv24
に格納する
- レジスタ
-
vsub.vv v4, v4, v4
- 直訳すると,ベクトルレジスタ
v4
からv4
を引いた値をv4
に格納する - すなわち,ベクトルレジスタ
v4
に0を格納する
- 直訳すると,ベクトルレジスタ
以降,次のような値として参照します.
- ベクトルレジスタ
v24
: 変数x
(内側のループ完了時に$\cos\theta_n$が格納される) - ベクトルレジスタ
v4
: 変数y
(内側のループ完了時に$\sin\theta_n$が格納される)
次に内側のループの準備をします.
-
li t1, 32
- レジスタ
t1
に内側のループのループ回数32を格納する
- レジスタ
-
li t2, angles
- レジスタ
t2
に定数の静的メモリ領域$\phi_i$(angles
)の先頭アドレスを格納する
- レジスタ
-
mv t6, x0
- レジスタ
t6
に0を格納する
- レジスタ
以降,次のような値として参照します.
- レジスタ
t1
: 内側のループのカウンタ値 - レジスタ
t2
: $\phi_i$(angles
)を参照するときのポインタ値 - レジスタ
t6
: 内側のループの変数j
内側のループ
ここまでで次のように各レジスタに格納されています.
- レジスタ
t2
: $\phi_i$(angles
)を参照するときのポインタ値 - ベクトルレジスタ
v8
: 正規化した角度ベクトルtheta_i
(32ビット固定小数点数表記) - ベクトルレジスタ
v24
: 変数x
(内側のループ完了時に$\cos\theta_n$が格納される) - ベクトルレジスタ
v4
: 変数y
(内側のループ完了時に$\sin\theta_n$が格納される)
in_loop:
-
lw a2, (t2)
- レジスタ
a2
にポインタレジスタt2
($\phi_i$,angles
)の値を格納する
- レジスタ
-
vmslt.vx v0, v8, x0
- ベクトルレジスタ
v8
の各要素の値が負であるならば1,そうでなければ0であるようなベクトル値をベクトルレジスタv0
に格納する
- ベクトルレジスタ
-
vmv.v.v v20, v24
- ベクトルレジスタ
v20
にベクトルレジスタv24
(x
)の値を格納する
- ベクトルレジスタ
-
vrsub.vx v20, v20, x0, v0.t
- ベクトルマスクレジスタ
v0
の各要素の値が1の要素のみ,レジスタx0
からベクトルレジスタv20
の各要素を引いた値をベクトルレジスタv20
に格納する - つまり,
theta_i
に各要素の値が負であるような要素のみ,ベクトルレジスタv20
の符号を反転する
- ベクトルマスクレジスタ
-
vmv.v.v v28, v4
- ベクトルレジスタ
v28
にベクトルレジスタv4
(y
)の値を格納する
- ベクトルレジスタ
ここまでで次のように各レジスタに格納されています.
- レジスタ
a2
: $\phi_i$(angles
)の値 - ベクトルレジスタ
v8
: 正規化した角度ベクトルtheta_i
(32ビット固定小数点数表記) - ベクトルレジスタ
v0
:theta_i
の各要素の値が負であるならば1,そうでなければ0であるようなベクトル値
-
vadd.vx v8, v8, a2, v0.t
- ベクトルマスクレジスタ
v0
の各要素の値が1の要素のみ,ベクトルレジスタv8
にレジスタa2
の値を足す - すなわち,
theta_i
の各要素が負の要素のみ,ベクトルレジスタv8
(theta_i
)に$\phi_i$(angles
)の値を足す
- ベクトルマスクレジスタ
-
vmnot.m v0, v0
- ベクトルマスクレジスタ
v0
のNOT値をベクトルマスクレジスタv0
に格納する
- ベクトルマスクレジスタ
-
vsub.vx v8, v8, a2, v0.t
- ベクトルマスクレジスタ
v0
の各要素の値が1の要素のみ,ベクトルレジスタv8
にレジスタa2
の値を引く - すなわち,
theta_i
の各要素が正または0の要素のみ,ベクトルレジスタv8
(theta_i
)に$\phi_i$(angles
)の値を引く
- ベクトルマスクレジスタ
以上でtheta_i
が更新されます.
-
vrsub.vx v28, v28, x0, v0.t
- ベクトルマスクレジスタ
v0
の各要素の値が1の要素のみ,レジスタx0
からベクトルレジスタv28
を引いた値をベクトルレジスタv28
に格納する - すなわち,
theta_i
の各要素が正または0の要素のみ,ベクトルレジスタv28
の符号を反転する
- ベクトルマスクレジスタ
ここまでで次のように各レジスタに格納されています.
- ベクトルレジスタ
v24
: 変数x
(内側のループ完了時に$\cos\theta_n$が格納される) - ベクトルレジスタ
v4
: 変数y
(内側のループ完了時に$\sin\theta_n$が格納される) - ベクトルレジスタ
v20
: CORDICアルゴリズムで定める条件に従って変数x
の符号を反転させた値 - ベクトルレジスタ
v28
: CORDICアルゴリズムで定める条件に従って変数y
の符号を反転させた値 - レジスタ
t6
: 内側のループの変数j
次の2つの命令で,変数j
の値に従って,ベクトルレジスタv20
,v28
の値を右シフト(v20 >>= j; v28 >>= j;
)します.
vsra.vx v20, v20, t6
vsra.vx v28, v28, t6
次の2つの命令で,変数x
, y
を更新します.
vadd.vv v24, v24, v28
vadd.vv v4, v4, v20
ここまでで次のように各レジスタに格納されています.
- レジスタ
t1
: 内側のループのカウンタ値 - レジスタ
t2
: $\phi_i$(angles
)を参照するときのポインタ値 - レジスタ
t6
: 内側のループの変数j
次の命令で各値を更新します.
addi t6, t6, 1
addi t2, t2, 4
addi t1, t1, -1
レジスタt1
(のループのカウンタ値)が0になるまで内側のループを継続します.
bnez t1, inloop
結果の符号調整と浮動小数点数表記への変換
ここまでで次のように各レジスタに格納されています.
- ベクトルレジスタ
v16
: 結果の各要素を符号反転させるべき場合に1,そうでない場合に0が入っているベクトル値 - ベクトルレジスタ
v24
: $\cos\theta_n$(固定小数点数表記) - ベクトルレジスタ
v4
: $\sin\theta_n$(固定小数点数表記) - 浮動小数点数レジスタ
f1
: 浮動小数点数から固定小数点数への変換係数2.0 * MAX
(2.0 * (1 << 30)
)(単精度32ビット値)
-
vmmv.m v0, v16
- ベクトルレジスタ
v16
に格納していたベクトルマスクレジスタ値をベクトルレジスタv0
に復旧
- ベクトルレジスタ
-
vrsub.vx v24, v24, x0, v0.t
- ベクトルマスクレジスタ
v0
の各要素の値が1の要素のみ,レジスタx0
からベクトルレジスタv24
を引いた値をベクトルレジスタv24
に格納する - すなわち,ベクトルマスクレジスタ
v0
の各要素の値が1の要素のみ,ベクトルレジスタv24
の符号を反転する
- ベクトルマスクレジスタ
-
vrsub.vx v4, v4, x0, v0.t
- ベクトルマスクレジスタ
v0
の各要素の値が1の要素のみ,レジスタx0
からベクトルレジスタv4
を引いた値をベクトルレジスタv4
に格納する - すなわち,ベクトルマスクレジスタ
v0
の各要素の値が1の要素のみ,ベクトルレジスタv4
の符号を反転する
- ベクトルマスクレジスタ
-
vfcvt.f.x.v v4, v4
- ベクトルレジスタ
v4
(符号調整済$\sin\theta_n$, 固定小数点数表記)を浮動小数点数に変換する
- ベクトルレジスタ
-
addi t1, x0, 2
- レジスタ
t1
にレジスタx0
に2を加えた値を格納する - すなわち,レジスタ
t1
に2を格納する
- レジスタ
-
fcvt.s.w f2, t1
- 予稿誤り
- 浮動小数点数レジスタ
f2
にレジスタt1
を浮動小数点数に変換した値を格納する - すなわち浮動小数点数レジスタ
f2
に2.0を格納する
-
fdiv.s f2, f1, f2
- 予稿誤り
- 浮動小数点数レジスタ
f2
に浮動小数点数レジスタf1
からf2
を割った値を格納する - すなわち,浮動小数点数レジスタ
f2
に変換係数MAX
を格納する
-
vfdiv.vf v4, v4, f2
- ベクトルレジスタ
v4
(符号調整済$\sin\theta_n$)を浮動小数点数レジスタf2
(浮動小数点数表記への変換係数)で割る
- ベクトルレジスタ
結果の格納と外側のループの継続
ここまでで次のように各レジスタに格納されています.
- ベクトルレジスタ
v4
: $\sin\theta_n$(浮動小数点数表記) - レジスタ
a1
:values
へのポインタ - レジスタ
t0
: 次のループのためにvalues
へのポインタに加算する値 - レジスタ
a0
: 残りのベクトル長
-
vse32.v v4, (a1)
- ポインタレジスタ
a1
にベクトルレジスタv4
の値を書き込む - すなわち,
values
に$\sin\theta_n$(浮動小数点数表記)を書き込む
- ポインタレジスタ
-
add a1, a1, t0
- レジスタ
a1
にレジスタt0
に値を足す - すなわち
values
のポインタ値を更新
- レジスタ
-
bnez a0, loop
- レジスタ
a0
が0でなければloop
にジャンプする - すなわち,残りのベクトルがある間,外側のループを継続する
- レジスタ
-
ret
- リターン
評価
詳しくは今後発行される予稿を参照ください(下記でリンクを紹介する予定です).
ベクトルサイズと実行命令数の相関関係と相関係数は次のようになりました.
- 提案手法(固定小数点数表記のRVVハンドコーディング実装): $36.932n + 406.53, 0.9611$
- 固定小数点数表記のC実装,GCC 12.2 (
-O2
): $349.35n + 31.996, 1$ - 固定小数点数表記のC実装,Clang 16.3.2 (Auto-vectorization): $175.16n + 52.681, 0.9887$
提案手法の増分が他に比べてかなり小さいことから,現状ではRVVのアセンブリコードをハンドコーディングすることに意味があると考えています.
提案手法の定数項が想定よりかなり大きいのは,最適化オプションを指定していないためです.最適化オプションを指定しなかった理由は,現状の実装では最適化オプションを指定すると不具合を生じるためです.この原因は,アセンブリコードの生成にGCCのインラインアセンブラ機能を使用したのですが,おそらく申告すべき使用レジスタの指定を誤っているためではないかと考えられます.
謝辞
東京大学大学院の塩谷 亮太先生と東京理科大学の福原 淳司先生に助言をいただいた.
本研究はJSPS科研費 22K04657の助成を受けたものである.