2

この質問を次のステートメントで説明させてください。このコードは意図したとおりに機能しますが、実際には非常に遅いです。newton メソッドの収束を速くする方法や、float 配列などをいじることなく __m256 var を単一の float に等しく設定する方法はありますか?

__m256 nthRoot(__m256 a, int root){

#define aligned __declspec(align(16)) float

// uses the calculation
// n_x+1 = (1/root)*(root * x + a / pow(x,root))


//initial numbers
aligned r[8];
aligned iN[8];
aligned mN[8];

//Function I made to fill arrays
/*
template<class T>
void FillArray(T a[],T b)
{
int n = sizeof(a)/sizeof(T);
for(int i = 0; i < n; a[i++] = b);
}*/

//fills the arrays
FillArray(iN,(1.0f/(float)root));
FillArray(mN,(float)(root-1));
FillArray(r,(float)root);

//loads the arrays into the sse componenets
__m256 R = _mm256_load_ps(r);
__m256 Ni = _mm256_load_ps(iN);
__m256 Nm = _mm256_load_ps(mN);

    //sets initaial guess to 1 / (a * root)
__m256 x = _mm256_rcp_ps(_mm256_mul_ps(R,a));

for(int i = 0; i < 20 ; i ++){
    __m256 tmpx = x;
    for(int k = 0 ; k < root -2 ; k++){
        tmpx = _mm256_mul_ps(x,tmpx);
    }
    //f over f'
    __m256 tar = _mm256_mul_ps(a,_mm256_rcp_ps(tmpx)); 
    //fmac with Ni*X+tar
    tar = _mm256_fmadd_ps(Nm,x,tar);
    //Multipled by Ni
    x = _mm256_mul_ps(Ni,tar);
}
return x;
}

編集#1

__m256 SSEnthRoot(__m256 a, int root){

__m256 R = _mm256_set1_ps((float)root);
__m256 Ni = _mm256_set1_ps((1.0f)/((float)root));
__m256 Nm = _mm256_set1_ps((float)(root -1));

__m256 x = _mm256_mul_ps(a,_mm256_rcp_ps(R));

for(int i = 0; i < 10 ; i ++){
    __m256 tmpx = x;
    for(int k = 0 ; k < root -2 ; k++){
        tmpx = _mm256_mul_ps(x,tmpx);
    }
    //f over f'
    __m256 tar = _mm256_mul_ps(a,_mm256_rcp_ps(tmpx)); 
    //mult nm x then add tar because my compiler stoped thinking that fmadd is a valid instruction
    tar = _mm256_add_ps(_mm256_mul_ps(Nm,x),tar);
    //Multiplied by the inverse of power
    x = _mm256_mul_ps(Ni,tar);
}

return x;
}

ニュートン法をより速く収束させるためのヒントやポインタ(メモリの種類ではない)をいただければ幸いです。

_mm256_rcp_ps() を使用した _mm256_set1_ps() 関数呼び出しで編集 #2 を削除

__m256 SSEnthRoot(__m256 a, int root){
__m256 R = _mm256_set1_ps((float)root);
__m256 Ni = _mm256_rcp_ps(R);
__m256 Nm = _mm256_set1_ps((float)(root -1));

__m256 x = _mm256_mul_ps(a,Ni);
for(int i = 0; i < 20 ; i ++){
    __m256 tmpx = x;
    for(int k = 0 ; k < root -2 ; k++)
        tmpx = _mm256_mul_ps(x,tmpx);
    //f over f'
    __m256 tar = _mm256_mul_ps(a,_mm256_rcp_ps(tmpx)); 
    //fmac with Ni*X+tar
            //my compiler believes in fmac again
    tar = _mm256_fmadd_ps(Nm,x,tar);
    //Multiplied by the inverse of power
    x = _mm256_mul_ps(Ni,tar);
}
return x;
}

編集 #3

__m256 SSEnthRoot(__m256 a, int root){
__m256 Ni = _mm256_set1_ps(1.0f/(float)root);
__m256 Nm = _mm256_set1_ps((float)(root -1));
__m256 x = _mm256_mul_ps(a,Ni);
for(int i = 0; i < 20 ; i ++){
    __m256 tmpx = x;
    for(int k = 0 ; k < root -2 ; k++)
        tmpx = _mm256_mul_ps(x,tmpx);
    __m256 tar = _mm256_mul_ps(a,_mm256_rcp_ps(tmpx)); 
    tar = _mm256_fmadd_ps(Nm,x,tar);
    x = _mm256_mul_ps(Ni,tar);
}
return x;
}
4

3 に答える 3

2

ベクトルのすべての要素を__m256単一の値に設定するには:

__m256 v = _mm256_set1_ps(1.0f);

またはあなたの特定のケースでは:

__m256 R =  _mm256_set1_ps((float)root);
__m256 Ni = _mm256_set1_ps((1.0f/(float)root));
__m256 Nm = _mm256_set1_ps((float)(root-1));

FillArray明らかに、この変更を行ったら、ものを取り除くことができます。

于 2013-06-12T21:07:52.753 に答える
2

あなたのpow機能は非効率的です。

for(int k = 0 ; k < root -2 ; k++)
        tmpx = _mm256_mul_ps(x,tmpx);

あなたの例では、29 乗根を取っています。が必要 pow(x, 29-1) = x^28です。現在、そのために 27 回の乗算を使用していますが、6 回の乗算だけでそれを行うことができます。

x^28 = (x^4)*(x^8)*(x^16)
x^4 = y -> 2 multiplications
x^8 = y*y = z -> 1 multiplication
x^16 = z^2 = w-> 1 multiplications
y*z*w -> 2 multiplications
6 multiplications in total

これはあなたのコードの改良版で、私のシステムでは約 2 倍高速ですpow_avx_fastAVXを使用して一度に8つのフロートに対してx ^ nを実行する、私が作成した新しい関数を使用します。たとえば、27ではなく6回の乗算でx ^ 28を実行し ます。私の回答をさらに見てください。結果がある程度の許容範囲内にあるバージョンを見つけましたxacc。収束が迅速に行われる場合、これははるかに高速になる可能性があります。

inline __m256 pow_avx_fast(__m256 x, const int n) {
    //n must be greater than zero
    if(n%2 == 0) {
        return pow_avx_fast(_mm256_mul_ps(x, x), n/2);
    }
    else {
        if(n>1) return _mm256_mul_ps(x,pow_avx_fast(_mm256_mul_ps(x, x), (n-1)/2));
        return x;
    }
}

inline __m256 SSEnthRoot_fast(__m256 a, int root) {
    // n_x+1 = (1/root)*((root-1) * x + a / pow(x,root-1))
    __m256 R = _mm256_set1_ps((float)root);
    __m256 Ni = _mm256_rcp_ps(R);
    __m256 Nm = _mm256_set1_ps((float)(root -1));

    __m256 x = _mm256_mul_ps(a,Ni);
    for(int i = 0; i < 20 ; i ++) {
        __m256 tmpx = pow_avx_fast(x, root-1);
        //f over f'
        __m256 tar = _mm256_mul_ps(a,_mm256_rcp_ps(tmpx)); 
        //fmac with Ni*X+tar
        //tar = _mm256_fmadd_ps(Nm,x,tar);
        tar = _mm256_add_ps(_mm256_mul_ps(Nm,x),tar);
        //Multiplied by the inverse of power
        x = _mm256_mul_ps(Ni,tar);
    }
    return x;
}

効率的な関数を記述する方法の詳細については、http ://en.wikipedia.org/wiki/Addition-chain_exponentiationおよび http://en.wikipedia.org/wiki/Exponentiation_by_squaringpowのリンクを参照してください。

また、最初の推測はあまり良くないかもしれません。これは、メソッドに基づいて n 乗根を見つけるためのスカラー コードです (ただし、powおそらくあなたのものよりも高速な数学関数を使用しています)。16 の 4 乗根 (つまり 2) を解くには、約 50 回の反復が必要です。使用する20回の反復では、4000を超えて返されますが、これは2.0に近いものではありません。したがって、ある程度の許容範囲内で妥当な答えが得られるように、十分な反復を行うようにメソッドを調整する必要があります。

float fx(float a, int n, float x) {
    return 1.0f/n * ((n-1)*x + a/pow(x, n-1));
}
float scalar_nthRoot_v2(float a, int root) {
    //sets initaial guess to 1 / (a * root)
    float x = 1.0f/(a*root);
    printf("x0 %f\n", x);
    for(int i = 0; i<50; i++) {
        x = fx(a, root, x);
        printf("x %f\n", x);
    }
    return x;
}

ここからニュートン法の式を取得しました。 http://en.wikipedia.org/wiki/Nth_root_algorithm

これは、特定の許容範囲内で結果を返すか、反復xacc後に収束しない場合は終了する関数のバージョンです。nmaxこの関数は、収束が 20 回未満の反復で発生する場合、メソッドよりもはるかに高速になる可能性があります。8 つのフロートすべてが一度に収束する必要があります。言い換えれば、7 つが収束し、1 つが収束しない場合、他の 7 つは収束しないものを待つ必要があります。これは SIMD (GPU 上でも) の問題ですが、一般的には SIMD なしで実行するよりも高速です。

int get_mask(const __m256 dx, const float xacc) {
    __m256i mask = _mm256_castps_si256(_mm256_cmp_ps(dx, _mm256_set1_ps(xacc), _CMP_GT_OQ));
    return _mm_movemask_epi8(_mm256_castsi256_si128(mask)) + _mm_movemask_epi8(_mm256_extractf128_si256(mask,1));
}

inline __m256 SSEnthRoot_fast_xacc(const __m256 a, const int root, const int nmax, float xacc) {
    // n_x+1 = (1/root)*(root * x + a / pow(x,root))
    __m256 R = _mm256_set1_ps((float)root);
    __m256 Ni = _mm256_rcp_ps(R);
    //__m256 Ni = _mm256_set1_ps(1.0f/root);
    __m256 Nm = _mm256_set1_ps((float)(root -1));

    __m256 x = _mm256_mul_ps(a,Ni);

    for(int i = 0; i <nmax ; i ++) {
        __m256 tmpx = pow_avx_fast(x, root-1);
        __m256 tar = _mm256_mul_ps(a,_mm256_rcp_ps(tmpx)); 
        //tar = _mm256_fmadd_ps(Nm,x,tar);
        tar = _mm256_add_ps(_mm256_mul_ps(Nm,x),tar);
        tmpx = _mm256_mul_ps(Ni,tar);
        __m256 dx = _mm256_sub_ps(tmpx,x);
        dx = _mm256_max_ps(_mm256_sub_ps(_mm256_setzero_ps(), dx), dx); //fabs(dx)
        int cnt = get_mask(dx, xacc);
        if(cnt == 0) return x;
        x = tmpx;
    }
    return x; //at least one value out of eight did not converge by nmax.
}

これは、n<=0 でも機能する avx の pow 関数のより一般的なバージョンです。

__m256 pow_avx(__m256 x, const int n) {
    if(n<0) {
        return pow_avx(_mm256_rcp_ps(x), -n);
    }
    else if(n == 0) { 
        return _mm256_set1_ps(1.0f);
    }
    else if(n == 1) {
        return x;
    }
    else if(n%2 ==0) {
        return pow_avx(_mm256_mul_ps(x, x), n/2);
    }
    else {
        return _mm256_mul_ps(x,pow_avx(_mm256_mul_ps(x, x), (n-1)/2));
    }
}

その他の提案

n乗根を見つけるSIMD数学ライブラリを使用できます。 SSE および AVX 用の SIMD 数学ライブラリ

Intel の場合、高価でクローズド ソースの SVML を使用できます (Intel の OpenCL ドライバーは SVML を使用しているため、無料で入手できます)。AMD の場合、無料ですがクローズド ソースである LIBM を使用できます。http://software-lisc.fbk.eu/avx_mathfun/https://bitbucket.org/eschnett/vecmathlib/wiki/Homeなど、いくつかのオープン ソース SIMD 数学ライブラリがあります。

于 2013-06-13T09:02:34.127 に答える
0

おそらく、ログ ドメインでこれを行う必要があります。

pow(a,1/root) == exp( log(x) /root) 

Julien Pommier は、SSE、SSE2 の log および exp 関数を含むsse_mathfun.hを持っていますが、特にそれらを使用したとは言えません。この手法は、avx に拡張できる場合があります。

于 2013-06-13T03:21:47.963 に答える