+<div>■ 13. 代码之美:快排的简洁写法和比较次数<br>■ 13. 代码之美:图像过滤器的JIT<br>■ 13. 代码之美:python的字典优化<br>■ 14. 代码之美:运算符优先级的表达式分析<br>■ 14. 代码之美:递归的正则匹配<br>■ 15. 程序员修炼之道:可撤销性<br>■ 15. 程序设计实践:9-当编写了大量重复代码,或者解决问题的方法特别复杂,考虑换语言;如果没有合适的语言,自己创造一种记号(notation),不必像java那样强大,比如printf的格式化字符串<br>■ 15.程序设计实践:9.1-数据格式:强类型语言里,类型本身已经提供的格式的依据,但如果再要提供更多的格式化信息如对齐,就没有优势了;这时可以借鉴弱类型如c语言中的printf,用一种简单的记号来提供类型及其他信息<br>■ 15. 程序设计实践:9.2-正则表达式:简单优雅的正则实现;以及当多个*/*■同时出现时,预期的捕获情况?(最左优先)<br>■ 15. 程序设计实践:9.4-解释器、编译器和虚拟机:ast->tree-walking->tree op->linear op(suffix tree traverse, not recursive now, byte code)->jit<br>■ 15. 程序设计实践:9.5-写程序的程序:html是由程序生成的;带语义的注释,如python、java、doxygen,另外,也可以由枚举+注释生成枚举对应的字符串表示,结合makefile,每次枚举列表一变动,生成新的映射代码<br>■ 15. 程序设计实践:9.6-用宏生成代码:我经常写的"start=clock();...;clock()-start"可以用单参数的宏接受语句序列在c中实现;我的软件光栅器的模板光栅化那段代码由宏来写<br>■ 15. 程序设计实践: 9.7-JIT:算法和数据、模式一起工作,其中数据和模式是运行时的,如果算法是编译期的,则算法中嵌入的模式只能是变量,但如果算法是运行时JIT生成的,则模式被实例化为常量,于是算法可以被进一步进行常量折叠和去死代码,最终算法能得到优化。模式实例化的好例子:图像过滤器中卷积核,正则表达式中的模式串(甚至正则会退化成strstr),yacc的生成代码。<br>■ 16. valgrind: linux下的内存泄露检测工具<br>■ 16. 350行代码实现多种eval: tree-walking, stack-based vm, jit<br>■ 18. 使用AsmJit实现jit eval<br>■ 19. image filtering的gcc版jit<br>■ 21. 800行代码的tiny c<br>■ 22. 程序员修炼之道:曳光弹<br>■ 23. 英语:[AOT-ahead of time:预编译][out-of-order:乱序,如乱序处理器]<br>■ 23. CSAPP:序言,每一章的教学目的,以及一些有名的课后实验<br>■ 24. 在lua中只用lambda实现stack/list/bst三种ADT<br>■ 25. CSAPP: 第1章,计算机系统漫游<br> ■ 1. 信息是位+解释它的上下文<br> ■ 2. 程序的翻译:源码-(预处理)->修改后的源码-(编译)->汇编语言-(汇编)->可重定位的目标文件-(链接)->可执行文件<br> ■ 3. 了解编译系统的意义:有能力进行性能优化;处理链接错误;应对缓冲区攻击等<br> ■ 4. 处理器的工作方式:开机过后,反复读取下一条指令,解释并执行<br> ■ 5. 关于高速缓存:L1几乎和寄存器堆(register file)一样快!L2不一定挡在L1和主存之间?<br> ■ 6. 硬件的三大部分:CPU、主存、IO设备。网络->磁盘->主存->L2->L1->CPU,更快、更小、更贵。<br> ■ 6. CPU一般提供以下几种指令:(1)内存和寄存器间的读写,如mov/load/store(2)寄存器之间的修改,如add,mul(3)寄存器和IO端口直接的读写(4)PC的修改,如jmp<br> ■ 7. 操作系统将硬件的三大部分抽象成如下部分:(1)IO设备=文件(2)磁盘(IO设备)+主存=虚拟存储器(3)IO设备+主存+CPU=进程<br> ■ 8. 在非GUI系统中,可以将文件stdin看做IO设备键盘,将stdout看做显示器,及将socket看做网络适配器。<br> ■ 8. IO和主存之间的数据传输,以CPU作为中转站;DMA是个例外,可以直接在主存和磁盘之间传输<br> ■ EXT-1. 考虑在shell中输入echo hello这个命令:IO设备之键盘->CPU->shell进程的内存->内存和CPU之间交互进行命令解析->创建新进程,从磁盘中DMA加载echo的执行文件到内存->echo内存中的hello字符串(数据段)->CPU->IO设备之显示器<br> ■ EXT-2. 在ssh中输入echo hello:本机键盘(stdin)->CPU->内存->本机网卡(socket)->目标机网卡(socket,重定向的stdin■)->内存->解析并启动echo进程->echo进程内存中的hello字符串->目标机网卡(socket,重定向的stdout■)->本机网卡(socket)->CPU->本机显示器<br>■ 25. CSAPP: 第2章第0节<br> ■ 2进制优于10进制的理由:负载信息的最小单元,显然没有1进制这种东西;实现简单,成本低;容易纠错,因此可以可靠的传输和存储。<br> ■ 对世界进行二进制编码的可能性:任何整数可以用一个二进制向量表示;任何符号可以查表映射到整数再到二进制,例如位图的像素可以查表将亮度映射到整数再到二进制,字符/音符也可用同样的映射...<br> ■ 整数加、乘法有交换律结合律;因为精度原因,浮点的加乘没有交换律和结合律,比如大数加小数时。例:3.14+1e20-1e20!==0, 3.14+(1e20-1e20)==3.14<br>■ 26. CSAPP: 第2章第1节:信息的表示<br> ■ 0. 操作系统将主存、磁盘抽象成一个字节数组(虚拟存储器);编译器提供类型系统,为虚拟存储器中的字节、半字、字、双字提供不同的解释(解释为整形、浮点、指针)<br> ■ 1. 2进制、8进制、16进制、10进制,相互的转换<br> ■ 2. 字长决定了整形、指针的大小。由于指针大小为字长,因此字长决定了寻址范围,即虚拟存储器的最大容量<br> ■ 3. c标准只规定了每种整形的最小尺寸。实践上,有以下经验(32/64位):char->1,short->2,int->4,long long->8,float->4,double->8;long->字长,void*->字长。故long和指针在32位和64位程序中,分别为4字节和8字节<br> ■ 4. little endian和big endian出自格列佛游记,而后者是在讽刺英法的时代冲突。以intel为主的pc的cpu一般是little endian,而ibm、sun的小型机、大型机一般是big endian。字节顺序主要影响序列化和反序列化,目标可以是文件、网络流,甚至机器码中的immdiate/displacement也受影响。<br> ■ 5. 字符串的内存表示。其中ASCII是编码无关的<br> ■ 6. 机器码的表示。主要由cpu架构相关的机器指令,以及操作系统相关的pe格式和链接地址<br> ■ 7. 布尔代数和环:布尔在19世纪50年代为了研究命题逻辑而提出布尔代数;20世纪30年代,信息论的奠基人,香农,将布尔代数用于设计和分析继电器网络。环是特殊的阿贝尔群。布尔代数和环有相似性,常见的环包括整数环、实数环、模数环等,也可以构建布尔环。对于布尔环的研究可以用于纠错(如播放脏的cd)。将布尔环扩展成向量,也能构成环。<br> ■ 8. c中的位运算:&,|,~,^<br> ■ 9. c中的逻辑运算:&&,||<br> ■ 10. c中的移位运算:<<,>>。左移是右边补0;右移,无符号数应该补0,而有符号数,c标准没有规定应该逻辑/算数移位,但一般编译器都实现为算数右移(补符号位)<br>■ 27. 代码大全:第23章-调试<br> ■ 类c语言中if (a = 3)这种,不必将文字常量写在左边,而是开启最高级别的警告...<br> ■ 等到出现bug需要调试的时候已经晚了,更好的办法是通过设计尽量在更早避免问题。但投入精力提高调试技巧也是有必要的,因为优秀的调试技术和拙劣的技术之间效率差距是10:1<br>■ 31. CSAPP: 第2章第2节:整数的表示<br> ■ 1. java只有有符号数<br> ■ 1. c语言标准规定了各种整形的最小尺寸,它保证:char至少1字节,short至少2字节,int至少2字节,long至少4字节。典型的,32位机上,char是1字节,short是2字节,int是4字节,long是4字节;64位机上,char是1字节,short是2字节,int是4字节,long是8字节,指针是字长<br> ■ 2. 无符号数的位表示。B2U:各位乘以对应的2的幂再相加<br> ■ 2. 有符号数的位表示:(1)补码,符号位以外的位乘以对应的2的幂相加,再加上符号位乘以-2^(w-1)。补码是一般的实现(2)反码(3)符号数值表示法,符号位乘以其他位的2的幂的和,TMIN和TMAX绝对值相等,但存在+0和-0<br> ■ 3. 无符号和有符号的转换:位表示不变。结果是,无符号2^(w-1)~2^(w)-1映射到-2^(w-1)~-1<br> ■ 4. c语言的有符号会自动向无符号转换,且位构成不变<br> ■ 5. 扩展整数的位:无符号左边加0;有符号左边加符号位<br> ■ 6. 截断整数的位:扔掉左边<br> ■ 7. 不要使用无符号数。除非:(1)用作位域。如果用int声明位域的话,可能会浪费一位作为符号(2)用于实现大数等(3)想对一个int执行逻辑右移,(int)((unsigned)x>>k)<br> ■ 7. 有符号数的~x + 1和-x是等价的<br>■ 31. CSAPP:第2章第3节:整数的运算<br> ■ 1. 无符号数加法的上溢<br> ■ 2. 有符号数的加法:负+负可能下溢,正+正可能上溢。无符号加法和有符号加法的计算规则完全相同,cpu可以用相同的机器码,除了可以影响不同的标记位<br> ■ 3. 有符号数的加法逆元:各位取反再+1<br> ■ 4. 无符号数乘法:取结果的低w位<br> ■ 5. 有符号数乘法:取结果的低w位。机器级计算方式同无符号一样,除了影响不同的标记位<br> ■ 6. 乘以2的幂。无符号和有符号,都左移log2(n)位<br> ■ 7. 除以2的幂。无符号和正的有符号,都右移log2(n)位;负的有符号数,应该(x+n-1)>>(log2(n)),因为-5/2需要得到-2而不是-3<br>■ 31: CSAPP:第2章第4节:浮点的表示<br> ■ 0. 早期各家厂商各自为战,使用不同的浮点实现,直到IEEE的754标准的推出<br> ■ 7. 快速的将无限重复二进制小数转化为十进制:比如0.010101==1/4+1/4*(1/4)+1/4*(1/16)=(1/4)/(1-1/4)=1/3;又比如,0.100100100=4/8+4/8*(1/8)+4/8*(1/64)=(4/8)/(1-1/8)=4/7。可以看出,对0.yyyyy,其中y为k位,其十进制为y/(2^(k)-1)。换句话说,x/3,x/7,x/15,是可以很容易的写成重复二进制小数的<br> ■ 1. 10进制可以表示为1234.5678*10^(+-12345),类似的,二进制也可以表示为1001.1101*2^(+-110110)。一个有限精度的10进制小数对应的2进制小数可能是无限长的,所以在精度有限的情况下,必然有误差<br> ■ 2. IEEE的表示法为:(-1)^(sign)*M*2^(E)。其中sign被直接编码;M是1.fac,其中fac被编码为小数域;E被编码为指数域,一般是exp-bias的模式。单精度的符号、指数、小数域分别是1,8,23位,而双精度分别是1,11,52位。根据指数域的不同,浮点有三种解释,规格化、非规格化、特殊值。<br> ■ 2. 规格化:exp不是全0也不是全1的时候。E是exp-bias,其中bias是2^(k-1)-1(单精度是127,双精度是1023)。M是1.fac,其中1是隐含的。单精度下的范围是1.2*10^(-38)~3.4*10^(38)<br> ■ 2. 非规格化:exp是全0的时候。E是1-bias。M是0.fac。因此非规格化值提供了0 的唯一表示。单精度下,范围是1.4*10^(-45)~1.2*10^(-38)。其中+0的位表示也为0<br> ■ 2. 特殊值:当expr全1的时候。如果fac为0,则sign为负表示负无穷,sign为正表示正无穷。如果fac非0,表示NAN(not a number)<br> ■ 4. 舍入(rounding)。常见的有4种roudning方式:向偶数舍入(默认)、向0舍入、向下舍入、向上舍入。其中偶数舍入,是指首先向最近的一边舍入,当位于精度位之间的时候,向偶数舍入。在二进制中,把0看做偶数。比如精确到两位,1.11011向下舍入,1.11111向上舍入,而1.11100则摄入为1.000,即中值是指1.11100000...。<br> ■ 5. 浮点运算。由于精度原因,浮点加、乘法都不满足结合律、交换律,所以编译器不敢擅自优化(或者说,一旦利用交换律等来提取公共子表达式,这样的优化会带来误差)。<br> ■ 5. 正无穷+负无穷=NAN<br> ■ 6. 由于c标准不要求编译器用IEEE的浮点实现float和double,所以,c中的浮点范围是用<math.h>来声明的。<br> ■ 6. int->foat,舍入。int->double,没问题。float->int,溢出(TMIN和TMAX),向0截断。double->int,同上。foat->double,没问题。double->float,溢出(+-无穷),舍入。<br> ■ 6. IA32上,浮点寄存器为80位IEEE格式的浮点,而float和double分别为32位和64位,因此,浮点数在寄存器和内存中迁移的时候,会被来回转换,在部分编译器上可能会有误差。<br> ■ 6. 一般的编译器将long double看做double,但gcc认为long double有12~16字节。<br> ■ 7. 快速的将无限重复二进制小数转化为十进制:比如0.010101==1/4+1/4*(1/4)+1/4*(1/16)=(1/4)/(1-1/4)=1/3;又比如,0.100100100=4/8+4/8*(1/8)+4/8*(1/64)=(4/8)/(1-1/8)=4/7。可以看出,对0.yyyyy,其中y为k位,其十进制为y/(2^(k)-1)。换句话说,x/3,x/7,x/15,是可以很容易的写成重复二进制小数的<br><br></div>
0 commit comments