나는 이것으로 머리를 때리고있다. 매트릭스 A
에 매트릭스 B
을 곱하는 SSE 기반 알고리즘이 있습니다. 또한 A, B 또는 둘 모두가 바뀌는 위치에 대한 작업을 구현해야합니다. 나는 그것의 순진한 구현을했다. 아래에 표현 된 4x4 행렬 코드 (꽤 표준적인 SSE 연산이다.)는 A*B^T
연산은 A*B
의 두 배 정도 걸린다. ATLAS 구현은 A*B
에 대해 비슷한 값을 반환하고, 조인에 의한 곱셈과 거의 동일한 결과를 보여 주므로 효율적인 방법이 있음을 나에게 알립니다.전치 행렬을보다 효율적으로 곱하는 방법은 무엇입니까?
MM 승산 다음 MT 승산 (전치 행렬을 곱한 행렬) 용
m1 = (mat1.m_>>2)<<2;
n2 = (mat2.n_>>2)<<2;
n = (mat1.n_>>2)<<2;
for (k=0; k<n; k+=4) {
for (i=0; i<m1; i+=4) {
// fetch: get 4x4 matrix from mat1
// row-major storage, so get 4 rows
Float* a0 = mat1.el_[i]+k;
Float* a1 = mat1.el_[i+1]+k;
Float* a2 = mat1.el_[i+2]+k;
Float* a3 = mat1.el_[i+3]+k;
for (j=0; j<n2; j+=4) {
// fetch: get 4x4 matrix from mat2
// row-major storage, so get 4 rows
Float* b0 = mat2.el_[k]+j;
Float* b1 = mat2.el_[k+1]+j;
Float* b2 = mat2.el_[k+2]+j;
Float* b3 = mat2.el_[k+3]+j;
__m128 b0r = _mm_loadu_ps(b0);
__m128 b1r = _mm_loadu_ps(b1);
__m128 b2r = _mm_loadu_ps(b2);
__m128 b3r = _mm_loadu_ps(b3);
{ // first row of result += first row of mat1 * 4x4 of mat2
__m128 cX1 = _mm_add_ps(_mm_mul_ps(_mm_load_ps1(a0+0), b0r), _mm_mul_ps(_mm_load_ps1(a0+1), b1r));
__m128 cX2 = _mm_add_ps(_mm_mul_ps(_mm_load_ps1(a0+2), b2r), _mm_mul_ps(_mm_load_ps1(a0+3), b3r));
Float* c0 = this->el_[i]+j;
_mm_storeu_ps(c0, _mm_add_ps(_mm_add_ps(cX1, cX2), _mm_loadu_ps(c0)));
}
{ // second row of result += second row of mat1 * 4x4 of mat2
__m128 cX1 = _mm_add_ps(_mm_mul_ps(_mm_load_ps1(a1+0), b0r), _mm_mul_ps(_mm_load_ps1(a1+1), b1r));
__m128 cX2 = _mm_add_ps(_mm_mul_ps(_mm_load_ps1(a1+2), b2r), _mm_mul_ps(_mm_load_ps1(a1+3), b3r));
Float* c1 = this->el_[i+1]+j;
_mm_storeu_ps(c1, _mm_add_ps(_mm_add_ps(cX1, cX2), _mm_loadu_ps(c1)));
}
{ // third row of result += third row of mat1 * 4x4 of mat2
__m128 cX1 = _mm_add_ps(_mm_mul_ps(_mm_load_ps1(a2+0), b0r), _mm_mul_ps(_mm_load_ps1(a2+1), b1r));
__m128 cX2 = _mm_add_ps(_mm_mul_ps(_mm_load_ps1(a2+2), b2r), _mm_mul_ps(_mm_load_ps1(a2+3), b3r));
Float* c2 = this->el_[i+2]+j;
_mm_storeu_ps(c2, _mm_add_ps(_mm_add_ps(cX1, cX2), _mm_loadu_ps(c2)));
}
{ // fourth row of result += fourth row of mat1 * 4x4 of mat2
__m128 cX1 = _mm_add_ps(_mm_mul_ps(_mm_load_ps1(a3+0), b0r), _mm_mul_ps(_mm_load_ps1(a3+1), b1r));
__m128 cX2 = _mm_add_ps(_mm_mul_ps(_mm_load_ps1(a3+2), b2r), _mm_mul_ps(_mm_load_ps1(a3+3), b3r));
Float* c3 = this->el_[i+3]+j;
_mm_storeu_ps(c3, _mm_add_ps(_mm_add_ps(cX1, cX2), _mm_loadu_ps(c3)));
}
}
// Code omitted to handle remaining rows and columns
}
는, I는 다음 명령 b3r하는 b0r 저장 적절 루프 변수 변경 대신 :
__m128 b0r = _mm_set_ps(b3[0], b2[0], b1[0], b0[0]);
__m128 b1r = _mm_set_ps(b3[1], b2[1], b1[1], b0[1]);
__m128 b2r = _mm_set_ps(b3[2], b2[2], b1[2], b0[2]);
__m128 b3r = _mm_set_ps(b3[3], b2[3], b1[3], b0[3]);
나는 둔화가 부분적으로 한 번에 한 줄씩 당기는 것과 컬럼을 얻기 위해 매번 4 개의 값을 저장하는 것의 차이 때문이라고 생각한다. 그러나 나는 이것에 대해 다른 방법으로 생각한다. B의 행을 당긴다. 그런 다음 공동 As의 열은 4 열의 결과를 저장하는 데 드는 비용을 변경합니다.
나는 또한 B 행을 행으로 가져오고 _MM_TRANSPOSE4_PS(b0r, b1r, b2r, b3r);
을 사용하여 전환을 수행했지만 (실제로 매크로에 몇 가지 추가 최적화가있을 수 있다고 생각했지만) 실제 개선이 없습니다.
표면적으로 나는 이것이 더 빨라야한다고 느낍니다 ... 관련된 도트 제품이 본질적으로 더 효율적인 것처럼 보이는 행이 될 것이지만 점 제품을 똑바로 세우려고하면 바로 같은 결과를 저장하십시오.
무엇이 여기에 있습니까?
추가 : 그냥 명확히하기 위해 매트릭스를 조 변경하지 않으려 고합니다. 나는 그들을 따라 반복하는 것을 선호한다. 문제는 _mm_set_ps 명령이 _mm_load_ps보다 훨씬 느리다는 것입니다.
또한 A 행렬의 4 개 행을 저장 한 다음 1 개의로드, 4 개의 곱하기 및 4 개의 곱하기 명령어와 3 개를 포함한 2 개의 덧셈을 포함하는 4 개의 중괄호로 묶은 세그먼트를 대체했지만 조금 유용했습니다 . 타이밍은 동일하게 유지 (그래, 나는 코드가 내 테스트 컴파일 변경했다고 확인하기 위해 디버그 문으로 그것을 시도 말했다 디버그 문은 물론, 프로파일 링하기 전에 제거되었습니다.) :
{ // first row of result += first row of mat1 * 4x4 of mat2
__m128 cX1 = _mm_hadd_ps(_mm_mul_ps(a0r, b0r), _mm_mul_ps(a0r, b1r));
__m128 cX2 = _mm_hadd_ps(_mm_mul_ps(a0r, b2r), _mm_mul_ps(a0r, b3r));
Float* c0 = this->el_[i]+j;
_mm_storeu_ps(c0, _mm_add_ps(_mm_hadd_ps(cX1, cX2), _mm_loadu_ps(c0)));
}
{ // second row of result += second row of mat1 * 4x4 of mat2
__m128 cX1 = _mm_hadd_ps(_mm_mul_ps(a1r, b0r), _mm_mul_ps(a1r, b1r));
__m128 cX2 = _mm_hadd_ps(_mm_mul_ps(a1r, b2r), _mm_mul_ps(a1r, b3r));
Float* c0 = this->el_[i+1]+j;
_mm_storeu_ps(c0, _mm_add_ps(_mm_hadd_ps(cX1, cX2), _mm_loadu_ps(c0)));
}
{ // third row of result += third row of mat1 * 4x4 of mat2
__m128 cX1 = _mm_hadd_ps(_mm_mul_ps(a2r, b0r), _mm_mul_ps(a2r, b1r));
__m128 cX2 = _mm_hadd_ps(_mm_mul_ps(a2r, b2r), _mm_mul_ps(a2r, b3r));
Float* c0 = this->el_[i+2]+j;
_mm_storeu_ps(c0, _mm_add_ps(_mm_hadd_ps(cX1, cX2), _mm_loadu_ps(c0)));
}
{ // fourth row of result += fourth row of mat1 * 4x4 of mat2
__m128 cX1 = _mm_hadd_ps(_mm_mul_ps(a3r, b0r), _mm_mul_ps(a3r, b1r));
__m128 cX2 = _mm_hadd_ps(_mm_mul_ps(a3r, b2r), _mm_mul_ps(a3r, b3r));
Float* c0 = this->el_[i+3]+j;
_mm_storeu_ps(c0, _mm_add_ps(_mm_hadd_ps(cX1, cX2), _mm_loadu_ps(c0)));
}
업데이트 : 의 행로드를 a3r
으로 중괄호로 이동하여 레지스터 스래 싱을 피하려는 시도도 실패했습니다.
실제로 매트릭스를 조 변경해야하지만 다른 방식으로 액세스하면 안된다는 것을 알고 있습니까? – olivecoder
실제로 행렬을 조 변경 한 것처럼 보입니까? 물론 그 속도는 느려질 것입니다. – RandyGaul
사실, 다른 순서로 항목에 액세스하여 제자리에서 처리하려고합니다. 나는 in-place transpose로 시도했고 거의 같은 타이밍을 가졌다. –