ArchLab是CSAPP的第四个实验,主要考察对于架构的理解,根据虚拟的Y86-64指令架构,从而理解CPU与指令。
ArchLab同时涉及第4章架构和第5章优化,决定分两次完成,本博客也决定分两次完成。
从进度上来说:第4章相关的A/B部分只要一个晚上就可以完成了,没有遇到太多困难。
文章目录
Lab
A Part
A Part感觉上是主要考察对于Y86-64的“汇编”编写能力,同时还回顾了“过程”这一机制,对于递归能有更深的理解。
sum.ys
sum.ys迭代地对链表的元素求和,与下述的examples.c里的sum_list理应一致。
typedef struct ELE {
long val;
struct ELE *next;
} *list_ptr;
long sum_list(list_ptr ls)
{
long val = 0;
while (ls) {
val += ls->val;
ls = ls->next;
}
return val;
}
根据终止条件进行变成,注意CC的设置。同时还回顾了一下while的先jmp的视线,具体结果如下,也有较详细的注释:
# Execution begins at address 0
.pos 0
irmovq stack, %rsp # Set up stack pointer
call main # Execute main program
halt # Terminate program
# Sample linked list
.align 8
ele1:
.quad 0x00a
.quad ele2
ele2:
.quad 0x0b0
.quad ele3
ele3:
.quad 0xc00
.quad 0
main: irmovq ele1,%rdi
call sum_list #sum(ele1)
ret
# long sum_list(list_ptr ls)
# ls in %rdi
sum_list: xorq %rax,%rax # val = 0
andq %rdi,%rdi # Set CC
jmp test # Goto test
loop: mrmovq (%rdi),%r10 # Get val
addq %r10,%rax # Add to sum
mrmovq 0x8(%rdi),%rdi # ptr = ptr->next
andq %rdi,%rdi # Set CC
test: jne loop # Stop when 0
ret
# Stacks
.pos 0x200
stack:
运行:
usr@ub2004:~/csapplab/archlab/archlab-handout/sim/misc$ ./yis sum.yo
结果:
Stopped in 26 steps at PC = 0x13. Status 'HLT', CC Z=1 S=0 O=0
Changes to registers:
%rax: 0x0000000000000000 0x0000000000000cba
%rsp: 0x0000000000000000 0x0000000000000200
%r10: 0x0000000000000000 0x0000000000000c00
Changes to memory:
0x01f0: 0x0000000000000000 0x000000000000005b
0x01f8: 0x0000000000000000 0x0000000000000013
其中%rax结果为cba,满足条件。
rsum.ys
rsum.ys递归地对链表的元素求和,与下述的examples.c里的rsum_list理应一致。
long rsum_list(list_ptr ls)
{
if (!ls)
return 0;
else {
long val = ls->val;
long rest = rsum_list(ls->next);
return val + rest;
}
}
这里考察了“过程”这一机制,利用栈保存原有的结果,并在返回时利用:
# Execution begins at address 0
.pos 0
irmovq stack, %rsp # Set up stack pointer
call main # Execute main program
halt # Terminate program
# Sample linked list
.align 8
ele1:
.quad 0x00a
.quad ele2
ele2:
.quad 0x0b0
.quad ele3
ele3:
.quad 0xc00
.quad 0
main: irmovq ele1,%rdi
call rsum_list #sum(ele1)
ret
# long rsum_list(list_ptr ls)
# ls in %rdi
rsum_list: pushq %rbx # save ls->val
mrmovq (%rdi),%rbx # val = ls->val
irmovq 0,%rax # sum = 0
andq %rdi,%rdi # Set CC
je result
mrmovq 0x8(%rdi),%rdi # ls = ls->next
call rsum_list
addq %rbx,%rax # %rax = val + rest
result:
popq %rbx
ret
# Stacks
.pos 0x200
stack:
运行:
usr@ub2004:~/csapplab/archlab/archlab-handout/sim/misc$ ./yis rsum.yo
结果:
Stopped in 43 steps at PC = 0x13. Status 'HLT', CC Z=0 S=0 O=0
Changes to registers:
%rax: 0x0000000000000000 0x0000000000000cba
%rsp: 0x0000000000000000 0x0000000000000200
Changes to memory:
0x01b8: 0x0000000000000000 0x0000000000000c00
0x01c0: 0x0000000000000000 0x0000000000000090
0x01c8: 0x0000000000000000 0x00000000000000b0
0x01d0: 0x0000000000000000 0x0000000000000090
0x01d8: 0x0000000000000000 0x000000000000000a
0x01e0: 0x0000000000000000 0x0000000000000090
0x01f0: 0x0000000000000000 0x000000000000005b
0x01f8: 0x0000000000000000 0x0000000000000013
其中%rax结果为cba,满足条件。
copy.ys
copy.ys对数组进行复制,与下述的examples.c里的copy_block理应一致。
/* copy_block - 将src拷贝到dest并返回xor校验和 */
long copy_block(long *src, long *dest, long len)
{
long result = 0;
while (len > 0) {
long val = *src++;
*dest++ = val;
result ^= val;
len--;
}
return result;
}
这里发现没有IADDQ,却是用起来有点麻烦,只能用寄存器保存const + OPQ来进行:
# Execution begins at address 0
.pos 0
irmovq stack, %rsp # Set up stack pointer
call main # Execute main program
halt # Terminate program
# Source block
.align 8
src:
.quad 0x00a
.quad 0x0b0
.quad 0xc00
# Destination block
dest:
.quad 0x111
.quad 0x222
.quad 0x333
main: irmovq src,%rdi
irmovq dest,%rsi
irmovq $3,%rdx
call copy_block
ret
# long copy_block(long *src, long *dest, long len)
# src in %rdi, dest in %rsi, len in %rdx
copy_block: xorq %rax,%rax # result=0
irmovq $8,%r8 # const 8
irmovq $1,%r9 # const 1
andq %rdx,%rdx # Set CC
jmp test
loop: mrmovq (%rdi),%r10 # val=*src
addq %r8,%rdi # src++
rmmovq %r10,(%rsi) # *dest=val
addq %r8,%rsi # dest++
xorq %r10,%rax # result^=val
subq %r9,%rdx # len-- & Set CC
test: jne loop # Stop when 0
# Stacks
.pos 0x200
stack:
运行:
usr@ub2004:~/csapplab/archlab/archlab-handout/sim/misc$ ./yis copy.yo
结果:
Stopped in 34 steps at PC = 0xb6. Status 'HLT', CC Z=1 S=0 O=0
Changes to registers:
%rax: 0x0000000000000000 0x0000000000000cba
%rsp: 0x0000000000000000 0x00000000000001f0
%rsi: 0x0000000000000000 0x0000000000000048
%rdi: 0x0000000000000000 0x0000000000000030
%r8: 0x0000000000000000 0x0000000000000008
%r9: 0x0000000000000000 0x0000000000000001
%r10: 0x0000000000000000 0x0000000000000c00
Changes to memory:
0x0030: 0x0000000000000111 0x000000000000000a
0x0038: 0x0000000000000222 0x00000000000000b0
0x0040: 0x0000000000000333 0x0000000000000c00
0x01f0: 0x0000000000000000 0x000000000000006f
0x01f8: 0x0000000000000000 0x0000000000000013
其中%rax结果为cba,且内存0x0030-0x0040都成功复制为对应的元素。
B Part
CSAPP的IADDQ对应格式如下:
0 1 2 10
iaddq V,rB [C 0][F rB][ V ]
阶段 | OPq rA,rB | irmovq V,rB | iaddq V,rB |
---|---|---|---|
取指 | icode:ifun ← M1[PC] rA:rB ← M1[PC+1] valP ← PC+2 | icode:ifun ← M1[PC] rA:rB ← M1[PC+1] valC ← M8[PC+2] valP ← PC+10 | icode:ifun ← M1[PC] rA:rB ← M1[PC+1] valC ← M8[PC+2] valP ← PC+10 |
译码 | valA ← R[rA] valB ← R[rB] | valB ← R[rB] | |
执行 | valE ← valB OP valA Set CC | valE ← 0 + valC | valE ← valB + valC Set CC |
访存 | |||
写回 | R[rB] ← valE | R[rB] ← valE | R[rB] ← valE |
更新PC | PC ← valP | PC ← valP | PC ← valP |
这样我们就很清楚,我们需要在哪些阶段上添加HCL代码。最终所需修改的地方共有取指、译码、执行三个阶段,具体如下:
- 取指阶段
bool instr_valid = icode in
{ INOP, IHALT, IRRMOVQ, IIRMOVQ, IRMMOVQ, IMRMOVQ,
IOPQ, IJXX, ICALL, IRET, IPUSHQ, IPOPQ, IIADDQ }; # note: add IIADDQ
# Does fetched instruction require a regid byte?
bool need_regids =
icode in { IRRMOVQ, IOPQ, IPUSHQ, IPOPQ,
IIRMOVQ, IRMMOVQ, IMRMOVQ, IIADDQ }; # note: add IIADDQ
# Does fetched instruction require a constant word?
bool need_valC =
icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IJXX, ICALL, IIADDQ }; # note: add IIADDQ
- 译码阶段
## What register should be used as the B source?
word srcB = [
icode in { IOPQ, IRMMOVQ, IMRMOVQ, IIADDQ } : rB; # note: add IIADDQ
icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
1 : RNONE; # Don't need register
];
## What register should be used as the E destination?
word dstE = [
icode in { IRRMOVQ } && Cnd : rB;
icode in { IIRMOVQ, IOPQ, IIADDQ} : rB; # note: add IIADDQ
icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
1 : RNONE; # Don't write any register
];
- 执行阶段
## Select input A to ALU
word aluA = [
icode in { IRRMOVQ, IOPQ } : valA;
icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IIADDQ } : valC; # note: add IIADDQ
icode in { ICALL, IPUSHQ } : -8;
icode in { IRET, IPOPQ } : 8;
# Other instructions don't need ALU
];
## Select input B to ALU
word aluB = [
icode in { IRMMOVQ, IMRMOVQ, IOPQ, ICALL,
IPUSHQ, IRET, IPOPQ, IIADDQ } : valB; # note: add IIADDQ
icode in { IRRMOVQ, IIRMOVQ } : 0;
# Other instructions don't need ALU
];
## Should the condition codes be updated?
bool set_cc = icode in { IOPQ, IIADDQ }; # note: add IIADDQ
回归测试运行:
1、测试除了iaddq和leave之外所有内容:
usr@ub2004:~/csapplab/archlab/archlab-handout/sim/seq$ (cd ../ptest; make SIM=../seq/ssim)
./optest.pl -s ../seq/ssim
Simulating with ../seq/ssim
All 49 ISA Checks Succeed
./jtest.pl -s ../seq/ssim
Simulating with ../seq/ssim
All 64 ISA Checks Succeed
./ctest.pl -s ../seq/ssim
Simulating with ../seq/ssim
All 22 ISA Checks Succeed
./htest.pl -s ../seq/ssim
Simulating with ../seq/ssim
All 600 ISA Checks Succeed
2、测试iaddq
usr@ub2004:~/csapplab/archlab/archlab-handout/sim/seq$ (cd ../ptest; make SIM=../seq/ssim)
./optest.pl -s ../seq/ssim -i
Simulating with ../seq/ssim
All 58 ISA Checks Succeed
./jtest.pl -s ../seq/ssim -i
Simulating with ../seq/ssim
All 96 ISA Checks Succeed
./ctest.pl -s ../seq/ssim -i
Simulating with ../seq/ssim
All 22 ISA Checks Succeed
./htest.pl -s ../seq/ssim -i
Simulating with ../seq/ssim
All 756 ISA Checks Succeed
C Part
在这一部分中,将在目录sim/pipe中工作。我们需要修改ncopy函数,使其并行度增加,并且提高运行的效率。ncopy函数将len元素整数数组src复制到一个不重叠的dst,重新计算src中包含的正整数数。
CPE 15.18
下列分别是ncopy的c代码和主要部分原始ys代码(CPE=15.18)。
int ncopy(int *src, int *dst, int len)
{
int count = 0;
int val;
while (len > 0) {
val = *src++;
*dst++ = val;
if (val > 0)
count++;
len--;
}
return count;
}
# % rdi = src, %rsi =dst, %rdx = len
ncopy:
xorq %rax,%rax # count = 0;
andq %rdx,%rdx # len <= 0?
jle Done # if so, goto Done:
Loop: mrmovq (%rdi), %r10 # read val from src...
rmmovq %r10, (%rsi) # ...and store it to dst
andq %r10, %r10 # val <= 0?
jle Npos # if so, goto Npos:
irmovq $1, %r10
addq %r10, %rax # count++
Npos: irmovq $1, %r10
subq %r10, %rdx # len--
irmovq $8, %r10
addq %r10, %rdo # src++
addq %r10, %rsi # dst++
andq %rdx,%rdx # len > 0?
jg Loop # if so, goto Loop:
CPE=11.70 使用iaddq
显然记数+1与迭代地址变化,没有iaddq就必须使用irmovq+opq共2个指令+额外1个寄存器实现,显然实现iaddq有助于我们提高运算效率。
仿照B部分,对pipe_full.hcl进行修改,修改部分如下:
bool instr_valid = f_icode in
{ INOP, IHALT, IRRMOVQ, IIRMOVQ, IRMMOVQ, IMRMOVQ,
IOPQ, IJXX, ICALL, IRET, IPUSHQ, IPOPQ, IIADDQ }; #note
bool need_regids =
f_icode in { IRRMOVQ, IOPQ, IPUSHQ, IPOPQ,
IIRMOVQ, IRMMOVQ, IMRMOVQ, IIADDQ }; #note
bool need_valC =
f_icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IJXX, ICALL, IIADDQ }; #note
word d_srcB = [
D_icode in { IOPQ, IRMMOVQ, IMRMOVQ, IIADDQ } : D_rB; #note
D_icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
1 : RNONE; # Don't need register
];
word d_dstE = [
D_icode in { IRRMOVQ, IIRMOVQ, IOPQ, IIADDQ} : D_rB; #note
D_icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
1 : RNONE; # Don't write any register
];
word aluA = [
E_icode in { IRRMOVQ, IOPQ } : E_valA;
E_icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IIADDQ } : E_valC; #note
E_icode in { ICALL, IPUSHQ } : -8;
E_icode in { IRET, IPOPQ } : 8;
# Other instructions don't need ALU
];
word aluB = [
E_icode in { IRMMOVQ, IMRMOVQ, IOPQ, ICALL,
IPUSHQ, IRET, IPOPQ, IIADDQ } : E_valB; #note
E_icode in { IRRMOVQ, IIRMOVQ } : 0;
# Other instructions don't need ALU
];
bool set_cc = E_icode in {IOPQ, IIADDQ} && #note: E_icode == IOPQ -> E_icode in {IOPQ, IIADDQ}
# State changes only during normal operation
!m_stat in { SADR, SINS, SHLT } && !W_stat in { SADR, SINS, SHLT };
将所有irmovq C,%r10 + opq %r10,%rB全部更替为iaddq C,%rA,修改后得到的CPE=11.70,结果如下:
xorq %rax,%rax # count = 0;
andq %rdx,%rdx # len <= 0?
jle Done # if so, goto Done:
Loop: mrmovq (%rdi), %r10 # read val from src...
rmmovq %r10, (%rsi) # ...and store it to dst
andq %r10, %r10 # val <= 0?
jle Npos # if so, goto Npos:
iaddq $1,%rax # count++
Npos: iaddq $8, %rdi # src++
iaddq $8, %rsi # dst++
iaddq $-1,%rdx # len--
jg Loop # if so, goto Loop:
CPE=12.33 条件传送【放弃】
可以看到修改后的代码,使用jle和jg这种控制转移,一旦预测失败就会面临分支惩罚,所以很自然地一个想法就是用条件传送来优化,代码如下:
xorq %rax,%rax # count = 0;
andq %rdx,%rdx # len <= 0?
irmovq $1,%r11 # %r11 = const 1
jle Done # if so, goto Done:
Loop: mrmovq (%rdi), %r10 # read val from src...
rmmovq %r10, (%rsi) # ...and store it to dst
xorq %r12,%r12 # set take=0
andq %r10, %r10 # val <= 0?
cmovg %r11,%r12 # if val>0,take=1; else take=0
addq %r12,%rax # count++
iaddq $8, %rdi # src++
iaddq $8, %rsi # dst++
iaddq $-1,%rdx # len--
jg Loop # if so, goto Loop:
出乎意料地,最终结果CPE=12.33,效果反而变差了,这说明由于在Y86-64下使用条件传送所需的指令更多,这部分的开销超过了预测惩罚,所以我们确定了一个基调:不使用条件传送。
CPE=9.93 4 x 4循环展开
修改了一些细枝末节后,我们直接进入重头戏:对于循环内指令并行度的提升,直接使用循环展开,这里采用4 x 4的循环展开。
4. 4x4循环展开 CPE=9.93
xorq %rax,%rax # count = 0;
xorq %rcx,%rcx # count2 = 0;
xorq %rbx,%rbx # count3 = 0;
xorq %r8,%r8 # count4 = 0;
rrmovq %rdx,%r9 # limit=len;
iaddq $-3,%r9 # limit=len-3;
jle After # if so, goto After:
Loop: mrmovq (%rdi), %r10
rmmovq %r10, (%rsi)
mrmovq $8(%rdi),%r11
rmmovq %r11,$8(%rsi)
mrmovq $16(%rdi),%r12
rmmovq %r12,$16(%rsi)
mrmovq $24(%rdi),%r13
rmmovq %r13,$24(%rsi) # 4 x 4 loop expand
andq %r10, %r10 # val <= 0?
jle b1 # if so, goto Npos:
iaddq $1,%rax # count++
b1: andq %r11,%r11
jle b2
iaddq $1,%rcx
b2: andq %r12,%r12
jle b3
iaddq $1,%rbx
b3: andq %r13,%r13
jle Npos:
iaddq $1,%r8
Npos: iaddq $32, %rdi
iaddq $32, %rsi # i=i+4
iaddq $-4,%rdx # len-=4
iaddq $-4,%r9 # limit-=4
jg Loop # if so, goto Loop:
andq %rdx,%rdx # len = 0
je a1
After:
mrmovq (%rdi), %r10
rmmovq %r10, (%rsi)
andq %r10, %r10 # val <= 0?
jle Npos2 # if so, goto Npos:
iaddq $1,%rax # count++
Npos2: iaddq $8, %rdi # src++
iaddq $8, %rsi # dst++
iaddq $-1,%rdx # len--
jg After # if so, goto After:
a1:
addq %rcx,%rax
addq %rbx,%r8
addq %r8,%rax
最终测得CPE=9.93,这里我犯了两个错误:
- mrmovq和rmmovq连续使用,这触发了数据读写相关的加载使用冒险,使得两条指令之间插入了气泡bubble,大大拖累了效率。
- 没有意识到这里由于分支语句,%rax的增加并不是关键路径,完全不必使用n x n的循环展开,使用n x 1的循环展开,并且使用了n x n的循环展开后,在最后答案的合并上也耗费了更多的指令。
CPE=7.82 10 x 1循环展开 + 多余指令删除 + 3 x 1剩余处理
iaddq $-10,%rdx # limit=len-K;
jl loop9
loop10:
mrmovq (%rdi),%rcx
mrmovq $8(%rdi),%rbx
mrmovq $16(%rdi),%rbp
mrmovq $24(%rdi),%r8
mrmovq $32(%rdi),%r9
mrmovq $40(%rdi),%r10
mrmovq $48(%rdi),%r11
mrmovq $56(%rdi),%r12
mrmovq $64(%rdi),%r13
mrmovq $72(%rdi),%r14 # 10 x 1 loop expand
iaddq $80,%rdi
rmmovq %rcx,(%rsi)
rmmovq %rbx,$8(%rsi)
rmmovq %rbp,$16(%rsi)
rmmovq %r8,$24(%rsi)
rmmovq %r9,$32(%rsi)
rmmovq %r10,$40(%rsi)
rmmovq %r11,$48(%rsi)
rmmovq %r12,$56(%rsi)
rmmovq %r13,$64(%rsi)
rmmovq %r14,$72(%rsi) # 10 x 1 loop expand
iaddq $80,%rsi
one:
andq %rcx,%rcx
jle two
iaddq $1,%rax
two:
andq %rbx,%rbx
jle three
iaddq $1,%rax
three:
andq %rbp,%rbp
jle four
iaddq $1,%rax
four:
andq %r8,%r8
jle five
iaddq $1,%rax
five:
andq %r9,%r9
jle six
iaddq $1,%rax
six:
andq %r10,%r10
jle seven
iaddq $1,%rax
seven:
andq %r11,%r11
jle eight
iaddq $1,%rax
eight:
andq %r12,%r12
jle nine
iaddq $1,%rax
nine:
andq %r13,%r13
jle ten
iaddq $1,%rax
ten:
andq %r14,%r14
jle iter10
iaddq $1,%rax
iter10:
iaddq $-10,%rdx # len-=10
jg loop10 # if so, goto Loop:
loop9:
iaddq $8,%rdx # len=len+10-2
jle Nloop2 # if len <= 2 ,len = 0 or len = 1 or len = 2 =》 len = -2 or len =-1 or len=0
# remain >=3 means using 3 x 1 loop expand
loop3:
mrmovq (%rdi),%rcx
mrmovq $8(%rdi),%rbx
mrmovq $16(%rdi),%rbp # 3 x 1 loop expand
iaddq $24,%rdi
rmmovq %rcx,(%rsi)
rmmovq %rbx,$8(%rsi)
rmmovq %rbp,$16(%rsi) # 3 x 1 loop expand
iaddq $24,%rsi
loop3one:
andq %rcx,%rcx
jle loop3two
iaddq $1,%rax
loop3two:
andq %rbx,%rbx
jle loop3three
iaddq $1,%rax
loop3three:
andq %rbp,%rbp
jle loop3iter
iaddq $1,%rax
loop3iter:
iaddq $-3,%rdx # len-=10
jg loop3 # if so, goto Loop:
Nloop2:
iaddq $1,%rdx # if len == 2 not jmp
jle Nloop1 # remain = 0 or remain = 1
mrmovq (%rdi),%rcx # remain = 2
mrmovq 8(%rdi),%rbx
rmmovq %rcx,(%rsi)
rmmovq %rbx,(%rsi)
Nloop2one:
andq %rcx,%rcx
jle Nloop2two
iaddq $1,%rax
Nloop2two:
andq %rbx,%rbx
jle Done
iaddq $1,%rax
Nloop1:
iaddq $1,%rdx
jle Done
mrmovq (%rdi),%rcx
iaddq $8,%rdi
rmmovq %rcx,(%rsi)
iaddq $8,%rsi
Nloop1one:
andq %rcx,%rcx
jle Done
iaddq $1,%rax
最终这一版的CPE=7.82,已经很接近满分7.5了。
这里主要的细节如下:
- 相同寄存器的mrmovq和rmmovq进行隔开,避免加载使用冒险,从而流水线上的执行、译码同时进行,从而塞满流水线。
- %rax的默认值为0,可以省略开头xorq指令
- 采用10 x 1循环展开的原因在于,除了%rax、%rdi、%rbx、%rsi以及不宜使用的%rsp(若要使用必须采用push+pop),Y86-64只剩下10个寄存器。虽然可以采用指令隔开的方法,从而进行更多的循环展开,但对于程序的性能已经没有提升了,在benchmark.pl的结果中可以看到主要的CPE集中于剩余部分
- 由于目前程序受限于剩余部分,即在循环之外的部分,我们需要思考对于个数不超过9个剩余元素的处理,这里采用了3x1的循环展开,采用新的预测分支,一支使用3x1循环展开处理大于3个的情况,另一支采用了分类讨论的方法对0、1、2三种情况的处理。由于第一支经过3x1循环展开后,剩余部分必然<3,自然那地向下走入另一支分支,从而处理了所有的剩余元素情况。
CPE=7.49 分10种情况讨论剩余元素
很自然地,延续上一条的处理流程,在benchmark.pl的测试结果,大头CPE集中于<10的情况,这意味着循环已经优化足够了,必须更加有效地处理剩余元素部分。
iaddq $-10,%rdx # limit=len-K;
jl remain
loop10:
mrmovq (%rdi),%rcx
mrmovq $8(%rdi),%rbx
mrmovq $16(%rdi),%rbp
mrmovq $24(%rdi),%r8
mrmovq $32(%rdi),%r9
mrmovq $40(%rdi),%r10
mrmovq $48(%rdi),%r11
mrmovq $56(%rdi),%r12
mrmovq $64(%rdi),%r13
mrmovq $72(%rdi),%r14 # 10 x 1 loop expand
rmmovq %rcx,(%rsi)
rmmovq %rbx,$8(%rsi)
rmmovq %rbp,$16(%rsi)
rmmovq %r8,$24(%rsi)
rmmovq %r9,$32(%rsi)
rmmovq %r10,$40(%rsi)
rmmovq %r11,$48(%rsi)
rmmovq %r12,$56(%rsi)
rmmovq %r13,$64(%rsi)
rmmovq %r14,$72(%rsi) # 10 x 1 loop expand
one:
andq %rcx,%rcx
jle two
iaddq $1,%rax
two:
andq %rbx,%rbx
jle three
iaddq $1,%rax
three:
andq %rbp,%rbp
jle four
iaddq $1,%rax
four:
andq %r8,%r8
jle five
iaddq $1,%rax
five:
andq %r9,%r9
jle six
iaddq $1,%rax
six:
andq %r10,%r10
jle seven
iaddq $1,%rax
seven:
andq %r11,%r11
jle eight
iaddq $1,%rax
eight:
andq %r12,%r12
jle nine
iaddq $1,%rax
nine:
andq %r13,%r13
jle ten
iaddq $1,%rax
ten:
andq %r14,%r14
jle iter10
iaddq $1,%rax
iter10:
iaddq $80,%rdi
iaddq $80,%rsi
iaddq $-10,%rdx # len-=10
jg loop10 # if so, goto Loop:
remain:
iaddq $7,%rdx # recover len <- len+10 and compare len with 3
jl left
jg right
jmp remain3
left:
iaddq $2,%rdx
je remain1
iaddq $-1,%rdx
je remain2
ret
right:
iaddq $-3,%rdx # compare with 6
jg remain789 # remain >=7
je remain6 # remain ==6
iaddq $1,%rdx # remain == 4 / 5
je remain5
jmp remain4
remain789:
iaddq $-2,%rdx
jl remain7
je remain8 # jg remain9 is default
remain9:
mrmovq 64(%rdi),%r8
andq %r8,%r8
rmmovq %r8,64(%rsi)
remain8:
mrmovq 56(%rdi),%r8
jle remain82
iaddq $1,%rax
remain82:
rmmovq %r8,56(%rsi)
andq %r8,%r8
remain7:
mrmovq 48(%rdi),%r8
jle remain72
iaddq $1,%rax
remain72:
rmmovq %r8,48(%rsi)
andq %r8,%r8
remain6:
mrmovq 40(%rdi),%r8
jle remain62
iaddq $1,%rax
remain62:
rmmovq %r8,40(%rsi)
andq %r8,%r8
remain5:
mrmovq 32(%rdi),%r8
jle remain52
iaddq $1,%rax
remain52:
rmmovq %r8,32(%rsi)
andq %r8,%r8
remain4:
mrmovq 24(%rdi),%r8
jle remain42
iaddq $1,%rax
remain42:
rmmovq %r8,24(%rsi)
andq %r8,%r8
remain3:
mrmovq 16(%rdi),%r8
jle remain32
iaddq $1,%rax
remain32:
rmmovq %r8,16(%rsi)
andq %r8,%r8
remain2:
mrmovq 8(%rdi),%r8
jle remain22
iaddq $1,%rax
remain22:
rmmovq %r8,8(%rsi)
andq %r8,%r8
remain1:
mrmovq (%rdi),%r8
jle remain12
iaddq $1,%rax
remain12:
rmmovq %r8,(%rsi)
andq %r8,%r8
jle Done
iaddq $1,%rax
最终测得CPE=7.49,这里有意思的地方如下:
- 首先直接采用树状结构的分支跳转,会增加指令数,应该尽可能地利用顺序执行这一特性,上述代码中就将remain 4和5的情况自然地放在6和7的处理之后,从而省略一个jmp指令。
- 因为示例代码对于1/2/3/4的测试往往较高,最终发现以3为底最终的效果要优于以4/5的传统二分为底。
总结
到Lab4的C Part,终于遇到了无论如何都无法进一步优化的情况,不得不参考了他人的博客。
- 一方面在于,第5章我看的比较快,虽然在终于熬过了痛苦的前4章后,第5章的内容又流畅又愉快,所以不到3天就看完了,但确实基础可能还没那么牢固。
- 另一方面,尽信书则不如无书,CSAPP中n x 1展开不改变关键路径,效率提升不如n x n循环展开让我绕进了死胡同,中途揪着寄存器与内存IO、条件传送、局部变量死凹,无论如何都无法将CPE降到9以下,却完全忽略了nx1循环展开的可能性。
但还是很愉快地看到在痛苦的2/3/4章后,终于有一章让我有如鱼得水的快乐了,接下来的第6章Cache我早有耳闻,准备在考试之后,认认真真地慢慢精读,以结束第一部分!
——2023.4.17