17.5. 整数関数

Note

詳しくは「表:四則・絶対値・最小最大値関数」(Table B.110, “表:四則・絶対値・最小最大値関数”)を参照ください。

OpenCL-Cの整数型はC99と基本的には同じものと考えて構いません。符号付きと符号無しのintとuintがあり。整数をサポートする関数には、プロセッサの演算器の機能であるMAD演算を活用して、本来複数の処理に分ける計算を1つの計算サイクルで行なうことができます。

四則演算(加算、減算、除算、乗算)があり、ビット演算のために2〜3個便利な組込み関数が存在するだけです。

gentype rotate (gentype v, gentype i)

vの各要素に対して、iビットの左シフトを行ないます。左シフトで押し出されたビットは右側に挿入されます。

uint x = 0x12345678;
uint shift = 4;

output = rotate(x, shift); //(1)
printf("rotate: %x\n", output);

(1)

4桁左にシフトします。

rotate: 23456781
short upsample (char hi, uchar lo)
ushort upsample (uchar hi, uchar lo)
shortn upsample (charn hi, ucharn lo)
ushortn upsample (ucharn hi, ucharn lo)
int upsample (short hi, ushort lo)
uint upsample (ushort hi, ushort lo)
intn upsample (shortn hi, ushortn lo)
uintn upsample (ushortn hi, ushortn lo)
long upsample (int hi, uint lo)
ulong upsample (uint hi, uint lo)
longn upsample (intn hi, uintn lo)
ulongn upsample (uintn hi, uintn lo)

upsample関数は以下の計算をします。

result[i] = ((short)hi[i] << 8) | lo[i]
result[i] = ((ushort)hi[i] << 8) | lo[i]
result[i] = ((int)hi[i] << 16) | lo[i]
result[i] = ((uint)hi[i] << 16) | lo[i]
result[i] = ((long)hi[i] << 32) | lo[i]
result[i] = ((ulong)hi[i] << 32) | lo[i]

16ビット型であれば、hiを8ビット左にシフト、32ビットであれば、hiを16ビット左にシフトし、loを下位ビットに設定します。

uint hi = 0x888888;
uint lo = 0x222222;
ulong output_upsample;
output_upsample = upsample(hi,lo);
printf("upsample: %lx\n",output_upsample);
printf("upsample for mad: %lx\n", upsample(mul_hi(a,b), a*b))

出力. 

upsample: 88888800222222
upsample for mad: 10404204001

gentype popcount (gentype x)

引数xの0でないビットの数を計算します。

uint nonzero = 0x00001111;
output = popcount(nonzero); //(1)
printf("popcount: %x\n", output);

出力. 

popcount: 4

gentype clz (gentype x)

最上位ビットから連続する0のビット数を計算します。

printf("clz: %u\n", clz(0xf0001111)); //(1)
printf("clz: %u\n", clz(0x00000000)); //(2)

(1)

上位ビットがfで0がありません。

(2)

上位ビットから下位ビットまで全て0。

出力. 

clz: 0
clz: 32

最後に加算をするための関数を2つご案内します。

gentype hadd (gentype x, gentype y)

和のオーバーフローを発生させずに`(x + y) >> 1`を計算します。

gentype rhadd (gentype x, gentype y)

和のオーバーフローを発生させずに`(x + y + 1) >> 1`を計算します。

output = hadd(a, b); //(1)
printf("hadd: %#lx\n", output);

output = rhadd(a, b); //(2)
printf("rhadd: %#lx\n", output);

(1)

(概ね)`(a+b)/2`の計算をします。

(2)

(概ね)`(a+b+1)/2`の計算をします。

出力. 

hadd: 0x102001
rhadd: 0x102001

17.5.1. MADと乗算

Note

詳しくは「表:乗算関数」(Table B.111, “表:乗算関数”)を参照ください。

基本的な乗算をより詳しく見てみましょう。例えばOpenCL-Cでuint型間で基本乗算を行なうと以下のような結果となります。

a = 102001
b = 102001
a * b = 4204001

これをulong型にすると計算結果は`10404204001`となります。明らかに桁が少ないことになります。

下記が検証に使ったコードと出力です。

サンプル. 

uint a = 0x102001;
uint b = 0x102001;
uint c = 0;
uint output;
printf("a = %lx\n", a);
printf("b = %lx\n", b);
printf("c = %lx\n", c);

output = a * b; //(1)
printf("a times b = %lx\n", output);

ulong a_long = 0x102001;
ulong b_long = 0x102001;
ulong output_long;

output_long = a_long * b_long; //(2)
printf("(ulong)a times b = %lx\n", output_long);

(1)

aとbのかけ算。結果はuintの最大値を超える。

(2)

longでのかけ算。

出力. 

a = 102001
b = 102001
c = 0
a times b = 4204001
(ulong)a times b = 10404204001

もちろんlongを使えば溢れた桁についても結果に参入できますが、OpenCLではメモリのアラインメントが重要な最適化要因となるため、32ビットでの演算が望ましいと言えます。

その答えとしては以下の定義にあるmulやmadといった高速演算関数です。

gentype mul_hi (
    gentype x,
    gentype y)

`x * y`を計算し、算出した値の上半分のビットを返します。

gentype mul24 (
    gentype x,
    gentype y)

2つの24ビット整数値xとyの積を計算します。xとyは両方とも32ビット整数値ですが計算では下位24ビットのみを使います。xとyの両方が符号つきのときは、[-223, 223-1]`、符号無しのときは[0, 224-1]`の範囲内にあるとき使うことが推奨されています。xとyがこれらの範囲内にないとき、計算結果は実装システムに依存します。

gentype mad24 (
    gentype x,
    gentype y,
    gentype z)

2つの24ビット整数値xとyの積を計算し、32ビット整数値zを足します。

gentype mad_hi (
    gentype a,
    gentype b,
    gentype c)

`mul_hi(a, b) + c`を計算します。

サンプル. 

uint a = 0x102001;
uint b = 0x102001;
uint c = 0;
uint output;

output = mad24(a, b, c); //(1)
printf("mad24: %lx\n", output);

output = mul_hi(a, b); //(2)
printf("mul_hi: %lx\n", output);

output = mad_hi(a, b, c); //(3)
printf("mad_hi: %lx\n", output);

(1)

24ビットの整数の積と32ビットの和。

(2)

x * y

(3)

mul_hi(a, b) + c

出力. 

mad24: 4204001
mul_hi: 104
mad_hi: 104

17.5.2. 飽和演算

Note

詳しくは「表:(飽和)加算・減算関数」(Table B.112, “表:(飽和)加算・減算関数”)を参照ください。

飽和演算とは信号処理の際に`0xFFFFFFFF`以上の値となる計算は全て、`0xFFFFFFFF`と一意的に処理を行なうような演算のことを指します。

前の項目のサンプルコードで使った、`mad_sat`関数がそれにあたります。以下がOpenCLでサポートしている、整数型の飽和演算です。

gentype mad_sat (
    gentype a,
    gentype b,
    gentype c)

`a * b + c`を計算します。結果については飽和を適用します。

gentype add_sat (gentype x, gentype y)

`x + y`を計算します。結果については飽和を適用します。

uint a = 0x102001;
uint b = 0x102001;
uint c = 0;
uint output;

int a_complement = 0x100000;
int b_complement = 0x100000;
int c_complement = 1111;

output = add_sat(a, b); //(1)
printf("add_sat: %#lx\n", output);

output = mad_sat(a, b, c); //(2)
printf("mad_sat: %u\n", output);

output = mad_sat(a_complement, b_complement, c_complement); //(3)
printf("mad_sat with sign: %d\n", output);

(1) (2)

`a * b + c`の結果を飽和。

(3)

`a_complement * b_complement + c_complement`の結果を飽和。

出力. 

add_sat: 0x204002 //(1)
mad_sat: 4294967295 //(2)
mad_sat with sign: 2147483647 //(3)

(1)

飽和つき足し算。

(2)

飽和つきmad(uint型)

(3)

飽和つきmad(int型)

uintに格納できる整数の範囲は、`0〜4,294,967,295`となります。(2の補数で表す)intに格納できる整数の範囲は、`−2,147,483,648〜2,147,483,647`です。

Copyright 2018-2019, by Masaki Komatsu