【汇编】如果你觉得指令优化对你有用的话
所谓指令优化说的是,你觉得编译器把你的代码编译成垃圾了,那么你就干脆自己写汇编指令,自行完成优化。不过在进行自己手头上的指令优化之前,先仔细思考一下你的代码在算法层面上有没有可以优化的地方,不然你付出的劳动并不能给你带来与之对等的收益,事实上你会亏本,因为现在的C编译器对于指令的优化技术已经十分成熟了,很多你能想到的优化操作它都能给你完成,搞不好你写的“优化指令”还不如编译器给你生成的指令呢。
首先我们要弄清楚到底哪些操作比较费时。
1、能够明显感觉到的费时操作:外存读写(从硬盘、U盘等媒介读取文件或数据),网络传输(两个概念,延迟和带宽,延迟是你的包裹到达目标地址的时间,带宽是你一次能寄多大的包裹)。
往一个硬盘里读写不同文件的不同部分是最耗时的,磁头会忙得哒哒响,CPU却只能等着。如果你的程序是因此而变得很慢的话,再怎么优化你的指令都得不到明显的加速效果。
2、做一定次数的处理的时候能体现速度差异的地方:内存管理(malloc、new、realloc等),字符串操作(主要指的VB、Java等,有的人喜欢用String存储数值的字符串,然后用Val来取值并计算。但将字符串存储的数值转换为数值形变量,这个操作本身就是非常缓慢的。另外对于一定长度的数据的比较算法,比如4096个字节的数据判断是否和另外的4096个字节完全相同的,这方面合适的话请使用哈希算法)
对策是减少内存分配的次数,在做数据比较的时候应用哈希算法(虽然不能比较数据的相似度,但至少能够比较出,数据是一样的,还是不一样的)。
3、做大量重复处理的时候能体现速度差异的地方:你用的CPU指令集,指令的长度(字节数),指令的周期数,内存的读写次数,读写的地址是否对齐等。这里是做指令优化的时候你能优化的地方。
假设你的某个函数只是对内存中的数据进行处理或者计算的话,你的这个函数在第一次被CPU执行的时候,它的执行速度取决于它的长度,而当它已经执行过一次后,在第二次被调用的时候,它的速度是取决于它的指令的总周期数的。CPU要先读取内存来将指令读取进CPU的缓存,然后再执行它。执行过一次以后,下次再次执行这个函数的时候,因为它已经被缓存进了CPU,所以它的执行速度是取决于它的周期数的。这样的原因是,我们的CPU的频率和内存的频率不一样,CPU的频率通常是高于内存频率的。假设一个4.0 GHz的CPU,配上一个2.666 GHz的DDR4内存,你的函数第一次被执行的时候,速度其实是2.666 GHz的,第二次被执行的时候才是4.0 GHz。然而单片机不一样,单片机的话,除非你读写DDR内存,否则通常情况下它自身的“内存”(然而是寄存器)的读写速度是和它的频率是一样的。
数据吞吐量的概念,“吞”,就是从内存里读取数据到CPU的寄存器里,“吐”,则是把计算好的数据再写入到内存里。这里能做的指令优化是,对于需要重复运算的函数,尽量一次多读取一些数据到寄存器里。比如,mov rax,[要读取的8个字节]。这样你就一次读取了8个字节(但如果指针不是8字节对齐的话,CPU可能会进行两次读内存操作)。把数据都读进寄存器后,再在寄存器里将其拆开、计算。比如移动某些字节到另一个寄存器,做加减乘除运算。作为对比,如果你对这8个字节中你想要操作的字节进行单独的读取、计算、存储操作的话,处理的速度就会慢很多。
x86的指令集里面有额外照顾ax、eax寄存器,典型的32位的“xchg eax,别的寄存器”的指令只需要8个bit,而“xchg ecx,edx”则需要16个bit来表达。多使用eax比较好。
对于内存中的浮点数的大小判断,如果你没有把它读进FPU寄存器栈的话,其实你可以直接用有符号整数判断大小的方式来对比大小。因为浮点数的存储结构是[符号位|小数点位置|有效数字],首先符号位在前面,然后是“小数点位置”。这个数值越大,小数点的位置就越靠右边。而符号和小数点位置都相同的时候,当然是有效数字越大的浮点数值越大。浮点数的存储结构正好满足这样的规律,所以直接用整数比较的方式就可以了(比如cmp、sub指令)。切记不能用ja jb jae jbe,只能用jg jl jge jle。
这种比较方式只能用在相同精度的条件下的浮点数,对于不同精度的浮点数,不能用这种方式比大小。你需要设置FPU的控制字,来处理不同情况下对循环小数的识别和位数扩充。
页:
[1]