一、数组与指针
对于C语言的新手来说,理解指针的存在是比较困难的一件事情。那么,我们可以通过对比利用指针与直接用数组的下标码值来看看指针的便利之处在哪儿。
有如下C语言程序:
void clear1(int array[],int size )
{
int i;
for (i=0;i < size;i+=1)
array[i]=0;
}void clear2( int *array,int size )
{
int *p;
for (p=&array[0];p < =&array[size];p=p+1)
*p=0;
}
上述的程序中两者的作用都是一样的,把数组array内的所有数值清零,但是实现过程却是很不相同,一个是直接用的数组下标,另一个是用的数组首地址的指针。那么,我们从MIPS汇编程序上来看这两者之间的差异!
首先,用数组下标的代码如下:
//假设两个参数array 和 size 分别存储在 $a0 和 $a1中,i则保存在临时寄存器$t0中;
move $t0;$zero;
Cle1://此处不算一行
sll $t1,$t0,2;
add $t2,$a0,$t1;
sw $zero,0($t2);
addi $t0,$t0,1;
slt $t3,$t0,$a1;
bne $t3,$zero,Cle1;
下面逐行解释代码的含义:
• 首先对i取0;i是存放在寄存器$t0里面的;
• 开始进入第一次循环(我们假定size肯定大于0,所以并不在此之前进行判断),取第i个元素的四倍(左移两位等同于乘以4)
• 将i的四倍加上数组的首地址,就得到array[i]的地址;
• 对array[i]存入0;
• i=i+1;
• 比较当前的i是否小于size,如果小于,那么$t3=1;
• 如果$t3不等于0,那么就跳转到下一次循环
其实我个人觉得,是不是第六第七句太麻烦,直接用一个bne $t0,$a1,Cle1 就行了吧~?
那么我们的第二个基于数组指针的汇编程序是怎么样的呢?
//假设两个参数array 和 size 分别存储在 $a0 和 $a1中,p则保存在临时寄存器$t0中;
move $t0;$a0;
sll $t1,$a1,4;
add $t2,$a0,$t1;
Cle2://此处不算一行
sw $zero,$t0;
addi $t0,$t0,4;
bne $t0,$t2,Cle2
下面逐行解释代码的含义:
• 首先是要取出数组首地址的指针放到$t0,也就是指针p中;
• 为了避免在每一次循环中都会对size所在的数组下标进行操作,干脆腾出一个寄存器直接记录size 的位置;
• 求出size所对应的数组位置的地址;
• 进入循环首先把数组第一个元素清零;
• i=i+1;
• 比较当前的地址是否小于size,如果小于,那么进入下一次循环,不然就继续执行下面的代码;
如果只循环一次,那么是差不多的性能,但是当这个数组进入一定的规模,那么我们就可以看出两者之间的巨大差距。
1.首先,Clear1的循环代码中,包括了简化乘法(左移),这是一个比较占用性能的操作
2.其次,Clear2的循环体经过优化之后只包含了三条语句,而Clear1则包涵六条语句,两倍的性能差距还是保守的说。
二、 ARMv8(x86)指令集中存在的一些谬误与陷阱
• 谬误:更强大的指令意味着更强大的性能
然而并不是,很多强大的指令可以完成复杂的行为但是很显然的牺牲了一定的性能
• 谬误:使用汇编语言来获得更高的性能
如今随着各种编译器的出现,编程语言的编译器产生大量低级指令序列的时代已经过去了。编译器与汇编语言程序员的斗争正在逐渐减少,软件工程中的几个真理之一:代码越多,需要的时间越多。那么毫无疑问的,汇编代码比之C语言等要多了很多,自然投入的时间也就多了。高级语言不仅仅可以现在使用,未来编译器升级之后还可以继续在不同性能的机器上使用,而很显然的,手工编写的汇编语言可移植性是很差的。
• 陷阱: 忘记字节寻址的时候连续的“字”地址相差不是1,是4或者8