例如简化的C++,它只会通过矩阵的所有元素,因为这就是你的嵌套循环所做的。你并不需要计算i*M+j
,为你的公式不使用I/J,在任何特定的方式,它只是通过所有的元素去一次:
void muldata(float* fxx, const float* fx, const float* fy, const unsigned int M, const unsigned int N) {
int ofs = 0;
do {
fxx[ofs] = fx[ofs] * fy[ofs];
++ofs;
} while (ofs < M*N);
}
会让clang -O3 -m32
(V4.0.0)产生这样的:
muldata(float*, float const*, float const*, unsigned int, unsigned int): # @muldata(float*, float const*, float const*, unsigned int, unsigned int)
push ebp
push ebx
push edi
push esi
sub esp, 12
mov esi, dword ptr [esp + 48]
mov edi, dword ptr [esp + 40]
mov ecx, dword ptr [esp + 36]
mov edx, dword ptr [esp + 32]
mov eax, 1
imul esi, dword ptr [esp + 44]
cmp esi, 1
cmova eax, esi
xor ebp, ebp
cmp eax, 8
jb .LBB0_7
mov ebx, eax
and ebx, -8
je .LBB0_7
mov dword ptr [esp + 4], eax # 4-byte Spill
cmp esi, 1
mov eax, 1
mov dword ptr [esp], ebx # 4-byte Spill
cmova eax, esi
lea ebx, [ecx + 4*eax]
lea edi, [edx + 4*eax]
mov dword ptr [esp + 8], ebx # 4-byte Spill
mov ebx, dword ptr [esp + 40]
cmp edx, dword ptr [esp + 8] # 4-byte Folded Reload
lea eax, [ebx + 4*eax]
sbb bl, bl
cmp ecx, edi
sbb bh, bh
and bh, bl
cmp edx, eax
sbb al, al
cmp dword ptr [esp + 40], edi
mov edi, dword ptr [esp + 40]
sbb ah, ah
test bh, 1
jne .LBB0_7
and al, ah
and al, 1
jne .LBB0_7
mov eax, dword ptr [esp] # 4-byte Reload
lea ebx, [edi + 16]
lea ebp, [ecx + 16]
lea edi, [edx + 16]
.LBB0_5: # =>This Inner Loop Header: Depth=1
movups xmm0, xmmword ptr [ebp - 16]
movups xmm2, xmmword ptr [ebx - 16]
movups xmm1, xmmword ptr [ebp]
movups xmm3, xmmword ptr [ebx]
add ebp, 32
add ebx, 32
mulps xmm2, xmm0
mulps xmm3, xmm1
movups xmmword ptr [edi - 16], xmm2
movups xmmword ptr [edi], xmm3
add edi, 32
add eax, -8
jne .LBB0_5
mov eax, dword ptr [esp] # 4-byte Reload
mov edi, dword ptr [esp + 40]
cmp dword ptr [esp + 4], eax # 4-byte Folded Reload
mov ebp, eax
je .LBB0_8
.LBB0_7: # =>This Inner Loop Header: Depth=1
movss xmm0, dword ptr [ecx + 4*ebp] # xmm0 = mem[0],zero,zero,zero
mulss xmm0, dword ptr [edi + 4*ebp]
movss dword ptr [edx + 4*ebp], xmm0
inc ebp
cmp ebp, esi
jb .LBB0_7
.LBB0_8:
add esp, 12
pop esi
pop edi
pop ebx
pop ebp
ret
这是远远优于您的代码(默认情况下包括循环矢量化)。
如果你指定对齐指针并且使M/N编译时间保持不变,那么它可能会产生更好的结果。
我只是验证了C++变种作品将cpp.sh网站,它延伸到这一点:
#include <iostream>
void muldata(float* fxx, const float* fx, const float* fy, const unsigned int M, const unsigned int N) {
unsigned int ofs = 0;
do {
fxx[ofs] = fx[ofs] * fy[ofs];
++ofs;
} while (ofs < M*N);
}
int main()
{
// constexpr unsigned int M = 1;
// constexpr unsigned int N = 1;
// const float fx[M*N] = { 2.2f };
// const float fy[M*N] = { 3.3f };
constexpr unsigned int M = 3;
constexpr unsigned int N = 2;
const float fx[M*N] = { 2.2f, 1.0f, 0.0f,
1.0f, 1.0f, 1e-24f };
const float fy[M*N] = { 3.3f, 3.3f, 3.3f,
5.5f, 1e30f, 1e-24f };
float fr[M*N];
muldata(fr, fx, fy, M, N);
for (unsigned int i = 0; i < N; ++i) {
for (unsigned int j = 0; j < M; ++j) std::cout << fr[i*M+j] << " ";
std::cout << std::endl;
}
}
输出:
7.26 3.3 0
5.5 1e+30 0
还有的还评论了1x1的输入数据,这应该是你的案例中第一个要调试的东西。试着让这个例子在你最喜欢的C++ IDE中工作,然后用你的汇编代码替换muldata
,并通过它进行调试,看看它发生了什么。
如果'mulps xmm5,xmm6'为零,那么'xmm5'或'xmm6'中的一个为零。那它是哪一个?你为什么不使用C++,它肯定会产生更快的循环,至少它会优化'i * M'等等,再加上它可能会更容易调试和维护。 – Ped7g
其实当然有其他的角落案例,其中float x * float y = 0,即使当x/y都非为零时,因为float本身的精确度有限,所以例如'1e-23 * 1e-23 = 0 '等等......如果没有来自调试器的一些示例数据,就不可能知道你遇到了什么,如果你看到这些数据,你可能也会看到答案。 – Ped7g
我认为这个问题不在于寄存器的值。因为如果我改变istruction mulps与istruction addps代码工作 –