【系统】深入理解计算机系统·持续更新
- 对于一个无符号数字x,截断它到k位的结果就相当于计算x mod 2^k.
- 在大多数的机器上,整数乘法指令相当地慢,需要12或者更多的始终周期,然而其他整数运算-例如加法、减法、位移运算和移位-只需要1个时钟周期.因此,编译器使用的一项重要的优化就是试着使用移位和加法运算的组合来代替乘以常数因子的乘法.
- 在大多数的机器上,整数除法要比整数乘法更慢-需要30或者更多的始终周期.
- 除以2的幂也可以用移位运算来实现,只不过我们用的是右移,而不是左移.对于无符号和二进制补码数,分别使用逻辑移位和算术移位来达到目的.
- 注意系统的分类:主流的IA32(也就是x86),以及x86-64(也就是x64),还有种Intel的与原32位系统不兼容的IA64。
- 编译系统由预处理器,编译器,汇编器和链接器组成。
- 单指令多数据并行称为SIMD并行,其扩展为SSE指令集。
- x64上long为8字节,指针也为8字节。
- 无符号数右移必须采用逻辑右移,而有符号数一般采用算术右移。
- 有符号数遇见无符号数会默认强转为无符号数。
- short转为unsigned时,是先扩展大小再符号转换。
- 补码非的计算:从左到右将第一个为1的位前的所有位取反
- 负数的补码移位向下舍入。
- 正浮点数能使用整数排序函数来进行排序。
- 浮点加法和乘法不具备结合性,浮点乘法在加法上不具备分配性。
- 预处理器扩展源代码,然后编译器生成源代码的文本汇编代码,汇编器转成二进制汇编码,链接器生成exe或dll或lib。
- 寄存器可以保存地址也可以保存值。注意汇编中的加括号表示为取该地址指向的值,如(%eax)指%eax中保存的地址指向的值。
- 传送指令的两个操作符不能都指向存储器。
- 栈指针%esp保存着栈顶元素的值,%eax保存函数返回值。
- 栈从高地址往低地址分配,堆从低地址往高地址分配。
- 注意:lea,假设为leal 7(%edx, %eax, 4),则当%edx中保存的是地址时,lea为取有效地址,而当%edx中保存的是值时,lea为算术运算,即7 + %edx + %eax * 4。这儿的%eax总保存值。说白了,其实lea一直是在做计算,只是%edx影响了直观表达而已。
- 注意:处理有无符号值的操作是通过不同的汇编指令来区分的。
- 大多数汇编器根据一个循环的do-while形式来产生循环代码,逆向工程会用到。
- 指令无视操作数的长度。
- 因为有个条件传送的优化策略,所以(xp ? *xp : 0)这条语句其实两个选择分支都会执行。
- 32位系统中,大多数栈中信息的访问其位置都是基于帧指针的。而64位系统中,栈的存储信息数已被弱化,所以无帧指针了。
- 访问某个局部变量的前提是该局部变量至少有个可引用的地址,所以局部变量被保存在了栈中。
- 为了防止从效率低的存储器读写值时,可能因数据未对齐而造成多次读写从而导致低性能,IA32要求数据一定要对齐。编译器在编译时会强制对齐。
- 汇编指令leave等于俩pop,效果一样,选择随意。pop和push在栈上分配空间的方式是直接栈指针减去或加上偏移量。
- 指针之差等于 相差字节数 / 所指类型大小字节数。
- 寄存器不够用时会出现寄存器溢出,这时就必须有值被保存在栈上了,一般是将只读变量放入栈。
- GCC会对局部char类型的缓冲区插入金丝雀保护代码。
- 在C中内联汇编代码只能针对某一类机器。
- SSE2引入浮点数面向寄存器的指令集,而不用基于栈的方法。
- x64能让汇编代码比x32少很多,但是实际性能提升不会很大。
- 注意rep有时当空操作使。
- x64中,栈空间向下128字节以内的区域仍可以被函数访问,该区域被ABI称为红色地带。
- 浮点相关:把存储模型,指令和传递规则组合称为浮点体系结构。
- 逻辑门只是简单的响应输入的变化而已。
- HCL中,'='只是表示用一个名字来称谓一个表达式。其类switch的表达中的”1:“等同于switch中的Default Case。
- 寄存器文件上的读或写端口都分别成对,一个传ID,一个传内容。
- 访存阶段读写存储器,写回阶段将结果写到寄存器文件。
- 寄存器文件和数据存储器等都是当前时钟随意读,下一时钟统一写入更新。即时钟控制状态元素的更新。
- 流水线即保持各单元在时钟周期内忙碌不已,一套流水线硬件供多个流水线使用。
- 加载互锁和数据转发技术结合起来足以处理可能类型的数据冒险。
- 当流水线化的系统中出现多条指令引起的异常时,最深的指令被处理的优先级最高。
- 每个时钟周期执行多个操作称为超标量,而超线程是指一个核同时运行俩线程。
- 处理器功能单元的性能表示中,延迟指按照严格顺序执行完成合并运算所需要的最小周期数,而吞吐量指理论上最快完成一个操作所需周期数。
- 使用SSE可以降低吞吐量界限。
- 书写适合条件传送实现的”功能式“代码。
- 每个加载/存储单元每个时钟周期只能启动一条加载/存储操作。
- 性能提高技术:①.采用合适的算法和数据结构。②.消除连续的函数循环调用,在可能时尽量将计算移到循环外;消除不必要的存储器引用,引入临时变量来保存中间结果;保持内层循环在存储器层面的局部性。③展开循环;通过多个累计变量和重新结合等技术,提高指令级并行;用功能的风格重写条件操作,使得编译采用条件数据传送。
- 内存是硬盘的缓存。
- core i7上所有的SRAM高速缓存存储器都在CPU芯片上。
- 对于性能来说,存储器访问总数和不命中率相比,不命中率影响要更大。估计是因为存储器的写回缓存机制。
- 存储器性能注意:将注意力集中在内循环上,大部分计算和存储器访问都发生在这里;通过按照数据对象存储在存储器中的顺序,以步长为1来读数据,从而使得空间局部性最大;一旦从存储器中读入了一个数据对象,就尽可能多的使用它,从而使得时间局部性最大。
- 对于静态库的链接,只会链接程序中用到的该库(.lib)中的模块(.obj)。这其实也解释为什么分别链接静态库版本和动态库版本的两程序大小相差不是那么悬殊。
- 当前指令处理完后,处理器才能去发现中断是否发生。
- Linux系统调用的参数都是通过通用寄存器而不是栈传递的。
- C++的try-catch是C种setjmp和longjmp的更加结构化的版本。
- DRAM作为磁盘的缓存,不命中开销巨大。
- 磁盘上的交换文件同时又作为DRAM保存数据的缓存。
- 延迟私有对象中的拷贝最充分的利用了稀有的物理存储器,这是通过写时拷贝实现的。
- 一个系统中被所有进程分配的虚拟存储器的全部数量是受磁盘上交换空间的数量限制的。
- 造成堆利用率很低的主要原因是内存碎片的存在。
- 有时为了极个别的几个特殊情况而要去每次调用时都检查,还不如通过某些方式直接把特殊情况一般化,能用通用的方式去处理。
- 内存引用导致的崩溃要注意:引用坏指针/野指针,以及读未初始化的存储器。
- UNIX信号是不排队的,若为考虑处理,则会直接丢弃。
- 函数内部的static变量也是线程间共享的。
- 注意并行和并发的区别,并行程序是一个运行在多个处理器上的并发程序。
- 线程数多过核数对效率反而会有影响,但影响不大。
- 注意:rand和ctime,localtime等函数时线程不安全的,慎用啊慎用!可用其可重入版本。
- 互斥锁记得相同顺序加锁解锁。
- 包装错误处理函数,是一个非常不错的做法。