5

このコードは、行列を 4 通りに転置します。1 つ目は、順次書き込み、非順次読み取りを行います。2番目はその逆です。次の 2 つは同じですが、キャッシュが書き込みをスキップします。シーケンシャル書き込みが高速になり、キャッシュのスキップが高速になるようです。私が理解できないのは、キャッシュがスキップされている場合、シーケンシャル書き込みがさらに高速なのはなぜですか?

QueryPerformanceCounter(&before);
for (i = 0; i < N; ++i)
   for (j = 0; j < N; ++j)
      tmp[i][j] = mul2[j][i];
QueryPerformanceCounter(&after);
printf("Transpose 1:\t%ld\n", after.QuadPart - before.QuadPart);

QueryPerformanceCounter(&before);
for (j = 0; j < N; ++j)
   for (i = 0; i < N; ++i)
     tmp[i][j] = mul2[j][i];
QueryPerformanceCounter(&after);
printf("Transpose 2:\t%ld\n", after.QuadPart - before.QuadPart);

QueryPerformanceCounter(&before);
for (i = 0; i < N; ++i)
   for (j = 0; j < N; ++j)
      _mm_stream_si32(&tmp[i][j], mul2[j][i]);
QueryPerformanceCounter(&after);
printf("Transpose 3:\t%ld\n", after.QuadPart - before.QuadPart);

QueryPerformanceCounter(&before);
for (j = 0; j < N; ++j)
   for (i = 0; i < N; ++i)
      _mm_stream_si32(&tmp[i][j], mul2[j][i]);
QueryPerformanceCounter(&after);
printf("Transpose 4:\t%ld\n", after.QuadPart - before.QuadPart);

編集:出力は

Transpose 1:    47603
Transpose 2:    92449
Transpose 3:    38340
Transpose 4:    69597
4

2 に答える 2

4

CPU には、キャッシュ ラインでの書き込みを組み合わせて 1 つのバーストで発生させるための書き込み組み合わせバッファーがあります。この場合 (シーケンシャル書き込みでキャッシュがスキップされる)、この書き込み結合バッファーは 1 ライン キャッシュとして機能し、キャッシュがスキップされない場合と非常に似た結果になります。

正確には、キャッシュがスキップされた場合でも、書き込みはメモリへのバーストでまだ発生しています。

ここで書き込み結合ロジックの動作を参照してください。

于 2011-12-18T05:29:51.727 に答える
0

マトリックスの非線形メモリ レイアウトを試して、キャッシュの使用率を向上させることができます。4x4 の 32 ビット float タイルを使用すると、各キャッシュ ラインへの 1 回のアクセスのみで転置を行うことができます。さらに、_MM_TRANSPOSE4_PS を使用すると、ボーナス タイルの転置を簡単に行うことができます。

非常に大きな行列の転置は、依然として非常にメモリを集中的に使用する操作です。帯域幅は引き続き大幅に制限されますが、少なくともキャッシュ ワードの負荷は最適に近くなります。パフォーマンスがまだ最適化されているかどうかはわかりません。私のテストでは、数年前のラップトップが約 300 ミリ秒で 16k*16k (1G メモリ) を転置できることが示されています。

_mm_stream_sd も使用しようとしましたが、実際には何らかの理由でパフォーマンスが低下します。_mm_stream_psでパフォーマンスが低下する理由を実際に推測できるほど、非一時的なメモリ書き込みを理解していません。考えられる理由はもちろん、キャッシュ ラインがすでに L1 キャッシュにあり、書き込み操作の準備ができていることです。

しかし、実際には非線形行列の重要な部分は、転置を完全に回避し、単純にタイルに適した順序で乗算を実行する可能性があります。しかし、アルゴリズムのキャッシュ管理に関する知識を向上させるために使用している転置コードしかありません。

プリフェッチがメモリ帯域幅の使用を改善するかどうかはまだテストしていません。現在のコードは、1 サイクルあたり約 0.5 命令で実行されます (キャッシュに適した優れたコードは、この CPU で 1 サイクルあたり約 2 in で実行されます)。これにより、プリフェッチ命令用に多くの空きサイクルが残され、非常に複雑な計算でも実行時のプリフェッチ タイミングを最適化できます。

私の転置ベンチマーク テストのサンプル コードは次のとおりです。

#define MATSIZE 16384
#define align(val, a) (val + (a - val % a))
#define tilewidth 4
typedef int matrix[align(MATSIZE,tilewidth)*MATSIZE] __attribute__((aligned(64)));


float &index(matrix m, unsigned i, unsigned j)
{
    /* tiled address calculation */
    /* single cache line is used for 4x4 sub matrices (64 bytes = 4*4*sizeof(int) */
    /* tiles are arranged linearly from top to bottom */
    /*
     * eg: 16x16 matrix tile positions:
     *   t1 t5 t9  t13
     *   t2 t6 t10 t14
     *   t3 t7 t11 t15
     *   t4 t8 t12 t16
     */
    const unsigned tilestride = tilewidth * MATSIZE;
    const unsigned comp0 = i % tilewidth; /* i inside tile is least significant part */
    const unsigned comp1 = j * tilewidth; /* next part is j multiplied by tile width */
    const unsigned comp2 = i / tilewidth * tilestride;
    const unsigned add = comp0 + comp1 + comp2;
    return m[add];
}

/* Get start of tile reference */
float &tile(matrix m, unsigned i, unsigned j)
{
    const unsigned tilestride = tilewidth * MATSIZE;
    const unsigned comp1 = j * tilewidth; /* next part is j multiplied by tile width */
    const unsigned comp2 = i / tilewidth * tilestride;
    return m[comp1 + comp2];

}
template<bool diagonal>
static void doswap(matrix mat, unsigned i, unsigned j)
{
        /* special path to swap whole tile at once */
        union {
            float *fs;
            __m128 *mm;
        } src, dst;
        src.fs = &tile(mat, i, j);
        dst.fs = &tile(mat, j, i);
        if (!diagonal) {
            __m128 srcrow0 = src.mm[0];
            __m128 srcrow1 = src.mm[1];
            __m128 srcrow2 = src.mm[2];
            __m128 srcrow3 = src.mm[3];
            __m128 dstrow0 = dst.mm[0];
            __m128 dstrow1 = dst.mm[1];
            __m128 dstrow2 = dst.mm[2];
            __m128 dstrow3 = dst.mm[3];

            _MM_TRANSPOSE4_PS(srcrow0, srcrow1, srcrow2, srcrow3);
            _MM_TRANSPOSE4_PS(dstrow0, dstrow1, dstrow2, dstrow3);

#if STREAMWRITE == 1
            _mm_stream_ps(src.fs +  0, dstrow0);
            _mm_stream_ps(src.fs +  4, dstrow1);
            _mm_stream_ps(src.fs +  8, dstrow2);
            _mm_stream_ps(src.fs + 12, dstrow3);
            _mm_stream_ps(dst.fs +  0, srcrow0);
            _mm_stream_ps(dst.fs +  4, srcrow1);
            _mm_stream_ps(dst.fs +  8, srcrow2);
            _mm_stream_ps(dst.fs + 12, srcrow3);
#else
            src.mm[0] = dstrow0;
            src.mm[1] = dstrow1;
            src.mm[2] = dstrow2;
            src.mm[3] = dstrow3;
            dst.mm[0] = srcrow0;
            dst.mm[1] = srcrow1;
            dst.mm[2] = srcrow2;
            dst.mm[3] = srcrow3;
#endif
        } else {
            __m128 srcrow0 = src.mm[0];
            __m128 srcrow1 = src.mm[1];
            __m128 srcrow2 = src.mm[2];
            __m128 srcrow3 = src.mm[3];

            _MM_TRANSPOSE4_PS(srcrow0, srcrow1, srcrow2, srcrow3);

#if STREAMWRITE == 1
            _mm_stream_ps(src.fs +  0, srcrow0);
            _mm_stream_ps(src.fs +  4, srcrow1);
            _mm_stream_ps(src.fs +  8, srcrow2);
            _mm_stream_ps(src.fs + 12, srcrow3);
#else
            src.mm[0] = srcrow0;
            src.mm[1] = srcrow1;
            src.mm[2] = srcrow2;
            src.mm[3] = srcrow3;
#endif
        }
    }
}

static void transpose(matrix mat)
{
    const unsigned xstep = 256;
    const unsigned ystep = 256;
    const unsigned istep = 4;
    const unsigned jstep = 4;
    unsigned x1, y1, i, j;
    /* need to increment x check for y limit to allow unrolled inner loop
     * access entries close to diagonal axis
     */
    for (x1 = 0; x1 < MATSIZE - xstep + 1 && MATSIZE > xstep && xstep; x1 += xstep)
        for (y1 = 0; y1 < std::min(MATSIZE - ystep + 1, x1 + 1); y1 += ystep)
            for ( i = x1 ; i < x1 + xstep; i += istep ) {
                for ( j = y1 ; j < std::min(y1 + ystep, i); j+= jstep )
                {
                    doswap<false>(mat, i, j);
                }
                if (i == j && j < (y1 + ystep))
                    doswap<true>(mat, i, j);
            }

    for ( i = 0 ; i < x1; i += istep ) {
        for ( j = y1 ; j < std::min(MATSIZE - jstep + 1, i); j+= jstep )
        {
            doswap<false>(mat, i, j);
        }
        if (i == j)
            doswap<true>(mat, i, j);
    }
    for ( i = x1 ; i < MATSIZE - istep + 1; i += istep ) {
        for ( j = y1 ; j < std::min(MATSIZE - jstep + 1, i); j+= jstep )
        {
            doswap<false>(mat, i, j);
        }
        if (i == j)
            doswap<true>(mat, i, j);
    }
    x1 = MATSIZE - MATSIZE % istep;
    y1 = MATSIZE - MATSIZE % jstep;

    for ( i = x1 ; i < MATSIZE; i++ )
        for ( j = 0 ; j < std::min((unsigned)MATSIZE, i); j++ )
                    std::swap(index(mat, i, j+0), index(mat, j+0, i));

    for ( i = 0; i < x1; i++ )
        for ( j = y1 ; j < std::min((unsigned)MATSIZE, i) ; j++ )
                    std::swap(index(mat, i, j+0), index(mat, j+0, i));
}
于 2014-08-15T06:31:47.500 に答える