3

誰かがここで助けてくれることを願っています。

大きなバイト ベクトルがあり、そこから小さなバイト ベクトル (マスクに基づく) を作成し、それを simd で処理します。

現在、マスクは baseOffset + submask (byte[256]) の配列であり、 > 10^8 があるため、ストレージ用に最適化されています。maxsize サブベクトルを作成し、マスク配列をループ処理して baseOffsset を 256 倍し、マスクのビット オフセットごとに大きなベクトルから読み込み、小さなベクトルに値を順番に入れます。小さい方のベクトルは、多数の VPMADDUBSW を介して処理され、累積されます。この構造を変えることができます。たとえば、ビットを 1 回ウォークして 8K ビット配列バッファを使用し、小さなベクトルを作成します。

サブアレイを作成するより速い方法はありますか?

アプリからコードを取り出してテスト プログラムに入れましたが、元のコードは流動的な状態です (AVX2 に移行し、C# からさらに引き出しています)。

#include "stdafx.h"
#include<stdio.h>
#include <mmintrin.h>
#include <emmintrin.h>
#include <tmmintrin.h>
#include <smmintrin.h>
#include <immintrin.h>


//from 
char N[4096] = { 9, 5, 5, 5, 9, 5, 5, 5, 5, 5 };
//W
char W[4096] = { 1, 2, -3, 5, 5, 5, 5, 5, 5, 5 };

char buffer[4096] ; 





__declspec(align(2))
struct packed_destination{
    char blockOffset;
    __int8   bitMask[32];

};

__m128i sum = _mm_setzero_si128();
packed_destination packed_destinations[10];



void  process128(__m128i u, __m128i s)
{
    __m128i calc = _mm_maddubs_epi16(u, s); // pmaddubsw 
    __m128i loints = _mm_cvtepi16_epi32(calc);
    __m128i hiints = _mm_cvtepi16_epi32(_mm_shuffle_epi32(calc, 0x4e));
    sum = _mm_add_epi32(_mm_add_epi32(loints, hiints), sum);
}

void process_array(char n[], char w[], int length)
{
    sum = _mm_setzero_si128();
    int length128th  = length >> 7;
    for (int i = 0; i < length128th; i++)
    {
        __m128i u = _mm_load_si128((__m128i*)&n[i * 128]);
        __m128i s = _mm_load_si128((__m128i*)&w[i * 128]);
        process128(u, s);
    }
}


void populate_buffer_from_vector(packed_destination packed_destinations[], char n[]  , int  dest_length)
{
    int buffer_dest_index = 0; 
    for (int i = 0; i < dest_length; i++)
    {
        int blockOffset = packed_destinations[i].blockOffset <<8 ;
        // go through mask and copy to buffer
        for (int j = 0; j < 32; j++)
        {
           int joffset = blockOffset  + j << 3; 
            int mask = packed_destinations[i].bitMask[j];
            if (mask & 1 << 0)
                buffer[buffer_dest_index++] = n[joffset +  1<<0 ];
            if (mask & 1 << 1)
                buffer[buffer_dest_index++] = n[joffset +  1<<1];
            if (mask & 1 << 2)
                buffer[buffer_dest_index++] = n[joffset +  1<<2];
            if (mask & 1 << 3)
                buffer[buffer_dest_index++] = n[joffset +   1<<3];
            if (mask & 1 << 4)
                buffer[buffer_dest_index++] = n[joffset +  1<<4];
            if (mask & 1 << 5)
                buffer[buffer_dest_index++] = n[joffset +  1<<5];
            if (mask & 1 << 6)
                buffer[buffer_dest_index++] = n[joffset + 1<<6];
            if (mask & 1 << 7)
                buffer[buffer_dest_index++] = n[joffset +  1<<7];
        };

    }


}

int _tmain(int argc, _TCHAR* argv[])
{
    for (int i = 0; i < 32; ++i)
    {
        packed_destinations[0].bitMask[i] = 0x0f;
        packed_destinations[1].bitMask[i] = 0x04;
    }
    packed_destinations[1].blockOffset = 1;

    populate_buffer_from_vector(packed_destinations, N, 1);
    process_array(buffer, W, 256);

    int val = sum.m128i_i32[0] +
        sum.m128i_i32[1] +
        sum.m128i_i32[2] +
        sum.m128i_i32[3];
    printf("sum is %d"  , val);
    printf("Press Any Key to Continue\n");
    getchar();
    return 0;
}

通常、一部のワークロードではマスクの使用率は 5 ~ 15% で、25 ~ 100% になります。

MASKMOVDQU は近いですが、保存する前にマスクに従って /swl を再パックする必要があります..

4

1 に答える 1

1

既存のコードのいくつかの最適化:

データがまばらな場合は、追加のビットをテストする前に、各 8 ビット マスク値の追加のテストを追加することをお勧めします。

        int mask = packed_destinations[i].bitMask[j];
        if (mask != 0)
        {
            if (mask & 1 << 0)
                buffer[buffer_dest_index++] = n[joffset +  1<<0 ];
            if (mask & 1 << 1)
                buffer[buffer_dest_index++] = n[joffset +  1<<1];
            ...

次に、process128関数を大幅に最適化できます。

inline __m128i process128(const __m128i u, const __m128i s, const __m128i sum)
{
    const __m128i vk1 = _mm_set1_epi16(1);
    __m128i calc = _mm_maddubs_epi16(u, s);
    calc = _mm_madd_epi16(v, vk1);
    return _mm_add_epi32(sum, calc);
}

SSE 命令数を 6 から 3 に減らすだけでなく、sumグローバル変数への依存を避けるためにパラメーターも作成しました (優れたソフトウェア エンジニアリングだけでなく、グローバル変数を避けることは常に良い考えです)。特定のコンパイラの最適化を阻害する可能性があるため)。

コードのプロファイルを確認することは興味深いでしょう (インストルメンテーションではなく適切なサンプリング プロファイラーを使用)。

于 2015-05-20T07:23:12.713 に答える