스택큐힙리스트

SSE를 사용하여 4차원 벡터의 평균을 계산하세요. 본문

카테고리 없음

SSE를 사용하여 4차원 벡터의 평균을 계산하세요.

스택큐힙리스트 2023. 8. 31. 01:02
반응형

평균 계산을 위해 4차원 벡터가 배열에 위치한 계산 속도를 높이려고 노력합니다. 여기에 제 코드가 있습니다:

'#include

#include

#include

#include

#include

#include

typedef float dot[4];

#define N 1000000

double gettime ()

{

struct timeval tv;

gettimeofday (&tv, 0);

return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);

}

void calc_avg1 (dot res, const dot array[], int n)

{

int i,j;

memset (res, 0, sizeof (dot));

for (i = 0; i < n; i++)

{

for (j = 0; j<4; j++) res[j] += array[i][j];

}

for (j = 0; j<4; j++) res[j] /= n;

}

void calc_avg2 (dot res, const dot array[], int n)

{

int i;

__v4sf r = _mm_set1_ps (0.0);

for (i=0; i

r /= _mm_set1_ps ((float)n);

_mm_store_ps (res, r);

}

int main ()

{

void *space = malloc (N*sizeof(dot)+15);

dot *array = (dot*)(((unsigned long)space+15) & ~(unsigned long)15);

dot avg __attribute__((aligned(16)));

int i;

double time;

for (i = 0; i < N; i++)

{

array[i][0] = 1.0*random();

array[i][1] = 1.0*random();

array[i][2] = 1.0*random();

}

time = gettime();

calc_avg1 (avg, array, N);

time = gettime() - time;

printf (%f\n%f %f %f\n, time, avg[0], avg[1], avg[2]);

time = gettime();

calc_avg2 (avg, array, N);

time = gettime() - time;

printf (%f\n%f %f %f\n, time, avg[0], avg[1], avg[2]);

return 0;

}

'

그래서 보시다시피 'calc_avg1' 는 순진한 루프를 0부터 4까지 사용하고, 'calc_avg2' 은 이를 SSE 명령어로 대체합니다. 저는 이 코드를 clang 3.4로 컴파일합니다.

'cc -O2 -o test test.c

'

calc_avgX 함수의 분해입니다:

'0000000000400860 :

400860: 55 push %rbp

400861: 48 89 e5 mov %rsp,%rbp

400864: 85 d2 test %edx,%edx

400866: 0f 57 c0 xorps %xmm0,%xmm0

400869: 0f 11 07 movups %xmm0,(%rdi)

40086c: 7e 42 jle 4008b0

40086e: 48 83 c6 0c add $0xc,%rsi

400872: 0f 57 c0 xorps %xmm0,%xmm0

400875: 89 d0 mov %edx,%eax

400877: 0f 57 c9 xorps %xmm1,%xmm1

40087a: 0f 57 d2 xorps %xmm2,%xmm2

40087d: 0f 57 db xorps %xmm3,%xmm3

400880: f3 0f 58 5e f4 addss -0xc(%rsi),%xmm3

400885: f3 0f 11 1f movss %xmm3,(%rdi)

400889: f3 0f 58 56 f8 addss -0x8(%rsi),%xmm2

40088e: f3 0f 11 57 04 movss %xmm2,0x4(%rdi)

400893: f3 0f 58 4e fc addss -0x4(%rsi),%xmm1

400898: f3 0f 11 4f 08 movss %xmm1,0x8(%rdi)

40089d: f3 0f 58 06 addss (%rsi),%xmm0

4008a1: f3 0f 11 47 0c movss %xmm0,0xc(%rdi)

4008a6: 48 83 c6 10 add $0x10,%rsi

4008aa: ff c8 dec %eax

4008ac: 75 d2 jne 400880

4008ae: eb 0c jmp 4008bc

4008b0: 0f 57 c0 xorps %xmm0,%xmm0

4008b3: 0f 57 c9 xorps %xmm1,%xmm1

4008b6: 0f 57 d2 xorps %xmm2,%xmm2

4008b9: 0f 57 db xorps %xmm3,%xmm3

4008bc: f3 0f 2a e2 cvtsi2ss %edx,%xmm4

4008c0: f3 0f 5e dc divss %xmm4,%xmm3

4008c4: f3 0f 11 1f movss %xmm3,(%rdi)

4008c8: f3 0f 5e d4 divss %xmm4,%xmm2

4008cc: f3 0f 11 57 04 movss %xmm2,0x4(%rdi)

4008d1: f3 0f 5e cc divss %xmm4,%xmm1

4008d5: f3 0f 11 4f 08 movss %xmm1,0x8(%rdi)

4008da: f3 0f 5e c4 divss %xmm4,%xmm0

4008de: f3 0f 11 47 0c movss %xmm0,0xc(%rdi)

4008e3: 5d pop %rbp

4008e4: c3 retq

4008e5: 66 66 2e 0f 1f 84 00 nopw %cs:0x0(%rax,%rax,1)

4008ec: 00 00 00 00

00000000004008f0 :

4008f0: 55 push %rbp

4008f1: 48 89 e5 mov %rsp,%rbp

4008f4: 85 d2 test %edx,%edx

4008f6: 0f 57 c0 xorps %xmm0,%xmm0

4008f9: 7e 10 jle 40090b

4008fb: 89 d0 mov %edx,%eax

4008fd: 0f 1f 00 nopl (%rax)

400900: 0f 58 06 addps (%rsi),%xmm0

400903: 48 83 c6 10 add $0x10,%rsi

400907: ff c8 dec %eax

400909: 75 f5 jne 400900

40090b: 66 0f 6e ca movd %edx,%xmm1

40090f: 66 0f 70 c9 00 pshufd $0x0,%xmm1,%xmm1

400914: 0f 5b c9 cvtdq2ps %xmm1,%xmm1

400917: 0f 5e c1 divps %xmm1,%xmm0

40091a: 0f 29 07 movaps %xmm0,(%rdi)

40091d: 5d pop %rbp

40091e: c3 retq

40091f: 90 nop

'

그리고 여기가 결과입니다:

'> ./test

0.004287

1073864320.000000 1074018048.000000 1073044224.000000

0.003661

1073864320.000000 1074018048.000000 1073044224.000000

'

SSE 버전은 1.17배 더 빠르다. 하지만 동일한 작업을 수행하려고 할 때, 즉 배열 내 싱글 프리시전 스칼라의 평균을 계산하는 작업 (예: 'SSE reduction of float vector' 에서 설명한 대로)을 수행 할 때 SSE 버전은 3.32배 더 빠르다. 여기에 calc_avgX 함수의 코드가 있습니다.

'float calc_avg1 (const float array[], int n)

{

int i;

float avg = 0;

for (i = 0; i < n; i++) avg += array[i];

return avg / n;

}

float calc_avg3 (const float array[], int n)

{

int i;

__v4sf r = _mm_set1_ps (0.0);

for (i=0; i

r = _mm_hadd_ps (r, r);

r = _mm_hadd_ps (r, r);

return r[0] / n;

}

'

그래서 내 질문은 다음과 같다: 왜 나는 맨 마지막 예제에서 SSE로 많은 이득을 얻을까 (단일 float 스칼라의 평균 계산) 첫 번째 예제에서는 이득을 얻지 못하는 걸까 (4D 벡터의 평균 계산)? 나에게는 이 두 작업이 거의 동일해 보인다. 만약 첫 번째 예제에서 계산 속도를 높이는 올바른 방법이 있다면, 그 방법은 무엇인가요?

UPD:

만약 관련 있다고 생각하시면, 스칼라의 평균을 계산한 두 번째 예의 어셈블리도 제공합니다. (또한 clang3.4 -O2로 컴파일되었습니다).

'0000000000400860 :

400860: 55 push %rbp

400861: 48 89 e5 mov %rsp,%rbp

400864: 85 d2 test %edx,%edx

400866: 0f 57 c0 xorps %xmm0,%xmm0

400869: 0f 11 07 movups %xmm0,(%rdi)

40086c: 7e 42 jle 4008b0

40086e: 48 83 c6 0c add $0xc,%rsi

400872: 0f 57 c0 xorps %xmm0,%xmm0

400875: 89 d0 mov %edx,%eax

400877: 0f 57 c9 xorps %xmm1,%xmm1

40087a: 0f 57 d2 xorps %xmm2,%xmm2

40087d: 0f 57 db xorps %xmm3,%xmm3

400880: f3 0f 58 5e f4 addss -0xc(%rsi),%xmm3

400885: f3 0f 11 1f movss %xmm3,(%rdi)

400889: f3 0f 58 56 f8 addss -0x8(%rsi),%xmm2

40088e: f3 0f 11 57 04 movss %xmm2,0x4(%rdi)

400893: f3 0f 58 4e fc addss -0x4(%rsi),%xmm1

400898: f3 0f 11 4f 08 movss %xmm1,0x8(%rdi)

40089d: f3 0f 58 06 addss (%rsi),%xmm0

4008a1: f3 0f 11 47 0c movss %xmm0,0xc(%rdi)

4008a6: 48 83 c6 10 add $0x10,%rsi

4008aa: ff c8 dec %eax

4008ac: 75 d2 jne 400880

4008ae: eb 0c jmp 4008bc

4008b0: 0f 57 c0 xorps %xmm0,%xmm0

4008b3: 0f 57 c9 xorps %xmm1,%xmm1

4008b6: 0f 57 d2 xorps %xmm2,%xmm2

4008b9: 0f 57 db xorps %xmm3,%xmm3

4008bc: f3 0f 2a e2 cvtsi2ss %edx,%xmm4

4008c0: f3 0f 5e dc divss %xmm4,%xmm3

4008c4: f3 0f 11 1f movss %xmm3,(%rdi)

4008c8: f3 0f 5e d4 divss %xmm4,%xmm2

4008cc: f3 0f 11 57 04 movss %xmm2,0x4(%rdi)

4008d1: f3 0f 5e cc divss %xmm4,%xmm1

4008d5: f3 0f 11 4f 08 movss %xmm1,0x8(%rdi)

4008da: f3 0f 5e c4 divss %xmm4,%xmm0

4008de: f3 0f 11 47 0c movss %xmm0,0xc(%rdi)

4008e3: 5d pop %rbp

4008e4: c3 retq

4008e5: 66 66 2e 0f 1f 84 00 nopw %cs:0x0(%rax,%rax,1)

4008ec: 00 00 00 00

00000000004008d0 :

4008d0: 55 push %rbp

4008d1: 48 89 e5 mov %rsp,%rbp

4008d4: 31 c0 xor %eax,%eax

4008d6: 85 f6 test %esi,%esi

4008d8: 0f 57 c0 xorps %xmm0,%xmm0

4008db: 7e 0f jle 4008ec

4008dd: 0f 1f 00 nopl (%rax)

4008e0: 0f 58 04 87 addps (%rdi,%rax,4),%xmm0

4008e4: 48 83 c0 04 add $0x4,%rax

4008e8: 39 f0 cmp %esi,%eax

4008ea: 7c f4 jl 4008e0

4008ec: 66 0f 70 c8 01 pshufd $0x1,%xmm0,%xmm1

4008f1: f3 0f 58 c8 addss %xmm0,%xmm1

4008f5: 66 0f 70 d0 03 pshufd $0x3,%xmm0,%xmm2

4008fa: 0f 12 c0 movhlps %xmm0,%xmm0

4008fd: f3 0f 58 c1 addss %xmm1,%xmm0

400901: f3 0f 58 c2 addss %xmm2,%xmm0

400905: 0f 57 c9 xorps %xmm1,%xmm1

400908: f3 0f 2a ce cvtsi2ss %esi,%xmm1

40090c: f3 0f 5e c1 divss %xmm1,%xmm0

400910: 5d pop %rbp

400911: c3 retq

400912: 66 66 66 66 66 2e 0f nopw %cs:0x0(%rax,%rax,1)

400919: 1f 84 00 00 00 00 00

'

답변 1

죄송합니다, 이 답변이 조금 길고 어슬렁거립니다. 몇 가지 테스트를 돌려보았지만, 무언가 다른 시도를 생각하고 나서는 과거 내용을 오래 편집하지 않았습니다.

당신의 작업 집합은 15.25MiB (16MB)입니다. 일반적으로 이와 같은 루틴을 벤치마크하려면 작은 버퍼를 여러 번 평균하여 캐시에 맞출 수 있습니다. 느린 버전과 빠른 버전 사이에 큰 차이가 보이지 않는 이유는 메모리 병목 현상으로 인해 차이가 가려지기 때문입니다.

'calc_avg1'는 전혀 자동 벡터화되지 않습니다. ( 'addss'에 유의하세요. 'ss' 는 스칼라, 단일 정밀도를 의미합니다. 'addps' (패킹된 싱글 정밀도)와 달리. 제 생각에는 이것은 메인으로 인라인될 때에도 자동 벡터화 할 수 없는데, 이는 4 번째 벡터 위치에 #@$@^&$$$& 가 있을 수 있기 때문입니다. 이는 스칼라 코드에서는 FP 예외를 발생시킬 것입니다. 저는 gcc 4.9.2 '-O3 -march=native -ffast-math'와 clang-3.5로 Sandybridge에 대하여 이것을 컴파일 해보았지만 둘 다 행운이 없었습니다.

그래도, 'main'에 내장 된 버전은 메모리가 병목현상이기 때문에 조금 더 느리게 실행됩니다. 32비트 로드는 주 메모리에 접근할 때 거의 128비트 로드와 따라잡을 수 있습니다. (내장되지 않은 버전은 좋지 않을 것입니다: 모든 '+=' 결과는 다른 참조가 있는 메모리에 직접 누적하기 때문에 'res' 배열에 저장해야 합니다. 따라서 모든 연산을 저장으로 보이게 해야 합니다. 이것은 당신이 디어섬블리를 게시한 버전입니다, 그런데 main의 어느 부분인지 구분하는 작업은 '-S -fverbose-asm'으로 컴파일하는 데 도움이 되었습니다.)

약간 실망스러운 점은 clang과 gcc가 4-wide에서 8-wide AVX로 자동 벡터화할 수 없다는 점입니다.

제자리 걸음을 하면서도, 'calc_avgX'에 대한 호출들을 감싸는 것을 거친 후, 'N'를 10k로 줄인 뒤, gcc '-O3'는 avg1의 내부 내부 루프를 다음과 같이 변환시킵니다:

' 400690: c5 f8 10 08 vmovups (%rax),%xmm1

400694: 48 83 c0 20 add $0x20,%rax

400698: c4 e3 75 18 48 f0 01 vinsertf128 $0x1,-0x10(%rax),%ymm1,%ymm1

40069f: c5 fc 58 c1 vaddps %ymm1,%ymm0,%ymm0

4006a3: 48 39 d8 cmp %rbx,%rax

4006a6: 75 e8 jne 400690

$ (get CPU to max-turbo frequency) && time ./a.out

0.016515

1071570752.000000 1066917696.000000 1073897344.000000

0.032875

1071570944.000000 1066916416.000000 1073895680.000000

'

이게 이상한 거야; 왜 32B 로드를 사용하지 않는지 전혀 모르겠어. L2 캐시에 들어가는 데이터 세트와 함께 작업할 때 병목 현상이 되는건 32B 이지만, 이거을 사용하네 'vaddps' .

IDK 왜 또 다른 루프 내에 있을 때 내부 루프를 자동으로 벡터화하는 것에 성공했는지 알 수 없다. 이는 'malloc' 에 인라인 된 버전에만 해당한다는 점에 유의하십시오. 호출 가능한 버전은 여전히 스칼라 전용입니다. 또한 이를 성공 시킨 것은 gcc만이었습니다. clang 3.5는 그렇지 않았습니다. 아마도 gcc는 4번째 요소에서의 'NaN' 을 걱정할 필요가 없는 버퍼를 반환하는 방식으로 'malloc' 를 사용할 것을 알고 있었을 수도 있습니다.

저도 놀랍게도 모든 것이 캐시에 맞을 때, clang의 비벡터화된 'avg1' 가 느려지지 않은 것에 놀랍습니다. 'N=10000' , 반복 횟수 = 40k.

'3.3GHz SNB i5 2500k, max turbo = 3.8GHz.

avg1: 0.350422s: clang -O3 -march=native (not vectorized. loop of 6 scalar addss with memory operands)

avg2: 0.320173s: clang -O3 -march=native

avg1: 0.497040s: clang -O3 -march=native -ffast-math (haven't looked at asm to see what happened)

avg1: 0.160374s: gcc -O3 -march=native (256b addps, with 2 128b loads)

avg2: 0.321028s: gcc -O3 -march=native (128b addps with a memory operand)

avg2: ~0.16: clang, unrolled with 2 dependency chains to hide latency (see below).

avg2: ~0.08: unrolled with 4 dep chains

avg2: ~0.04: in theory unrolled-by-4 with 256b AVX. I didn't try unrolling the one gcc auto-vectorized with 256b addps

'

그래서 큰 놀람은 스칼라만 가지고 있는 clang 'avg1' 코드가 'avg2'와 따라잡는다는 것입니다. 아마도 루프에 의해 전달되는 종속성 체인이 더 큰 병목 현상인가요?

'perf'은 clang의 비-벡터화된 'avg1'의 사이클당 1.47개의 인스트럭션을 보여주고 있으며, 아마도 포트 1의 FP 덧셈 유닛에 포화 상태를 만들고 있을 것입니다. (루프의 대부분 인스트럭션은 덧셈입니다).

그러나, 128비트로 'avg2' 메모리 피연산자를 사용할 때, 사이클 당 0.58명령어만 얻을 수 있습니다. 배열 크기를 또 다른 10분의 1로 줄이면, 사이클 당 0.60명령어를 얻습니다. 아마도 전후처리에 더 많은 시간을 소비하기 때문일 것입니다. 그러므로 루프-지연 의존성 체인에 심각한 문제가 있는 것 같습니다. clang은 루프를 4로 언롤링하지만, 하나의 누산기만 사용합니다. 이 루프는 7개의 명령어가 있으며, 10개의 uop으로 디코딩됩니다. (각각의 'vaddps'는 2이며, 2레지스터 주소 모드로 메모리 피연산자와 함께 사용되어 마이크로퓨전을 방지합니다. 'cmp'와 'jne'는 매크로퓨전됩니다). #$@$&는 'perf'에 대한 'UOPS_DISPATCHED.CORE' 이벤트를 말하며, 'r2b1'입니다.

'$ perf stat -d -e cycles,instructions,r2b1 ./a.out

0.031793

1053298112.000000 1052673664.000000 1116960256.000000

Performance counter stats for './a.out':

118,453,541 cycles

71,181,299 instructions # 0.60 insns per cycle

102,025,443 r2b1 # this is uops, but perf doesn't have a nice name for it

40,256,019 L1-dcache-loads

21,254 L1-dcache-load-misses # 0.05% of all L1-dcache hits

9,588 LLC-loads

0 LLC-load-misses:HG # 0.00% of all LL-cache hits

0.032276233 seconds time elapsed

'

이것은 uops 분석에 대한 내 7:10 지시를 확인합니다. 실제로는 여기의 성능 문제와는 관련이 없습니다: 루프는 1사이클당 4개의 uops 제한보다 훨씬 느리게 실행됩니다. 내부 루프를 변경하여 두 개의 별개의 의존 체인을 만들면 처리량이 두 배가 됩니다(117M 대신 60M 사이클,하지만 71M 대신 81M 인스트럭션).

'for (i=0; i

r0 += _mm_load_ps (array[i]);

r1 += _mm_load_ps (array[i+1]);

}

r0 += r1;

'

4로 언롤링 (루프 끝에서 병합되는 4개의 어큐뮬레이터가 있는)은 성능을 두 배로 늘리게 됩니다. (사이클 수는 42M로, 명령어 수는 81M로, uop 수는 112M로 감소됩니다.) 내부 루프는 4배로 'vaddps -0x30(%rcx),%xmm4,%xmm4' (및 유사한)이 포함되어 있으며, 2배로 'add', mp' + , !'jl' 이 포함되어 있습니다. 이 형태의 s' as 2, b는 마이크로 퓨즈될 것으로 예상되지만, 명령어보다 uop이 훨씬 많이 보이므로 # counts un은 퓨즈되지 않은 도메인의 uop으로 계산됩니다. (리눅스 'perf' 에서는 플랫폼별 HW 이벤트에 대한 좋은 문서가 없습니다). ain, to make 를 다시 높인 결과, 모든 카운트에 완전히 우세한 내부 루프임을 확인하기 위해, 명령어당 uop의 비율이 1.39로 나타납니다. 이는 8개의 명령어, 11개의 uop (1.375)와 잘 일치합니다 ( s' as 2, b 를 2로 계산하고, mp' + + !'jl' 을 하나로 계산함). !'http://www.bnikolic.co.uk/blog/hpc-prof-events.html' , 를 찾았는데, 이는 Sandybridge에 대한 지원되는 perf 이벤트의 전체 목록을 포함하고 있습니다. (그리고 다른 CPU의 테이블을 덤프하는 방법에 대한 지침도 있음). (각 블록에서 !'Code:' 라인을 찾으세요. 'perf' 에 대한 인자로 사용할 umask 바이트 및 코드가 필요합니다.)

'# a.out does only avg2, as an unrolled-by-4 version.

$ perf stat -d -e cycles,instructions,r14a1,r2b1,r10e,r2c2,r1c2 ./a.out

0.011331

1053298752.000000 1052674496.000000 1116959488.000000

Performance counter stats for './a.out':

42,250,312 cycles [34.11%]

56,103,429 instructions # 1.33 insns per cycle

20,864,416 r14a1 # UOPS_DISPATCHED_PORT: 0x14=port2&3 loads

111,943,380 r2b1 # UOPS_DISPATCHED: (2->umask 00 -> this core, any thread).

72,208,772 r10e # UOPS_ISSUED: fused-domain

71,422,907 r2c2 # UOPS_RETIRED: retirement slots used (fused-domain)

111,597,049 r1c2 # UOPS_RETIRED: ALL (unfused-domain)

0 L1-dcache-loads

18,470 L1-dcache-load-misses # 0.00% of all L1-dcache hits

5,717 LLC-loads [66.05%]

0 LLC-load-misses:HG # 0.00% of all LL-cache hits

0.011920301 seconds time elapsed

'

그래, 그것은 이러한 분리 도메인 및 비분리 도메인의 uop을 계산할 수 있는 것 같아요!

8로 언롤링하는 것은 전혀 도움이 되지 않습니다: 여전히 42M 사이클이 소요됩니다. (하지만 루프 오버헤드가 줄어들어 61M 인스트럭션과 97M uop으로 감소되었습니다). 흥미로운 것은, clang이 add 대신에 'sub $-128, %rsi'를 사용한다는 것입니다. 왜냐하면 -128은 'imm8'에 적합하지만 +128은 그렇지 않기 때문입니다. 그래서 FP add 포트를 포화시키기에 4로 언롤링하는 것이 충분한 것 같습니다.

1avg 함수들에 대해서 말씀드리면, 벡터 대신에 단일한 부동소수점을 반환하는 함수에 대해서는, clang은 첫 번째 함수를 자동으로 벡터화하지 않지만, gcc는 벡터화합니다. gcc는 정렬된 주소에 도달할 때까지 스칼라 합을 수행하는 엄청난 프롤로그와 에필로그를 생성한 후, 작은 루프 내에서 32B AVX 'vaddps'를 수행합니다. 당신은 그것들로 더 큰 속도 차이를 발견했다고 말씀하셨는데, 아마도 더 작은 버퍼로 테스트하셨을 수도 있습니다. 이는 벡터 코드 대비 비-벡터 코드에서 큰 속도 향상을 보게 해줄 것입니다.

답변 2

4D 벡터의 평균을 SSE로 계산하기

4D 벡터의 평균을 계산하는 방법 중 하나는 SSE(평방합 오차)를 사용하는 것입니다. SSE는 주어진 데이터 포인트와 평균 값 사이의 차이를 계산하여 오차를 측정하는 방법으로, 평균을 계산할 때 자주 사용됩니다. 이 방법은 계산량이 적고 간단하며, 벡터의 차원에 상관없이 적용할 수 있는 장점이 있습니다.

4D 벡터는 4개의 요소로 이루어진 벡터로, 일반적으로 (x, y, z, w)와 같이 표현됩니다. 이러한 벡터들의 평균을 구하기 위해서는 주어진 벡터들을 모두 더한 후 벡터의 개수로 나누면 됩니다.

먼저, SSE를 이용하여 4D 벡터의 평균을 구하기 위해서는 다음과 같은 단계를 따를 수 있습니다.

1. 주어진 4D 벡터들을 모두 더합니다. 이를 위해 각각의 요소를 이용하여 벡터들을 더하는 과정을 거칩니다. 예를 들어, 벡터 A = (a1, a2, a3, a4)와 벡터 B = (b1, b2, b3, b4)가 주어졌다면, A와 B를 더해 새로운 벡터 C = (a1 + b1, a2 + b2, a3 + b3, a4 + b4)를 구합니다.

2. 모든 벡터를 더한 결과를 벡터의 개수로 나눕니다. 주어진 벡터들의 개수가 n이라고 가정한다면, 위에서 구한 총합 벡터 C를 n으로 나누어 새로운 벡터 D = (d1, d2, d3, d4)를 얻게 됩니다. 이 벡터 D는 4D 공간에서 주어진 벡터들의 평균을 나타냅니다.

3. SSE를 계산합니다. 이를 위해서는 주어진 벡터들과 평균 벡터 D 간의 차이를 계산해야 합니다. 각각의 요소를 이용하여 벡터들 간의 차이를 계산하고 해당 계산 값을 제곱하여 구한 후, 모든 요소의 제곱 값을 더합니다.

4. 구한 SSE 값을 사용하여 결과를 해석합니다. SSE는 오차의 측정값이기 때문에, 계산된 값이 작을수록 주어진 벡터들의 평균에 가까운 것을 의미합니다. 따라서 이 값을 분석하여 주어진 데이터가 어느 정도 일치하는지 평가할 수 있습니다.

이렇게 SSE를 이용하여 4D 벡터의 평균을 계산하는 방법을 사용하면, 간단하면서도 정확한 결과를 얻을 수 있습니다. 벡터의 차원에 상관없이 적용할 수 있기 때문에, 다양한 분야에서 유용하게 사용될 수 있습니다. 따라서, 4D 벡터의 평균을 계산할 때 SSE를 활용하는 것은 효율적이고 신뢰할 수 있는 방법 중 하나입니다.

반응형
Comments