Uploaded by Daeun Kim (성선설)

SIMD란 일반 연산과 SIMD의 연산 성능 실허 b9941dfb328e40dbb532374de2900efa

advertisement
란 일반 연산과 SIMD의 연산
성능 실험
SIMD ?
란 일반 연산과 SIMD의 연산 성능 실험
SIMD ?
🔎 SIMD란?
는 Single Instruction, Multiple Data의 약자로 하나의 명령어로 여러 개의 값은 동
시에 계산하는 방식이다.
병렬 컴퓨팅의 한 종류
한 번에 여러 연산을 해야하는 벡터 연산, 행렬 연산에 유용하다.
SIMD
🔨 SSE: SIMD 확장
라 불리는 명령어 셋은 SIMD를 확장한 것으로 한 번
SSE (Streaming SIMD Extensions)
.
에 여러 데이터를 처리할 수 있다
128비트 너비의 레지스터를 사용할 수 있으며, 하나의 명령으로 128 비트만큼의 연산을 수
행할 수 있다. - 벡터 연산을 지원한다.
4개의 32 비트 정수/float
2개의 64 비트 정수/float
이후 SSE2, SSE3, SSE4등 발전 되어 256비트/512비트의 레지스터도 추가되었다.
이후 예시에서 SSE를 사용하기위해
헤더를 include하였다.
<immintrin.h>
SISD
연산과 SIMD 연산 성능 차이
와 SISD의 어셈블리 Instruction
SIMD
다음 블로그를 참고하였다: SIMD에 대한 집중탐구!
란 일반 연산과 SIMD의 연산 성능 실험
SIMD ?
1
직접 덧셈의 어셈블리 코드를 비교해보았다.
🙋 어셈블리 코드 확인하는 방법:
다음 링크를 참고하면 된다:
비주얼 스튜디오 어셈블리코드 확인하기 (DisAssembly)
전체 코드 파일SISD 덧셈 함수
void add(int* a, int* b, int* c)
{
c[0]
c[1]
c[2]
c[3]
=
=
=
=
a[0]
a[1]
a[2]
a[3]
+
+
+
+
b[0];
b[1];
b[2];
b[3];
}
SIMD
덧셈 함수
- void addSIMD(int* a, int* b, int* c)
{
__m128i mA = _mm_load_si128((__m128i*)a);
__m128i mB = _mm_load_si128((__m128i*)b);
__m128i mC = _mm_add_epi32(mA, mB);
란 일반 연산과 SIMD의 연산 성능 실험
SIMD ?
2
_mm_store_si128((__m128i*)c, mC);
}
의 Insturction들
SISD
<br><br>
SIMD
의 Insturction들
파일
<br><br>
같은 결과 값을 내는 연산에서 SIMD연산이 SISD보다 적은 Instruction을 쓰는 걸 볼 수 있
다.
성능 비교 - 시간
덧셈과 같은 단순한 연산에선 성능 차이가 크게 없지만, 행렬 곱셈 등 복잡한 연산에선 2~4
배의 차이를 보인다.
연산이 복잡할수록, 연산 횟수가 많을 수록 성능에 큰 차이를 보인다.
덧셈
개의 elements를 가진 int 배열을 1000000번 연산했을 때의 차이이다.
전체 코드 파일
// ...(생략) 위의 add 함수들과 동일
4
int main()
{
생략
// ... (
)
int a[] = { 1, 2, 3, 4 };
int b[] = { 5, 6, 7, 8 };
int c[4];
auto start = chrono::high_resolution_clock::now();
for (int i = 0; i < 1000000; i++)
{
add(a, b, c);
}
auto end = chrono::high_resolution_clock::now();
auto addTime = chrono::duration_cast<chrono::nanosecond
s>(end - start).count();
란 일반 연산과 SIMD의 연산 성능 실험
SIMD ?
3
cout << "add: " << addTime << "ns" << endl;
start = chrono::high_resolution_clock::now();
for (int i = 0; i < 1000000; i++)
{
addSIMD(a, b, c);
}
end = chrono::high_resolution_clock::now();
auto addSIMDTime = chrono::duration_cast<chrono::nanose
conds>(end - start).count();
cout << "addSIMD: " << addSIMDTime << "ns" << endl;
생략) 결과 비교
// ... (
}
큰 차이를 보이진 않지만, 성능이 향상됨을 볼 수 있다.
행렬 곱셈
이번엔 좀 더 복잡한 연산을 해보자.
1000 * 1000 크기의 행렬 곱셈을 수행해보았다.
전체 코드 파일
// ...
const int N = 1000;
일반 행렬 곱셈
//
void matrixMulti(float* A, float* B, float* C)
{
for (int i = 0; i < N; i++)
{
란 일반 연산과 SIMD의 연산 성능 실험
SIMD ?
4
for (int j = 0; j < N; j++)
{
C[i * N + j] = 0;
for (int k = 0; k < N; k++)
{
C[i * N + j] += A[i * N + k] * B[k * N +
j];
}
}
}
}
비트 레지스터를 사용하여
행렬 곱셈
// 128
SIMD
void matrixMultiplySIMD128(float* A, float* B, float* C)
{
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
__m128 sum = _mm_setzero_ps();
for (int k = 0; k < N; k += 4)
{
__m128 a = _mm_loadu_ps(&A[i * N + k]);
__m128 b = _mm_loadu_ps(&B[k * N + j]);
sum = _mm_add_ps(sum, _mm_mul_ps(a, b));
}
float result[4];
_mm_storeu_ps(result, sum);
C[i * N + j] = result[0] + result[1] + result
[2] + result[3];
}
}
}
비트 레지스터를 사용하여
행렬 곱셈
// 256
SIMD
void matrixMultiplySIMD256(float* A, float* B, float* C)
{
for (int i = 0; i < N; ++i)
란 일반 연산과 SIMD의 연산 성능 실험
SIMD ?
5
{
for (int j = 0; j < N; ++j)
{
__m256 sum
for (int k
{
__m256
__m256
= _mm256_setzero_ps();
= 0; k < N; k += 8)
a = _mm256_loadu_ps(&A[i * N + k]);
b = _mm256_loadu_ps(&B[k * N + j]);
sum = _mm256_add_ps(sum, _mm256_mul_ps(a,
b));
}
float result[8];
_mm256_storeu_ps(result, sum);
C[i * N + j] = result[0] + result[1] + result
[2] + result[3] + result[4] + result[5] + result[6] + resul
t[7];
}
}
}
int main()
{
// ...(
//
생략) 행렬 초기화
일반 행렬 곱셈
auto start = chrono::high_resolution_clock::now();
matrixMulti(A, B, C);
auto end = chrono::high_resolution_clock::now();
auto regularTime = chrono::duration_cast<chrono::millis
econds>(end - start).count();
cout << "Regular Matrix Multiply: " << regularTime <<
"ms" << endl;
행렬 곱셈
// SIMD
128
start = chrono::high_resolution_clock::now();
matrixMultiplySIMD128(A, B, C);
end = chrono::high_resolution_clock::now();
auto simdTime = chrono::duration_cast<chrono::milliseco
란 일반 연산과 SIMD의 연산 성능 실험
SIMD ?
6
nds>(end - start).count();
cout << "SIMD Matrix Multiply: " << simdTime << "ms" <<
endl;
행렬 곱셈
// SIMD
256
start = chrono::high_resolution_clock::now();
matrixMultiplySIMD256(A, B, C);
end = chrono::high_resolution_clock::now();
auto simdTime256 = chrono::duration_cast<chrono::millis
econds>(end - start).count();
cout << "SIMD Matrix Multiply 256: " << simdTime256 <<
"ms" << endl;
생략) 결과 출력 및 비교
// ... (
}
행렬 곱셈에서는 성능 차이가 크게 나는 것을 볼 수 있고,
레지스터의 크기가 커질 수록, 성능이 빨라짐을 볼 수 있다.
위의 예시처럼 1000 * 1000와 같은 큰 행렬을 계산하기 위해선
지스터를 사용하는 것이 좋겠지만,
란 일반 연산과 SIMD의 연산 성능 실험
SIMD ?
__m256
,
__m512
등더큰레
7
만일 벡터 연산과 같이 2~4 요소를 연산한다면
에서 더 좋을 것이다.
__m128
를 사용하는 것이 기능/메모리 차원
기타 출처:
을 이용한 3D 게임 프로그래밍 입문, 프랭크 D. 루나
SIMD - 위키백과, 우리 모두의 백과사전 (wikipedia.org)
스트리밍 SIMD 확장 - 위키백과, 우리 모두의 백과사전 (wikipedia.org)
DirectX 11
란 일반 연산과 SIMD의 연산 성능 실험
SIMD ?
8
Download