【系统】深刻理解计算机系统·持续更新
- 对于一个无符号数字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等函数时线程不安全的,慎用啊慎用!可用其可重入版本。
- 互斥锁记得相同顺序加锁解锁。
- 包装错误处理函数,是一个很是不错的作法。
欢迎关注本站公众号,获取更多信息