多线程固定大小矩阵向量乘法针对具有非均匀缓存的多核 CPU 进行了优化

问题描述 投票:0回答:1

我想为我的 CPU 和缓存布局(AMD Zen 2/4)优化的固定大小矩阵(~3500x3500 浮点数)实现并行矩阵向量乘法,该矩阵重复执行以更改输入向量(设置时间不是关键的、持续的性能是)。编程语言是 C++。

任何人都可以指出如何根据缓存利用率和同步(减少+=)开销对矩阵和线程进行分区的好的(也许是最佳的)策略吗?比如什么块大小最好,以及如何用多个线程最好地遍历乘法?然后我会尝试将该策略应用于我的特定 CPU。

我可以自由复制矩阵数据以提高跨多个 CCX 的缓存效率,并且矩阵也不需要在 RAM 中连续。我可以选择任何形式和顺序来保证最佳效率。

或者,如果有人知道这样的库或能够共享代码,我也很感激。不需要重新发明东西 :)

谢谢。

parallel-processing x86-64 matrix-multiplication simd blas
1个回答
0
投票

首先尝试Eigen。根据编译器的不同,您可能需要为正确的 SIMD 手动定义宏,对于 Zen 2-3,您需要

EIGEN_VECTORIZE_AVX
EIGEN_VECTORIZE_FMA
EIGEN_VECTORIZE_AVX2
,对于 Zen 4 也需要
EIGEN_VECTORIZE_AVX512
.
另外,请务必在项目设置中启用 OpenMP。

如果您想进一步提高性能,您的第一目标是节省内存带宽。矩阵乘以向量实际上肯定会成为内存瓶颈,而不是计算瓶颈。

像那样将矩阵重塑成面板。

表中的数字是内存中元素从0开始的索引。
仅代替 4,对 AVX 使用面板高度 = 32,或对 AVX512 使用 64。
另外,不要忘记至少按矢量大小对齐数据,最好按 64 字节(缓存行)

注意矩阵的最后一个面板可能需要这些列的零填充。理想情况下,输出向量还需要一些额外的元素来使它们的长度是面板高度的倍数,否则你需要特殊的代码来处理矩阵的最后一个面板。

在内循环中,做类似的事情,未经测试。

// Compute product of width*32 matrix by vector of length `width`,
// the result is vector of length 32
void multiplyInner_avx( const float* mat, const float* vec, size_t width, float* rdi )
{
    // Initialize the accumulators
    __m256 acc0 = _mm256_setzero_ps();
    __m256 acc1 = _mm256_setzero_ps();
    __m256 acc2 = _mm256_setzero_ps();
    __m256 acc3 = _mm256_setzero_ps();

    // Compute these products
    const float* const vecEnd = vec + width;
    while( vec < vecEnd )
    {
        const __m256 v = _mm256_broadcast_ss( vec );
        vec++;

        acc0 = _mm256_fmadd_ps( v, _mm256_load_ps( mat ), acc0 );
        acc1 = _mm256_fmadd_ps( v, _mm256_load_ps( mat + 8 ), acc1 );
        acc2 = _mm256_fmadd_ps( v, _mm256_load_ps( mat + 16 ), acc2 );
        acc3 = _mm256_fmadd_ps( v, _mm256_load_ps( mat + 24 ), acc3 );
        mat += 32;
    }

    // Store the products
    _mm256_store_ps( rdi, acc0 );
    _mm256_store_ps( rdi + 8, acc1 );
    _mm256_store_ps( rdi + 16, acc2 );
    _mm256_store_ps( rdi + 24, acc3 );
}

对于 Zen 4,您需要上述的另一个版本,以利用 AVX512 向量。

在外部循环中,将矩阵分成大小大致相等的批次,以便批次数等于 CPU 中的硬件线程数。将每个批次分派到不同的 CPU 线程,一种简单的方法是 OpenMP。

理想情况下,确保过程稳定,即当您为不同的向量调用乘法函数时,相同批次的输入矩阵被分配到相同的 CPU 内核中。

© www.soinside.com 2019 - 2024. All rights reserved.