组装中要学习的关键概念
我坚信,从学习一种新语言中学到的最重要的事情之一不是如何使用一种新语言,而是从中学到的概念知识。我不是在问我们认为Assembly有多重要或者有用,也不在乎我是否从未在任何实际项目中使用过Assembly。
我想知道的是,对于任何普通程序员而言,我们认为最重要的汇编概念是什么?它不必与Assembly直接相关,也可以让我们感到典型的程序员,将所有的时间都花在高级语言上,就不会理解或者认为是理所当然的事情,例如CPU缓存。
解决方案
内存,寄存器,跳转,循环,移位以及人们可以在汇编器中执行的各种操作。我不会错过调试我的汇编语言类程序的日子,他们感到很痛苦!但这无疑为我奠定了良好的基础。
我们忘记了(或者也许永远都不知道),我们今天使用的所有这些花哨的裤子(以及我喜欢的东西)最终归结为所有这些东西。
现在,我们当然可以在不了解汇编程序的情况下拥有富有成效和丰厚的职业,但是我认为这些概念很不错。
如今,x86 asm并非直接通往CPU的胆量,而是更多的API。我们编写的汇编程序操作码本身被编译为完全不同的指令集,重新排列,重写,固定,并且通常无法识别。
因此,这并不是像学习汇编程序可以使我们对CPU内部发生的情况有基本的了解。恕我直言,比学习汇编器更重要的是要更好地了解目标CPU和内存层次结构的工作方式。
本系列文章非常全面地涵盖了后一个主题。
最好了解汇编语言,以便对计算机"幕后"的工作方式有更好的了解,并且在调试某些东西时它会有所帮助,并且调试器可以给是汇编代码列表,至少可以为我们提供弄清楚问题可能出在哪儿。但是,尝试将低级知识应用于高级编程语言,例如尝试利用CPU缓存指令的方式,然后编写古怪的高级代码以强制编译器生成超高效的机器代码。我们正在尝试进行微优化的迹象。在大多数情况下,除非要提高性能,否则通常最好不要尝试使编译器胜于智能,在这种情况下,我们最好还是将这些位写入汇编中。
因此,为了更好地了解事物的工作原理而了解汇编是一件好事,但是所获得的知识并不一定直接适用于我们用高级语言编写代码的方式。但是,在该注释上,我发现学习函数调用在汇编代码级别上的工作方式(了解堆栈和相关寄存器,了解如何在堆栈上传递参数,了解自动存储的工作方式等)使它得以实现。我在高级代码中遇到的问题要容易得多,例如"堆栈空间不足"错误和"无效的调用约定"错误。
我要说的是寻址方式非常重要。
我的母校将其推到了极致,并且由于x86不够用,我们在PDP11的模拟器上研究了所有东西,而我至少记得其中7个。回想起来,这是一个不错的选择。
最重要的概念是SIMD,并对其进行创造性的使用。正确使用SIMD可以在从字符串处理到视频处理再到矩阵数学等各种应用中,为应用带来巨大的性能优势。在这里,我们可以得到比纯C代码高出10倍的性能提升-这就是为什么汇编仍然有用,而不仅仅是调试。
我正在研究的项目中的一些示例(所有数字都是Core 2上的时钟周期计数):
逆8x8 H.264 DCT(频率变换):
c: 1332 mmx: 187 sse2: 127
8x8色度运动补偿(双线性插值滤波器):
c: 639 mmx: 144 sse2: 110 ssse3: 79
4 16x16绝对差和的总和(运动搜索):
c: 3948 mmx: 278 sse2: 231 ssse3: 215
(是的,没错-比C快18倍!)
16x16块的均方误差:
c: 1013 mmx: 193 sse2: 131
16x16块的方差:
c: 783 mmx: 171 sse2: 106
我会说学习递归和汇编中的循环已经教会了我很多东西。它使我理解了我正在使用的语言的编译器/解释器如何将事物压入堆栈,并在需要时将其弹出的基本概念。我还学习了如何利用臭名昭著的堆栈溢出。 (在C中,使用一些getand put命令仍然非常容易)。
除了每天使用asm之外,我认为我不会使用Assembly教给我的任何概念。
定时
快速执行:
- 并行处理
- 简单说明
- 查找表
- 分支预测,流水线
快速到慢速访问存储:
- 寄存器
- 缓存以及各种级别的缓存
- 内存堆和堆栈
- 虚拟内存
- 外部I / O
寄存器分配和管理
汇编使我们对CPU可以同时处理多少个变量(机器字大小的整数)有一个很好的了解。如果我们可以分解循环以使它们仅涉及几个临时变量,则它们都将适合寄存器。如果不是这样,当事物交换到内存中时,循环将运行缓慢。
这确实对我的C编码有帮助。我尝试使所有循环更紧凑,更简单,并尽可能减少意大利面条。
x86傻了
学习几种汇编语言使我意识到x86指令集多么la脚。变长指令?难以预测的时机?非正交寻址模式?啊。
我认为,如果我们所有人都运行MIPS,甚至是ARM或者PowerPC,世界将会变得更好:-)或者,如果Intel / AMD拥有他们的半导体专业知识,并将其用于制造多核,超快,超低价的MIPS,具有所有这些可赎回质量的x86处理器,而不是x86处理器。
我认为汇编语言可以教给我们很多小知识以及一些大概念。
我将在此处列出一些我能想到的事情,但是没有替代品可以去学习和使用x86和RISC指令集。
我们可能认为整数运算是最快的。如果要查找整数的整数平方根(即floor(sqrt(i))),最好使用仅整数的近似例程,对吗?
没事数学协处理器(在x86上)具有fsqrt指令。转换为浮点数,取平方根,然后再次转换为int比全整数算法快。
然后,我们将可以进行诸如访问内存之类的事情,但是在我们深入研究汇编之前,并不能正确地理解它们。假设我们有一个链接列表,并且列表中的第一个元素包含一个我们需要经常访问的变量。该列表很少重新排序。好吧,每次需要访问该变量时,都需要将指针加载到列表中的第一个元素,然后使用该指针加载该变量(假设我们不能在两次使用之间将变量的地址保留在寄存器中) 。如果我们将变量存储在列表之外,则只需执行一次加载操作。
当然,这里节省几个周期,这些天通常并不重要。但是,如果我们打算编写需要快速的代码,则可以将这些知识应用于内联汇编,也可以通常以其他语言来应用。
调用约定如何? (某些汇编程序会为我们解决这一问题,真正的程序员不要使用这些程序。)调用方或者被调用方是否清理堆栈?我们甚至使用堆栈吗?我们可以在寄存器中传递值,但是由于有趣的x86指令集,最好在某些寄存器中传递某些内容。哪些寄存器将被保留? C编译器本身无法真正优化的一件事就是调用。
有一些小技巧,例如按一个返回地址,然后将其JMP转换为过程。该过程返回时,它将转到PUSHed地址。与通常的函数调用思考方式不同的是这些"启蒙状态"中的另一种。如果我们曾经设计过具有创新功能的编程语言,则应该了解硬件能够提供的有趣功能。
汇编语言知识可以教我们有关计算机安全的体系结构特定的知识。如何利用缓冲区溢出或者进入内核模式,以及如何防止此类攻击。
然后就是自我修改代码的超酷性,以及相关的问题,诸如重定位和将补丁应用于代码的机制(这也需要对机器代码进行研究)。
但是所有这些事情都需要正确的头脑。如果你是那种可以放的人
while(x--) { ... }
一旦了解了它的功能后就无法很好地利用它,但是会发现很难自己完成它,那么汇编语言可能会浪费时间。