whl 2014-07-23T07:45:23-07:00 ccf.developer@gmail.com 编程珠玑读书笔记(2) 2014-07-22T00:00:00-07:00 whl /2014/07/22/ReadingNotstOfProgrammingPerls2 第二部分 性能

当程序实现了用户的需求时,下一步就是关心提高程序的性能了。

第六章 程序性能分析

计算机系统是可以从很多层面上进行设计,从高层的软件结构一直到硬件中的晶体管。 如:

  1. 问题定义,如重复定义,或过度需求。
  2. 系统结构,将大的系统分成模块,也许是决定性能的最重要的单个元素。设计者需要完成简单的“粗略估计”,以确保程序的性能在正确的范围之内。
  3. 算法和数据结构 O(n*log(n))O(n^2)还是有很大的区别的。
  4. 代码调优 这个就是经验了,比如提前申请内存啊,循环优化,顺序访问内存,内存对齐等等。
  5. 系统软件 是否启用了所有的编译器优化?还有线程的优先级是否有保证?等等
  6. 硬件 最近项目上需要做很多的优化工作,在经历了算法优化、代码优化和硬件优化等方案后,发现如果财大气粗的话,换硬件是优化的第一选择。

Note:如果仅需要做少量更改的时候,找到性能瓶颈后再做修改。

第七章 粗略估算

和第四章“编写正确的程序”一样,这一章也留给我很深的印象。

想想坊间流传的微软面试题吧:

密西西比河(长江)一天流出多少水?

第一次看到类似题目的时候,心想这大公司还对脑筋急转弯有爱好,果然是要找聪明的人。直到项目上在对整个框架优化的时候,才发现这类题目需要的“粗略估算”方法,对于分析大型软件框架时非常有作用。

作者给出的答复为, 河的出口为1英里(1.6公里)宽,20英尺(1英尺=0.305米)深,河水的流速是5英里/h,即120英里/天,则得到:

1英里1/250英里120英里/天 = 1/2英里3/天

如果是原来的我,可能看到这里就开始恍然大悟,抛下书本得意洋洋了。

可是这个答案真的对吗?我们有什么方法可以验证吗? 书中给出了验证答案的几个方法:

  1. 两个答案比一个答案好。 通过另一个角度,当然还是粗略估算,来计算结果。如上题可以通过:密西西比河的面积密西西比河每年的降水量/每年的天数,10001000*1/5000英里/年/400天/年 = 1/2英里3/天。
  2. 快速检验。 这里介绍了“如何解题”中的“量纲检验”,即只有相同量纲的各项才能运算,否则不能运算。如之前的两个答案都是:英里*英里*英里/天 = 英里^3/天
  3. 经验法则。又称“72法则”,用于估算指数过程的增长非常方便。如果以年利率r%投资一笔钱y年,如果r*y=72,那么你的投资差不多会翻倍。

这些对于我们的粗略估算很有帮助,不过要记住,即使我们有了上面的方法,也不能保证我们的估算与实际结果一致,所以还要为我们的估算留下余地。

这样即使在我们的估算有误时,也不会过于影响我们的判断。

Little定律,队列中物体的平均数量为进入速率与平均停留时间的乘积。

第八章 算法设计技巧

本章通过解决一个经典问题“求连续子向量的最大和”,总结了一些算法设计的技巧。

  • 保存状态,避免重复计算。

这也就是所谓的“空间换时间”吧。

  • 将信息预处理至数据结构中

将能预先计算的内容提前运算,不要等到收到数据再做计算。

  • 分治算法

  • 扫描算法。与数组相关的问题经常可以通过思考“如何将x[0..i-1]的解扩展为x[0..i]的解”来解决。

  • 累积

  • 下界。只有确定完算法的下界后,才能确定自己的算法是最佳算法。

第九章 代码调优

过早的优化是万恶之源。 -- Donald Knuth

尽管我们的上帝说了这句话,但是boss和客户可不管你的万恶之源,只要产品慢,那就必须优化。当然,如果因为其他需要导致算法更改,那就意味着之前的工作可以重做了。

代码调优首先确定程序中开销最大的部分,然后进行少量的修改,以提高其运行速度。

自己在平时也看过一些优化方面的书,第一条都是确认"性能瓶颈"。毕竟,有了正确的目标,才能到达目的地嘛。

忘记之前在哪里看过一篇优化的文章,大概意思是,一个程序有A、B两部分,其中A耗时80%,B耗时20%。那么对某一部分优化之后的结果应该为:(tA*p + tB)/(tA+tB),其中p为优化后的时间与优化之前的性能之比。 如果对B进行优化,p = 20%,那么整体优化后与优化前的比例为: (0.8+0.2*0.2)/(0.8+0.2) = 0.84。如果对A进行优化,可得(0.8*0.2+0.2)/(0.8+0.2)=0.36。 相比优化B,优化A有了一倍多的提升。

下面给出书中几个代码优化的例子:

问题1. 整数取模。由于取模运算耗时100ns,而其他算术运算只需10ns,所以可将取模运算替换为“减法”。

问题2. 函数、宏和内联代码。 对于有些编译器而言,将函数改为宏可以提升性能,然后对于另一些编译器,这种代码优化可能没有任何作用。不过对于C++程序员可以将某一函数进行编译,从而可以兼得函数的简洁语义和宏的低廉开销。

问题3. 顺序搜索。 可通过展开循环来提高性能。如:

for( i = 0; ; i += 8)
    if(x[i  ] == t ) { break}
    if(x[i+1] == t ) { i += 1; break}
    ...

在作者的机器上,性能提高了56%。对于老式计算机来说,降低开销可以加速10%~20%。对于现代计算机来说,将循环展开有助于避免管道阻塞,减少分值,增加指令级的并行。

问题4. 计算球面距离。修改数据结构,使用开销较低的欧氏距离而不是角度距离。

即,修改原有算法,采用速度更快的算法。

]]>
编程珠玑读书笔记(1) 2014-07-15T00:00:00-07:00 whl /2014/07/15/reading-notes-of-programming-perls1 第一部分 基础

第一章 开篇

正确的问题。

正确的问题是解决问题的第一步也是最重要的一步。

位图。 多趟算法。 简单的设计。

程序设计的阶段:

  1. 定义问题(最好形式化为:输入、输出和约束
  2. 程序设计
  3. 实现概要。

第二章 啊哈!算法

问题A:在一个很大的文件中搜索一个元素

首先介绍了用处非常广的“二分搜索”算法。二分搜索并不只用于查找有序数组中元素的位置,而是一种将处理范围缩小的思想。比如求根程序(对分法),比如程序调试(当有一段很长的代码需要调试的时候,显然从中间找更容易定位,当然,首先还是要确认输入无误)。

问题B:左旋转数组。即对于"abcdefg",左旋转3个位置,可得"defgabc"。

使用“翻手代码”,即通过下面的代码解决问题:

reverse(0, i-1); // "cbadefg"
reverse(i, n);   // "cbagfed"
reverse(0, n);   // "defgabc"

作者Jon Bentley认为这可以作为一种“常识代码”。

问题C: 在一个字典中,找到所有的变位词。像"pots", "stop", "tops"互为变位词。

找到变位词,就需要将找到变位词的共同的东西,我们这里将其称为标记。然后根据标记,将相同标记的单词聚集在一起。

问题解决者的观点。优秀的程序员都有点懒,他们坐下来等待灵机一动的出现而不急于用最开始的想法编程。

最开始的很有可能并没有考虑周全,所以,当很快得到一个解决方案时,再花上15分钟来想想是不是有更好的方法是很值得的。

第三章 数据决定程序结构

恰当的数据视图实际上决定了程序的结构。

关于数据结构还有一种说法(忘记是哪位大神了),给我看你的数据视图,而不是代码,我就知道这份代码做了什么事情。

能用小程序实现的,就不要用大程序

借助合适的数据结构,将大程序缩减为小程序。

程序员在节省空间方面无计可施时,将自己从代码中解脱出来,退回起点并集中心力研究数据,常常有奇效。数据的表现形式是程序设计的根本。

退回起点思考的几条原则:

  1. 使用数组重新编写重复代码

  2. 封装复杂数据结构

  3. 尽可能使用高级工具如超文本、数据库等

  4. 从数据得出程序的结构。 在动手编写代码之前,优秀的程序员会彻底理解输入、输出和中间数据结构,并围绕这些结构创建程序。

第四章 编写正确的程序

编程珠玑共有两章留给我的印象最深,其一便是这章编写正确的程序

由于自己行业比较特殊,所以公司对程序的正确性要求较高。不仅需要开发者编写单元测试验证,还要有专门的测试人员对子系统测试,系统测试。不过尽管存在这么多测试,也无法保证通过测试的代码是正确无误的。所以脑袋里就一直会想,有什么方法可以保证程序一定是正确的?能不能像证明公式一样证明程序是正确的?不幸的是,现在并没有一种方法可以证明程序是无误的。不过这一章介绍了如何“编写正确的程序”,帮助我们更加接近“正确的程序”。

程序验证的一些基本原理:

断言,输入。

验证loop:

  1. 确认由初始化得到的循环不变式
  2. 每次迭代保持不变式为真
  3. 无论循环合适终止, 所得的结果都是正确的。 当然,还要证明loop一定会终止。

验证函数: 首先使用两个断言陈述目的。前置条件是调用函数之前就应该成立的状态,后置条件的正确性由函数在终止执行时保证。 由于这些条件像函数跟调用者之间的契约,因此也被称为“契约编程”。

第五章 编程小事

明智的程序员会使用脚手架(scaffolding)来方便的访问函数。

编程:先用伪代码构建程序框架,再将伪代码转换为从程序。

通过手动测试来运行完成的函数。

通过断言保证函数的输入是正确的。

自动测试

计时脚手架

]]>
Reading Notes of Willpower 2014-02-12T00:00:00-08:00 whl /2014/02/12/reading-notes-of-willpower 第一次看到《意志力》是在同人于野的博文上,这种基于科学研究的心理学看上去要比路边摊的励志成功学要好玩许多,于是便放入了douban的书单中。

这次过年回家的时候没有带电脑,恰好多了许多空闲时间,于是在kindle上看完了这本书。

如果想看具体内容的话,还是看书好了。我在这里只对最后的启示作摘抄,并添加自己的理解。

启示一:了解你的极限

首先要知道,你的意志力是有限的,而你做任何有阻力的事情都会用到你的意志力。

启示二:留意你的症状

当你感觉到很累,没有意志力的时候,需要休息,来呵护自己的意志力。

启示三:挑选你的战斗

由于你的意志力是有限的,那么显然要将有限的意志力用在最需要的事情上。

启示四:列个任务清单--至少列个“不做清单”

当事情未完成,人的大脑便会不停的提示自己,产生焦虑,这样很容易影响到自己正在处理的事情。而当你在计划中记录下来,这种焦虑便会减轻。

启示五:当心计划谬误

回想过去,避免做出夸大的计划。列出优先级,每天完成前三即可。

启示六:不要忘了小事情

从小事情上呵护自己的意志力,如身体的基本需求:饮食和睡眠,保持干净整洁等。

启示七:积极拖延的力量

启示八:别无选择

给自己一个明显规则:一条清晰明显绝不会弄错的边界。

启示九:追踪了解

监控自己的计划和行为,并做出相应调整

启示十:经常奖励

奖励有助于坚持。

我自己的总结:

1.意志力是会消耗的,所以要保护自己的意志力,并挑要做的事情。
2.可以有目的的培养意志力。通过其他事情的训练,可以增强个人的意志力,尤其是学习等方面。
3.长久持续的做一件事情,要比临时突击更有效。书中举的例子:一个每天写一两页论文的学者,其成果要比
deadline之前临时突击的学者要高。
4.通过监控调整自己的目标计划。
5.应根据过去的经验,将自己的目标和计划定的更合理。
]]>
一次修复bug的经历 2014-01-11T00:00:00-08:00 whl /2014/01/11/an-experience-of-fixing-a-bug-with-intel-compiler 为了优化代码性能,工作上除了使用vs编译器,部分项目还使用了Intel编译器。 相比开始编程就使用的vs编译器,我对intel编译器就没那么熟悉了。

最近在使用intel compiler的编译代码的时候出现一个错误: error MSB6006: "icl.exe" exited with code 2。 而换到vs2010编译器的时候,却顺利通过了。由于平时一直借助vs编译器输出的错误信息,所以当遇到这个问题就orz了。

仔细查看项目属性的时候发现C++ -> Diagnosis(intel)的属性Emit Diagnosis To File被设置为Yes,这样intel编译器的提示信息就被输出到一个指定路径下面的后缀为(.diag)文件中。将该属性改为No之后,就可以在output窗口中输出错误信息了。

根据错误信息发现,自己代码中使用了nullptr。而根据Intel官网的说明,intel编译器(12.0)不支持nullptr,直到12.6之后才支持,orz。

随后又稍微了解了intel编译器的设置。intel编译器不仅可以提供一般编译器的错误信息,还可以提供优化代码的信息。用户可以根据这些信息对代码做相应更改,从而优化代码。

总结:不同编译器对C++不同版本的支持不同,尤其是一些比较新的特性如auto,nullptr。如果在代码上找不到解决方法的时候,就可以考虑编译器了。

]]>
summary of 2013 2013-12-31T00:00:00-08:00 whl /2013/12/31/annual-of-2013 匆匆忙忙,又过去了一年的时间。

时间一直都是消费品。每个人只会花费时间,从来没有人能够可以获取时间。即使有人说他赚到了更多的时间,也不过是在相同的时间内做了自己想做的事情,时间从不理人。

2013年,是第一个完整工作的一年。

这一年,刚开始负责了两个比较复杂的图像后处理,不过在与算法组沟通后,还是比较顺利的实现了。但其中有一个教训,就是算法中包含一个步骤,即将生数据与计算得到的阈值比较,根据比较的大小采取不同的运算步骤。由于C++的浮点数有误差,导致相同的输入数据使用了不同的运算步骤,并导致最终结果有误。最后对算法做了少许更改,一定概率上规避了这种错误。

随后负责优化对原有的算法。经验教训便是:计算机会将最近使用的数据放在缓存中,而缓存比内存比硬盘的读写速度要快了多了,所以要尽量对当前内存进行操作,不要有事没事去在多个很大的数据中跳来跳去。当然,还有IPP库啊,并行运算啊等等。不过相比算法级别的优化,软件在代码实现上能做的优化微乎其微,有这么多时间,还不如重构代码方便阅读或增添新功能更值得。

最后负责搞一个通用的算法库,这样同组的小伙伴再不会为拟合等数学问题而苦恼了。然后发现,想写一个通用的东西确实不那么方便,干巴呆。


2013年,也看了不少书。程序员的自我修养,代码大全,clean code,程序员修炼之道,深入探索c++对象模型,还想看Algorithms,具体数学,只是能看书的时间更多久好了。


2013年夏,也开始负责公司足球队的事情。尽管从初三到现在已经有10年的球龄(怎么又是十年,想起了“十年编程”那句话),但一直只享受在球场上奔跑的大汗淋漓,未曾考虑过球队杂务。现在才发现,想要组织一个球队并不是那么容易的事情。联系比赛、组织活动,活动总结和发票报销等等对我来说完全是赶鸭子上架,尽管现在或许已经熟了,:P。


2014年,还需要做很多的事情。技术、英语,一件都不能拉下啦!


Update: 额,2014实在太敷衍了,自己重读一遍都已经看不下去了。

更新: 2014年要做的事情: 1. 读完Algorithms 2. 做完leetcode上的题目(150) 3. 做完USACO Training 4. 考托福or雅思(6.5)

]]>
list technics 2013-12-22T00:00:00-08:00 whl /2013/12/22/list-technics 总结stanford library的list Problem中的一些技巧如下:

Coding Techniques:

1. 创建新的链表

在处理链表的时候,如果需要返回新创建list的head pointer,可以通过Dummy Node来保存首地址。 所有新建的Node可以添加到其他Node的.next,这种方法可以让对第一个节点的处理与对其他节点的处理完全一致,有助于减少代码。 例子:

List* Process()
{
 struct node dummy;
 struct node* head = &dummy;

 /*
      Process with the head pointer.
      Such as:
           head->next = new node;
 */ 

 return dummy.next;
}

当然,也可以通过对返回Pointer的引用,但这时就需要对特殊指针,如头指针或尾指针,特殊处理。

2. 改变一个指针

指针p1赋值给指针p2后,两个指针会指向同一块地址。但如果想修改p1指向的地址,就必须获得p1的地址。这一点在处理pointer作为函数参数时需要特别注意。如果函数需要改变参数,那就需要传入指针的引用;如果函数只需要使用指针指向的空间,那传入指针即可。 如:

void ChangToNull(struct node** headRef)
{
     *headRef = NULL;
}

void ChangToNull(struct node*& headRef)
{
     headRef = NULL;
}
]]>
update the jekyll template 2013-12-11T00:00:00-08:00 whl /2013/12/11/update-the-jekyll-template 原来的模板在本地试运行时,经常会出现一些不兼容中文的warning。而我对ruby又不熟悉,自己试着找了几次,也没有找到合适的解决方案,就也推迟了更新。

今天google到一个看起来比较清爽的bloghttp://webfrogs.me/,而且里面还有一篇怎么clone他人blog的文章:http://webfrogs.me/2012/12/20/use-jekyll/,于是按照做法把webfrogs的blog克隆下来,再把site name等个人信息换掉。本地再运行时果然没有了原来的错误,而且排版什么的看起来都比原来清新许多。Thanks to webfrogs.

USACO一直有在做,不过最近卡在了USACO上最杀脑细胞的一道题Packing Rectangles。题目是IOI 95的一道题,相比前面思路直接,简单模拟即可搞定的题目,这道确实要难了许多。

不过征途不会停止。


12月11日更新:

本来9日就已经提交,但查看github的blog却没有任何更新。去rep上看,也发现内容已经更新了,orz。

后来才发现github会给注册邮箱发邮件通知build失败,并且会给出失败的详细信息。而我犯的错误则是在提交新模板的时候,把CNAME文件也一起提交上去。CNAME 文件的作用是指定url。如果两个人采用相同的CNAME文件,则会出现url冲突的问题。

于是直接在github的网页上,把CNAME文件删除,blog顿时清新了,:)。

]]>
USACO section1.2 2013-11-26T00:00:00-08:00 whl /2013/11/26/usaco-section12 Complete Search(穷竭搜索)

穷竭搜索时将所有的可能性罗列出来,在其中寻找答案的方法。穷竭搜索的思路非常简单直接,也是在编程竞赛中,第一个被想到的方法。而如果没有时间和空间的限制,那就采用这种方法吧,反正竞赛是为了找到一个解决问题的方法,而不是找到一个最快的方法。

题目见:http://www.nocow.cn/index.php/USACO_Training#Section_1.2

  1. Milking Cow

    很简单,按照牛奶价格从低到高选择即可。 需要注意的一点是,厂家在选择一个农民后,不需要购买这个农民所有的牛奶,即买一部分也是可以的。

  2. Transaction

    题意:判断是否可以通过一系列操作(90度旋转,180度旋转和翻转等等)从原始图像变换到目标图像。

    这个也很简单,只要计算出旋转前后的变换公式即可。其中有几个trick:1. 180度旋转=90度旋转+90度旋转;2.由于原始图像比较小,可以直接在栈里面申请空间。相比使用共同的临时空间,这样代码更整洁,逻辑也更清晰。

  3. namenum

    题意:根据一串数字找到对应的字符串,并判断该字符串是否已经存储在一个文件中

    利用深度搜索获得所有的字符串,再判断该字符串是否符合条件。

    Trick:1. 利用stl中的set存储文件中的字符串;

  4. Palindromic Squares

    题目是在规定的进制下,如果1~300的数的平方是回文数,则输出此数和次数的平方(均在该给定进制下)。

    该问题主要想考回文数和不同进制转换,这两点也是编程教学中常用的两个题目。 回文数自然没什么好说的,比较第i个和第len-1-i个字符是否相等就可以了。
    而进制转换却让我遇到了些麻烦。windows下有个api,itoa,可以将数字按照不同的进制转换成字符串,而linux下却没有,这就浪费了两三次提交。后来自己找到了个itoa的程序,又发现字符串数组开的太小,只开了10个,导致在处理二进制的时候,字符串不够长,判断出错。

    总结,windows的itoa不仅可以用于将数字转成字符串,还可以进行进制转换。 在自己实现进制转换时,先给出一个索引表,即char index[] = "0123456789ABCDEFG....",有助于实现。

  5. PROB Dual Palindromes

    题目是给定两个数S, N,分别表示需要查找到数目的个数,和查找的范围。如,在大于N的整数内,查找符合条件的S个数,条件为,以2~10为进制的表示中,至少有两个进制的表示为回文数。

    考察内容跟上题一样,只需要对2~10的进制表示判断一下即可。

]]>
usaco section1.1 2013-11-16T00:00:00-08:00 whl /2013/11/16/usaco-section11 题目可以在这里找到http://www.nocow.cn/index.php/USACO_Training#Section_1.0。ps:发现上面已经有写的很详细的详解,想要更详细的可以直接看那里了,⊙﹏⊙b汗。

  1. Your ride is here.

    分析:根据字符串计算余数,很简单。只是要记得所有字母都是大写,因此计算对应的数字,应与‘A’进行比较。

  2. Greedy gift givers.

    分析:关键在于如何根据字符串查找,在c++中,使用stl string重载的"=="运算符即可;在c中,使用strcmp()进行比较。

  3. Friday the thirteenth.

    分析:该题直接遍历每个月的13号即可,但有几个细节需要注意。1. 如果每个月第一天的编号是b,那么第13天的编号应该是b+12. 2. 下一个月的13号星期几可以根据当前月13号的星期计算出来,即, (x+dayPerMonth)%7. 所以,为方便计算,可以首先查找第一个13号是星期几,然后直接计算下一个月的13号的星期。

  4. Broken Necklace

    给定一个由两三个字符组成的环,确定连在一起最长字符串的长度。

    分析:如果数据量不够大的话,暴力即可。 但还有一个dp的方法,就是记录对于每个字符,记录其左边和右边连在一起最长字符串的长度。对于下一个字符,只需要比较是否与前一个字符相同,即可知道当前字符的情况,相同则加1,不相同则为0。

]]>
USACO training 2013-11-10T00:00:00-08:00 whl /2013/11/10/usaco-training 前段时间开始做codeforces,做了一些题目之后,发现自己对不少知识掌握并不全面。如果题目比较直接或者只使用到基本的数据结构,那还能比较容易做出来。但如果遇到复杂点的数学知识,可能就很难做出来。

后来在大牛cuitianyi的博客看到他在刚开始准备OI的时候,用usaco来练手,而且认为做完usaco进省队是没问题的。 usaco比其他oj网站有几个优点: 1. usaco对题目按照难度和类型进行了分类。 2. 在每类题目前面均有一篇讲解的文章。 3. 在你解决了这道题目之后,还能看到官方给出的解释和示例代码。 4. 在你做完当前章的题目之后,才能做下一章的题目,如果想看下面的题目,就不能偷懒跳过当前的题目。

做了大概一个星期,成果如下:

Section 1.0 DONE 2013.11.04 TEXT Introduction

Section 1.1 DONE 2013.11.04 TEXT Submitting Solutions

DONE 2011.11.04 PROB Your Ride Is Here [ANALYSIS]

DONE 2013.11.04 TEXT Contest Problem Types

DONE 2013.11.04 TEXT Ad Hoc Problems

DONE 2013.11.04 PROB Greedy Gift Givers [ANALYSIS]

DONE 2013.11.05 PROB Friday the Thirteenth [ANALYSIS]

DONE 2013.11.06 PROB Broken Necklace [ANALYSIS]

Section 1.2 DONE 2013.11.07 TEXT Complete Search

DONE 2013.11.08 PROB Milking Cows [ANALYSIS]

DONE 2013.11.09 PROB Transformations [ANALYSIS]

TODO PROB Name That Number

TODO PROB Palindromic Squares

TODO PROB Dual Palindromes

这两天应该再整理一个题目总结出来。恩,待续。

]]>