通过 ARM NEON 进行向量矩阵乘法
Vector Matrix multiplication via ARM NEON
我有一个任务 - 通过大列主矩阵(10 000 行,400 列)乘以大行向量(10 000 个元素)。我决定使用 ARM NEON,因为我对这项技术很好奇,想了解更多。
这是我写的向量矩阵乘法的工作示例:
//float* vec_ptr - a pointer to vector
//float* mat_ptr - a pointer to matrix
//float* out_ptr - a pointer to output vector
//int matCols - matrix columns
//int vecRows - vector rows, the same as matrix
for (int i = 0, max_i = matCols; i < max_i; i++) {
for (int j = 0, max_j = vecRows - 3; j < max_j; j+=4, mat_ptr+=4, vec_ptr+=4) {
float32x4_t mat_val = vld1q_f32(mat_ptr); //get 4 elements from matrix
float32x4_t vec_val = vld1q_f32(vec_ptr); //get 4 elements from vector
float32x4_t out_val = vmulq_f32(mat_val, vec_val); //multiply vectors
float32_t total_sum = vaddvq_f32(out_val); //sum elements of vector together
out_ptr[i] += total_sum;
}
vec_ptr = &myVec[0]; //switch ptr back again to zero element
}
问题是计算时间很长 - 在 iPhone 7+ 上需要 30 毫秒,而我的目标是 1 毫秒,如果可能的话甚至更少。当前执行时间是可以理解的,因为我启动乘法迭代 400 * (10000 / 4) = 1 000 000 次。
此外,我尝试处理 8 个元素而不是 4 个。这似乎有所帮助,但数字离我的目标还很远。
我知道我可能会犯一些可怕的错误,因为我是 ARM NEON 的新手。如果有人能给我一些如何优化我的代码的提示,我会很高兴。
此外 - 是否值得通过 ARM NEON 进行大型向量矩阵乘法?这项技术是否适合这样的目的?
您的代码完全有缺陷:假设 matCols
和 vecRows
均为 4,它迭代 16 次。那么 SIMD 有什么意义?
主要性能问题在于float32_t total_sum = vaddvq_f32(out_val);
:
您永远不应该在循环内将矢量转换为标量,因为它会导致每次花费大约 15 个周期的管道危险。
解决方案:
float32x4x4_t myMat;
float32x2_t myVecLow, myVecHigh;
myVecLow = vld1_f32(&pVec[0]);
myVecHigh = vld1_f32(&pVec[2]);
myMat = vld4q_f32(pMat);
myMat.val[0] = vmulq_lane_f32(myMat.val[0], myVecLow, 0);
myMat.val[0] = vmlaq_lane_f32(myMat.val[0], myMat.val[1], myVecLow, 1);
myMat.val[0] = vmlaq_lane_f32(myMat.val[0], myMat.val[2], myVecHigh, 0);
myMat.val[0] = vmlaq_lane_f32(myMat.val[0], myMat.val[3], myVecHigh, 1);
vst1q_f32(pDst, myMat.val[0]);
- 一次计算所有四行
- 通过
vld4
即时进行矩阵转置(旋转)
- 执行向量-标量乘法-累加,而不是向量-向量乘法和水平加法,这会导致管道危险。
你问的是SIMD适不适合矩阵运算?一个简单的 "yes" 将是一个巨大的轻描淡写。你甚至不需要循环。
我有一个任务 - 通过大列主矩阵(10 000 行,400 列)乘以大行向量(10 000 个元素)。我决定使用 ARM NEON,因为我对这项技术很好奇,想了解更多。
这是我写的向量矩阵乘法的工作示例:
//float* vec_ptr - a pointer to vector
//float* mat_ptr - a pointer to matrix
//float* out_ptr - a pointer to output vector
//int matCols - matrix columns
//int vecRows - vector rows, the same as matrix
for (int i = 0, max_i = matCols; i < max_i; i++) {
for (int j = 0, max_j = vecRows - 3; j < max_j; j+=4, mat_ptr+=4, vec_ptr+=4) {
float32x4_t mat_val = vld1q_f32(mat_ptr); //get 4 elements from matrix
float32x4_t vec_val = vld1q_f32(vec_ptr); //get 4 elements from vector
float32x4_t out_val = vmulq_f32(mat_val, vec_val); //multiply vectors
float32_t total_sum = vaddvq_f32(out_val); //sum elements of vector together
out_ptr[i] += total_sum;
}
vec_ptr = &myVec[0]; //switch ptr back again to zero element
}
问题是计算时间很长 - 在 iPhone 7+ 上需要 30 毫秒,而我的目标是 1 毫秒,如果可能的话甚至更少。当前执行时间是可以理解的,因为我启动乘法迭代 400 * (10000 / 4) = 1 000 000 次。
此外,我尝试处理 8 个元素而不是 4 个。这似乎有所帮助,但数字离我的目标还很远。
我知道我可能会犯一些可怕的错误,因为我是 ARM NEON 的新手。如果有人能给我一些如何优化我的代码的提示,我会很高兴。
此外 - 是否值得通过 ARM NEON 进行大型向量矩阵乘法?这项技术是否适合这样的目的?
您的代码完全有缺陷:假设 matCols
和 vecRows
均为 4,它迭代 16 次。那么 SIMD 有什么意义?
主要性能问题在于float32_t total_sum = vaddvq_f32(out_val);
:
您永远不应该在循环内将矢量转换为标量,因为它会导致每次花费大约 15 个周期的管道危险。
解决方案:
float32x4x4_t myMat;
float32x2_t myVecLow, myVecHigh;
myVecLow = vld1_f32(&pVec[0]);
myVecHigh = vld1_f32(&pVec[2]);
myMat = vld4q_f32(pMat);
myMat.val[0] = vmulq_lane_f32(myMat.val[0], myVecLow, 0);
myMat.val[0] = vmlaq_lane_f32(myMat.val[0], myMat.val[1], myVecLow, 1);
myMat.val[0] = vmlaq_lane_f32(myMat.val[0], myMat.val[2], myVecHigh, 0);
myMat.val[0] = vmlaq_lane_f32(myMat.val[0], myMat.val[3], myVecHigh, 1);
vst1q_f32(pDst, myMat.val[0]);
- 一次计算所有四行
- 通过
vld4
即时进行矩阵转置(旋转)
- 执行向量-标量乘法-累加,而不是向量-向量乘法和水平加法,这会导致管道危险。
你问的是SIMD适不适合矩阵运算?一个简单的 "yes" 将是一个巨大的轻描淡写。你甚至不需要循环。