본문 바로가기
컴퓨터시스템구조

[컴퓨터시스템구조] 18. MIPS 부동 소수점 연산자

by 파스텔코랄 2023. 10. 19.

MIPS 부동소수점 연산자

  • 부동소수점 하드웨어는 보조 프로세서(coprocessor) 1번
    • ISA를 확장하는 보조 프로세서
    • 메인 메모리의 프로세스는 인덱스가 32비트*32개가 있었다.
      • coprocessor도 인덱스가 32비트*32개가 있다.
      • f로 시작 : $f0 ~ $f31번 까지
  • 별도의 FP 레지스터
    • 32 single-precision : $f0, $f1, ..., $f31
    • double-precision 쌍 : $f0/$f1, $f2/$f3, ...
      • MIP 릴리스 2 ISA는 32*64비트 부동소수점 레지스터을 지원한다.
      • double의 경우 짝수번만 사용한다.
  • 부동소수점 명령어는 부동소수점 레지스터에서만 작동한다.
    • 프로그램은 일반적으로 부동소수점 데이터에 대해 정수 연산을 수행하지 않으며 그 반대의 경우도 마찬가지이다.
      • add $f0, $f2 등이 사용 불가
    • 코드 크기에 미치는 영향을 최소화하면서 더 많은 레지스터
  • 부동소수점 로드 및 저장 명령어
    • lwc1, ldc1, swc1, sdc1
      • load word coprocess, load double coprocess..
    • 예: ldc1 $f8, 32($sp)

 

  • Single-precision 산술
add.s, sub.s, mul.s, div.s
  • 예제
add.s $f0, $f1, $f6

 

  • Double-precision 산술
add.d, sub.d, mul.d, div.d
  • 예제
mul.d $f4, $f4, $f6

 

  • Single-precision과 Double-precision 비교
c.xx.s, c.xx.d(xx는 eq, lt, le, …)

부동소수점 조건 코드 비트를 설정하거나 지운다.

  • 예제
c.lt.s $f3, $f4

 

  • FP 조건 코드 참 또는 거짓 분기
bc1t, bc1f
  • 예제
bc1t TargetLabel

 


 

부동소수점 예: °F to °C

  • C 코드
float f2c (float fahr) {
    return ((5.0/9.0)*(fahr - 32.0));
}

  • fahr in $f12, result in $f0, literals in global memory space

 

  • MIPS 코드
f2c: lwc1 $f16, const5($gp)
        lwc2 $f18, const9($gp)
        div.s $f16, $f16, $f18
        lwc1 $f18, const32($gp)
        sub.s $f18, $f12, $f18
        mul.s $f0, $f16, $f18
        jr $ra

 


 

부동소수점 예: 배열 곱셈

  • X = X + Y × Z
    • 모든 32 × 32 행렬, 64비트 double-precision 요소

 

  • C 코드
void mm (double x[][], double y[][], double z[][]) {
    int i, j, k;
    for (i = 0; i! = 32; i = i + 1)
        for (j = 0; j! = 32; j = j + 1)
            for (k = 0; k! = 32; k = k + 1)
                x[i][j] = x[i][j] + y[i][k] * z[k][j];
}
  • x 주소 : $a0
  • y 주소 : $a1
  • z 주소 : $a2
  • i : $s0
  • j : $s1
  • k : $s2

 

  • MIPS 코드
li $t1, 32 # $t1 = 32 (row size/loop end)
li $s0, 0 # i = 0; initialize 1st for loop
L1: li $s1, 0 # j = 0; restart 2nd for loop
L2: li $s2, 0 # k = 0; restart 3rd for loop
sll $t2, $s0, 5 # $t2 = i * 32 (size of row of x)
addu $t2, $t2, $s1 # $t2 = i * size(row) + j
sll $t2, $t2, 3 # $t2 = byte offset of [i][j]
addu $t2, $a0, $t2 # $t2 = byte address of x[i][j]
l.d $f4, 0($t2) # $f4 = 8 bytes of x[i][j]
L3: sll $t0, $s2, 5 # $t0 = k * 32 (size of row of z)
addu $t0, $t0, $s1 # $t0 = k * size(row) + j
sll $t0, $t0, 3 # $t0 = byte offset of [k][j]
addu $t0, $a2, $t0 # $t0 = byte address of z[k][j]
l.d $f16, 0($t0) # $f16 = 8 bytes of z[k][j]
sll $t0, $s0, 5 # $t0 = i*32 (size of row of y)
addu $t0, $t0, $s2 # $t0 = i*size(row) + k
sll $t0, $t0, 3 # $t0 = byte offset of [i][k]
addu $t0, $a1, $t0 # $t0 = byte address of y[i][k]
l.d $f18, 0($t0) # $f18 = 8 bytes of y[i][k]
mul.d $f16, $f18, $f16 # $f16 = y[i][k] * z[k][j]
add.d $f4, $f4, $f16 # f4=x[i][j] + y[i][k]*z[k][j]
addiu $s2, $s2, 1 # $k k + 1
bne $s2, $t1, L3 # if (k != 32) go to L3
s.d $f4, 0($t2) # x[i][j] = $f4
addiu $s1, $s1, 1 # $j = j + 1
bne $s1, $t1, L2 # if (j != 32) go to L2
addiu $s0, $s0, 1 # $i = i + 1
bne $s0, $t1, L1 # if (i != 32) go to L1

 


 

정확한 산술

  • IEEE Std 754는 추가 반올림 제어를 지정한다.
    • 추가 정밀도(guard, round, sticky)
    • 반올림 모드 선택
    • 프로그래머가 계산의 수치적 동작을 미세 조정할 수 있다.
  • 모든 부동소수점유닛(FPU)이 모든 옵션을 구현하는 것은 아니다.
    • 대부분의 프로그래밍 언어와 부동소수점 라이브러리는 기본값만 사용합니다.
  • 하드웨어 복잡성, 성능 및 시장 요구 사항 간의 균형

 


 

Guard 숫자 반올림

2.56 * 10^0 + 2.34 * 10^2
  • 유효 소수점 이하 자릿수가 3개 있다 가정하면
    • 지수를 정렬하려면 작은 숫자를 오른쪽으로 이동(2.56 x 10^0 → 0.0256 x 10^2)
    • 0.0256에서 56이 잘리게 된다.
  • Guard 비트와 round 비트를 사용하여 두 개의 LSD를 표현할 수 있다.
    • Guard 비트와 round 비트로 저장한 값을 통해 반올림을 통해 더 가까운 값을 찾을 수 있다.
2.3400 + 0.0256 = 2.3656 ≒ 2.37
vs.
2.34 + 0.02 = 2.36
  • sticky 비트
    • 하위비트가 있는지 없는지 유무를 저장
    • 표준에는 guard와 round 외에 세 번째 비트가 있다.
    • round 비트 오른쪽에 0이 아닌 비트가 있을 때마다 설정된다.
    • 이 sticky 비트를 통해 컴퓨터는 반올림 시 0.50 ~ 00과 0.50 ~ 01 사이의 차이를 확인할 수 있다.

 


 

하위 단어(Subword) 병렬성

  • 그래픽 및 오디오 애플리케이션은 짧은 벡터에 대한 동시 작업 수행을 활용할 수 있다.
    • 하나의 가산기로 여러 개 동시 연산이 가능하다.
    • 예: 128비트 가산기
      • 16개의 8비트 추가
      • 8개의 16비트 추가
      • 4개의 32비트 추가
  • 데이터 수준 병렬성, 벡터 병렬성, SIMD(Single Instruction, Multiple Data)라고도 한다.

 


 

x86 부동소수점 아키텍처

  • 원래 8087 부종소수점 보조 프로세서를 기반으로 한다.
    • 8 × 80비트 확장 정밀도 레지스터
    • 푸시다운 스택으로 사용
    • TOS에서 인덱싱된 레지스터: ST(0), ST(1), ...
  • 부동소수점 값은 메모리에서 32비트 또는 64비트
    • 메모리 피연산자의 로드/저장 시 변환된다.
    • 정수 피연산자도 로드/저장 시 변환될 수도 있다.
  • 코드 생성 및 최적화가 매우 어렵다.
    • 결과: 부동소수점 성능 저하

 

데이터 전송 산수 비교 초월수
FILD mem/ST(i)
FISTP mem/ST(i)
FLDPI
FLD1
FLDZ
FIADDP mem/ST(i)
FISUBRP mem/ST(i)
FIMULP mem/ST(i)
FIDIVRP mem/ST(i)
FSQRT
FABS
FRNDINT
FICOMP
FIUCOMP
FSTSW AX/mem
FPATAN
F2XMI
FCOS
FPTAN
FPREM
FPSIN
FYL2X
  • 선택적 변형
    • I: 정수 피연산자
    • P: 스택에서 피연산자 pop
    • R: 피연산자의 역순
    • 하지만 모든 조합이 허용되는 것은 아니다.

 


 

Streaming SIMD Extension 2 (SSE2)

  • 4 * 128비트 레지스터 추가
    • AMD64/EM64T에서 8개 레지스터로 확장
  • 여러 부동소수점 피연산자에 사용할 수 있다.
    • 2 * 64비트 double precision
    • 4 * 32비트 double precision
    • 명령은 동시에 작동한다.
      • SIMD

 


 

행렬 곱셈

  • 최적화되지 않은 코드
1. void dgemm (int n, double* A, double* B, double* C)
2. {
3.     for (int i = 0; i < n; ++i)
4.         for (int j = 0; j < n; ++j)
5.         {
6.     double cij = C[i+j*n]; /* cij = C[i][j] */
7.     for(int k = 0; k < n; k++ )
8.         cij += A[i+k*n] * B[k+j*n]; /* cij += A[i][k]*B[k][j] */
9.         C[i+j*n] = cij; /* C[i][j] = cij */
10.    }
11. }

 

  • 최적화 C 코드
1. #include <x86intrin.h>
2. void dgemm (int n, double* A, double* B, double* C)
3. {
4.     for (int i = 0; i < n; i+=8)
5.         for (int j = 0; j < n; ++j)
6.         {
7.     __m512d c0 = _mm512_load_pd(C+i+j*n); // c0 = C[i][j]
8.     for( int k = 0; k < n; k++ )
9.         { // c0 += A[i][k]*B[k][j]
10.            __m512d bb = _mm512_broadcastsd_pd(_mm_load_sd(B+j*n+k));
11.            c0 = _mm512_fmadd_pd(_mm512_load_pd(A+n*k+i), bb, c0);
12.        }
13.        _mm512_store_pd(C+i+j*n, c0); // C[i][j] = c0
14.    }
15.}

 

  • 최적화 x86 어셈블리 코드
vmovapd (%r11),%zmm1 # Load 8 elements of C into %zmm1
mov %rbx,%rcx # register %rcx = %rbx
xor %eax,%eax # register %eax = 0
vbroadcastsd (%rax,%r8,8),%zmm0 # Make 8 copies of B element in %zmm0
add $0x8,%rax # register %rax = %rax + 8
vfmadd231pd (%rcx),%zmm0,%zmm1 # Parallel mul & add %zmm0, %zmm1
add %r9,%rcx # register %rcx = %rcx
cmp %r10,%rax # compare %r10 to %rax
jne 50 <dgemm+0x50> # jump if not %r10 != %rax
add $0x1, %esi # register % esi = % esi + 1
vmovapd %zmm1, (%r11) # Store %zmm1 into 8 C elements

 


 


오른쪽 시프트와 나눗셈

  • i 자리만큼 왼쪽으로 시프트하면 정수에 2^i를 곱한다.
  • 오른쪽 쉬프트는 2^i로 나눌까?
    • 부호 없는 정수에만 해당
  • 부호 있는 정수의 경우
    • 산술 오른쪽 시프트 : sign 비트(MSB) 복제
    • 예: -5 / 4
      • 111110112 >> 2 = 111111102 = -2
      • -무한대 방향으로 반올림
    • 참조 11111011_2 >>> 2 = 001111102 = +62

 


 

결합법칙(Associativity)

  • 병렬 프로그램은 예상치 못한 순서로 작업을 인터리브할 수 있다.
    • 결합법칙에 대한 가정이 실패할 수 있다.
    • 정수는 결합법칙이 잘 작동하지만 부동소수점은 결합법칙이 적용되지 않는다.
    (x+y)+z x+(y+z)
x -1.50E+38   -1.50E+38
y 1.50E+38 0.00E+00  
z 1.0 1.0 1.50E+38
    1.00E+00 0.00E+00
  • 다양한 수준의 병렬 처리에서 병렬 프로그램을 검증해야 한다.

 


 

누가 부동소수점 정확도에 관심이 있는가?

  • 과학 코드에 중요
    • 하지만 일상적인 소비자 사용을 위해서는?
      • “내 은행 잔고가 0.0002¢ 부족해요!”
  • 인텔 펜티엄 FDIV 버그
    • 시장은 정확성을 기대한다.
    • Colwell, The Pentium Chronicles 참조

댓글