combine3
→ combine4
)Inner loop of combine3. data_t = double, OP = *
dest in %rbx, data+i in %rdx, data+length in %rax
.L17: loop:
vmovsd (%rbx), %xmm0 Read product from dest
vmulsd (%rdx), %xmm0, %xmm0 Multiply product by data[i]
vmovsd %xmm0, (%rbx) Store product at dest
addq $8, %rdx Increment data+i
cmpq %rax, %rdx Compare to data+length
jne .L17 If !=, goto loop
Code Review
.L17: loop:
vmovsd (%rbx), %xmm0 *dest(메모리)의 누적값을 **읽어** %xmm0로 로드
vmulsd (%rdx), %xmm0, %xmm0 %xmm0 *= data[i] (현재 원소와 곱, **메모리 -> 연산유닛**)
vmovsd %xmm0, (%rbx) 갱신된 누적값을 다시 *dest에 저장(**쓰기**)
addq $8, %rdx data+i 포인터를 다음 원소로 이동(double 크기 8바이트)
cmpq %rax, %rdx 종료 조건 검사: 현재 포인터 vs 끝 포인터
jne .L17 같지 않으면 루프 반복
<aside> 💡
combine3
는 누적 결과를 매 반복마다 *dest
(메모리)에서 읽고,
새 값으로 쓰기 때문에, 반복당 메모리 읽기 2회 + 쓰기 1회가 발생
</aside>
product
→ dest
로 읽어오는 것
이전 Loop 실행의 마지막에 쓰인 값 : 낭비
결과값을 임시로 누적하기 (출처 : CSAPP Fig. 5.10)
combine4
는 로컬 변수 acc
를 사용해 루프 동안 레지스터에 결과를 유지하고, 루프 종료 후에만 *dest
에 한 번 저장
이에 따라 루프당 메모리 동작이 단일 읽기로 줄어들고, 누적값은 %xmm0
레지스터에 상주
Inner Loop of combine4. data_t = double, OP = *
acc in %xnm0, data+i in %rdx, data+length in %rax
.L25: loop:
vmulsd (%rdx), %xnm0, %xnm0 Multiply acc by data[i]
addq $8, %rdx Increment data+i
cmpq %rax, %rdx Compare to data+length
jne .L25 If !=, goto loop
Code Review
.L25: loop:
vmulsd (%rdx), %xnm0, %xnm0 acc *= data[i] 를 레지스터만으로 수행(메모리에는 미반영)
addq $8, %rdx 다음 원소로 이동
cmpq %rax, %rdx 종료 조건 검사
jne .L25 반복
성능 증가 : 전 범주에서 2.2× ~ 5.7× 향상
특히 int
덧셈의 경우 1.27 클럭 사이클까지 감소
출처 : CSAPP p.543
<aside> 💡
Memory Aliasing
서로가 다른 동작을 가질 가능성 </aside>
Aliasing이 가능하면 combine3
과 combine4
의 의미가 달라질 수 있음.
e.g., 벡터 v = [2, 3, 5]
의 호출
combine3(v, get_vec_start(v)+2);
combine4(v, get_vec_start(v)+2);
출처 : CSAPP p.543
combine3
은 결과를 그 자리에서 누적하여 최종 36 (2 → 6 → (6 * 6) = 36)combine4
는 벡터를 끝까지 보존했다가 마지막에 저장하여 30 (1 * 2 * 3 * 5 = 30)