读《游戏之旅-我的编程感悟》笔记

时间:2022-12-28 12:11:31

 无意中发现了这本《游戏之旅-我的编程感悟》,可能因为自己对游戏开发感兴趣,刚开始读就无法自拔。读完这本书,首先感叹于云风丰富的编程经历,然后就是云风对自己编程经验的总结让我受益匪浅。云风的成功让我觉得有自己兴趣的重要性,正如他所说“我因为爱游戏,才去制作游戏,因为制作游戏,才去学习和钻研编程”。真心推荐对游戏开发感兴趣的同学读一下。以下是读书过程中,摘录的对我自己有启发的语句。
1. 计算机,并不神秘。程序,也只是表达计算机控制逻辑的符号而已。
2. 模仿永远是最快的学习途径。
3. 算法,先于计算机存在于世,比编程语言更为重要。语言只是工具,算法才是灵魂。
4. 编程同样讲究熟能生巧。
5. 用空间换取时间的做法在游戏中用的非常广泛,也就是预处理方法;而用时间换取空间的做法也不能忽视,它多采用重复计算的方式实现。
6. 算法的研究离不开计算机硬件平台的特性。
7. stl中已经实现了常用的数据结构,为什么我们还要了解他们的实现方法?因为蕴含在数据结构中的思想,是程序员的必修课。启发你更多,让程序员按照更合理的方式进行编程。在特定情况下,我们还需要根据实际情况,对数据结构的实现进行改造,达到更高的效率。
8. 复杂的数据结构往往就是程序复杂的根源。
9. 算法优化:①数学方法的改进②预运算来节省时间或重复运算来节省空间③简化算法求得近似解来代替最优解④改进数据组织方式
10. 碰到棘手的问题不要急着google,自己思考是最好的解决问题的钥匙。每个问题都有无数的解决方案,每次独立的解决一个问题都是一次开拓思维的机会。
11. 建议每个游戏程序员或多或少的了解一下你工作平台下的汇编。
12. CPU:它不过是一个复杂化的图灵机,接受一些消息,按设计好的规则,利用内部保存的一些状态做出某种有意义的输出。
13. 通常一个函数会被编译成一段汇编代码,函数指针就是指向这段代码的地址。
14. Allegro的编译精灵直接用汇编指令保存图像数据,把运算的时间从运行时转换到制作时,这是软件常用的优化策略。
15. MFC不仅仅是封装了windows API而已,而是引导程序员使用一些设计模式来编写windows程序。
16. 如果理论上,能让编译器从代码中了解到足够的信息,使它能从信息中明白编程者想达到的目标,那么就一定能得到最优的编译结果(C++就是从这方面,利用               template等特性实现了比C更高的编译质量)。
17. 虽然现代处理器处理浮点数已经足够快,但是整数运算更快,所以能用整数运算解决的问题,尽量避免使用浮点数。
18. 低效的静态变量。局部变量存储在堆栈中,函数的调用重复使用相同的内存区域,而当一块内存被反复的读写,其数据就会存在CPU内部一级缓存中,访问速度非常快, 绝大多数情况堆栈顶的数据就属于这种情况,而静态变量则没有这个优势。内存同CPU内部缓存的数据交换,往往成为程序速度的瓶颈。同样的道理,构造临时对象,使用系统提供的new操作间接的调用了molloc是一种低效的操作。注重效率的做法是写一个placement new,改用alloca在堆栈中分配对象需要的空间,然后再退出时直接调用析构函数,让函数退出时的堆栈调整操作自动回收内存。前面提到“绝大多数情况”,也就是说存在特殊情况,有人喜欢在局部声明大数组,这既容易造成栈溢出,又使cpu内部缓存的内存地址不停的变动,极大的破坏了CPU高速缓存带来的好处,所以尽量避免这种情况。总之,无论在设计模式,还是效率角度,避免使用局部变量。
19. 应该极力避免或减少使用除法运算(除法运算实现复杂)。对于整数运算,除2的整数幂可以被优化成移位运算,但是不主张将n/8写成n>>3,因为现代编译器会帮你做这件事(如果你肯定n是正整数写成(unsigned)n/8有利于编译器帮你优化,因为有符号除以8并不完全等价于移位运算),即使除数不是2的幂数,编译器优化依然会消除除法,将除法变成一次乘法和一次移位操作(让编译器有优化余地的唯一前提是除数是一个常数,也可以用const int定义)。对于运算a/b+c/d, (a*d+c*b)/(b*d)更快些,这是因为即使3次乘法运算也快过一次除法。
20. 如今并不主张用汇编制作整个软件,通晓工作平台的汇编语言的目的通常有两个:提升调试的技能,使用汇编来改善核心代码的工作效率。
21. 直接写内存地址的操作,必须包含一个32位长的地址数据,使得指令无可避免的占用TC(踪迹缓存trace cache)2个单位,这就是为什么不提倡在C++这样的高级语言中使用全局变量的原因。而如果把一个全局变量放到一个类里边,通过this指针加偏移的形式,由于这个类几乎不可能大于32k,所以避免了出现32位地址数据。
22. 处理细致的调整指令的次序和精心的选择合适的指令外,汇编较之于C语言的有点还在于它在处理位操作时略胜C一筹。所以在做一些加密算法和图像解码操作时, 我优先选择汇编而不是C。
23. SIMD(单指令多数据),其基本原理是用一个超长的寄存器(64或128位)保存成组的多个短小的数据单位,然后用单一的数学运算指令,让这些成组的数据做相同的运算。这样CPU就有了许多数字的并行运算能力。
24. 在游戏引擎中,永远有性能可以去挖掘。优化是在各个层面进行的事情,可以进行大规模性能提升的地方往往在于高层次的设计,而非低层次的热点上。
25. windows的GDI是用脏矩阵的方式工作的。
26. 找到更好的方法解决问题和怎样充分利用已有的硬件环境把好的方法最佳的实现对游戏程序员同等重要。
27. 即使同样采用面向过程的软件设计方法,c编译器产生的代码效率会比C++编译器要低而不是高。
28. 全局变量在C语言中随处可见,但C++中却极力避免这种做法。C++程序员更多的会把一类全局变量写成一个类并做成一个单件,取得一个单件的指针访问这些数             据。(现代CPU对待数据时,把数据集中起来更容易提高数据缓存的使用)
29. C语言是用函数名的字符串本身帮助连接器区分函数的。所以它不支持函数重载,无法区分不同的函数调用形式。为了支持printf这种不定参数的函数调用,标准C语言强迫所有函数选择同一种函数调 用协议,即由调用函数的代码把压入栈的参数出栈,这比让函数自身将参数出栈要稍微低效一点。
30. 为了提高效率,成员函数的this指针,为了提高效率,在准标准中是使用寄存器传递的。
31. C++用模板技术代替了C的宏,不仅增加了编译时的类型检查,减少程序员犯错的可能,而且增加了编译器在编译时的推倒能力,使很多工作在编译期间完成,为运行期 生成最佳代码。
32. C语言用setjmp/longjmp处理异常,这给运行期带来了额外负担。而C++内建了异常,让编译器产生了许多额外的代码,尽力时间代码空间换时间的策略,提高运行时间效率。
33. 即使不使用面向对象的设计方法,有时候多态性也是必须实现的,C使用函数指针数组实现。这使得使用变得麻烦,而且很难达到编译器的效率。
34. C标准库qsort比C++标准库sort低效:qsort需要程序员编写比较函数,这比sort多了很多函数调用的消耗(sort使用模板技术使比较函数内联,消除了函数调用消              耗)。
35. 并不放弃使用C语言的原因:①广泛的可移植性,几乎所有开发平台都支持C语言②良好的面向对象的设计很复杂,需要丰富的经验,而使用C语言会使项目的实现更简单③C语言很简单,所以编译速度很快,相反C++项目的编译会很慢。
36. 宏在C语言编程的主要四大用途:定义常量,代码生成,内联代码和对编译流程做出选择。
37. C++对inline函数优化的很好,最终编译器会把内联函数优化成一个常数,没有函数调用的消耗。
38. 判断一个C++程序是否由一个有经验的C++程序员编写,最简单的方法之一就是看代码中const出现的频率。几乎所有程序员在描述字符串常量时都是用const char* 而不是char*C++程序尽量把函数参数声明成const,同样成员变量也经常被修饰成const,这样就只能在构造时创建他们。声明const对编译期间发现错误很重要。
39. static_const在没有继承关系的类型之间转换时,会发生错误。
40. 好的C++程序员会把所有的数据都声明为private,尽量在一个类中暴露过多的public方法,而protected应该慎用,而且尽量不要用在成员数据上。
41. C++较之于C引入了命名空间。   
42. 在项目中大规模的使用复杂的模板要谨慎:①编译时的模板推倒会使编译速度几何级数下降,直到令人发疯的地步,而且很容易引起编译器的抱怨(超过编译器的某 些限制,甚至引起编译器内部错误)②许多模板使用技巧都是生成更多的代码来切合不同的运行时情况以加快运行速度,这是典型的空间换时间策略,很容易引起代码膨胀。③不是所有编译器都支持模板的所有特性,换编译环境时可能引起代码不能被正确编译。④模板不好调试⑤模板直到客户程序员使用的时候才实例化出来,这可能使模板设计者不那么容易发现自己的错误。
43. 脚本编程的思维方式和C/C++不同,所以C/C++程序员必须转变思维。想让嵌入式脚本发挥最大的效率,每个游戏程序员都有必要熟悉嵌入式脚本本身的实现。