AVX-512によるvpermpsを用いたlog(x)の実装
初めに
今回はfloat配列に対する対数関数
log(x)の近似計算方法
まず
よって
もし
ところで「
テーブルの作成
floatのバイナリ表現は
符号 | 指数部 | 仮数部 |
---|---|---|
1 | 8 | 23 |
でした。今
正確には
テーブル参照
後は
テーブルは次のようにして作成します。
self.logTbl1 = []
self.logTbl2 = []
self.L = 4
LN = 1 << self.L
for i in range(LN):
u = (127 << 23) | ((i*2+1) << (23 - self.L - 1))
v = 1 / uint2float(u)
v = uint2float(float2uint(v)) # enforce C float type instead of double
# v = numpy.float32(v)
self.logTbl1.append(v)
self.logTbl2.append(math.log(v))
i << (23 - self.L)
ではなく(i*2+1) << (23 - self.L - 1)
としているのは、上図「テーブル参照」のように、+(1 << (23 - L - 1))
して四捨五入の効果を狙うためです。
v = uint2float(float2uint(v))
はPythonのfloat(つまりCのdouble)を正しくCのfloat値として扱うための処理です。
Pythonでfloatの値をfloat32の値に変換するというちょっと変わった記事を書いたのは、このテーブルを正しく作成するためでした。
vpermpsを用いたテーブル参照
AVXやAVX-512にはvgatherという複数のテーブル参照をまとめて行うSIMD命令があります。
vgatherdd(out|k, ptr(rax + idx*4))
outとidxにSIMDレジスタを指定すると要素ごとにout[i] = *(rax + idx[i])となります。残念ながら、この命令はかなり遅く20clk以上かかります。したがって、出来るならこの命令は避けたいです。
そこで今回は vpermps(x, y, z)
はZMMレジスタx, y, zに対してyのz[i]
番目の要素y[z[i]]
をx[i]
設定する命令です(y[i]
は0から15までの値です)。
vpermps(x, y, z)
x[i] = y[z[i]]
vpermpsはメモリにアクセスせず、レジスタ間だけの処理で高速(レイテンシ3)です。
expのときと同様
AVX-512による実装
入力をv0としてtbl1, tbl2や1などをレジスタに置いて計算します。
un(vgetexpps)(v1, v0) # n
un(vgetmantps)(v0, v0, 0) # a
un(vpsrad)(v2, v0, 23 - self.L) # d
un(vpermps)(v3, v2, self.tbl1) # b
un(vfmsub213ps)(v0, v3, self.one) # c = a * b - 1
un(vpermps)(v3, v2, self.tbl2) # log_b
un(vfmsub132ps)(v1, v3, ptr_b(rip+self.LOG2)) # z = n * log2 - log_b
vgetexpps
は v0(vgetmantps
は仮数部 vrndscaleps
やvreduceps
と同じく便利ですね。
テーブル参照として使うvpermps
はインデックスで使うレジスタの下位4ビットだけを参照するので、vpsrad
で右シフトした後マスクするする必要はありません。
1の付近での例外処理
上記アルゴリズムでほぼ完成なのですが、元々の入力値
そのため、
# precise log for small |x-1|
un(vsubps)(v2, keepX, self.one) # x-1
un(vandps)(v3, v2, ptr_b(rip+self.C_0x7fffffff)) # |x-1|
un(vcmpltps)(vk, v3, ptr_b(rip+self.BOUNDARY))
un(vmovaps)(zipOr(v0, vk), v2) # c = v0 = x-1
un(vxorps)(zipOr(v1, vk), v1, v1) # z = 0
0x7fffffff
とアンドをとる(vandps)ことで絶対値 BOUNDARY
よりも小さければ(vcmpltps)マスクレジスタvkを設定します。
続く2行で、それまで計算していたテーブル引きの値v1, v2を御破算して
全体のコードはfmathのlogCore
を参照ください。今回は必要な一時レジスタが多いためアンロール回数は4に設定しています。
補足(2023/6/15追記)
AVX-512にはvpermpsの他に5ビットテーブル引きができるvpermi2psという命令もあります。こちらのほうがより精度が高くなるのでよいのですが、使うと若干遅くなってしまいました。それで今回はvpermpsを使っています。
まとめ
AVX-512を使ったstd::logfの近似計算例を紹介しました。AVX-512にはvgetexppsやvgetmantpsなどの浮動小数点数の指数部や仮数部を得る便利な命令があります。
vpermpsは小さいテーブル参照として使えると便利です。
Discussion