- UID
- 1
- 精华
- 积分
- 76361
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
有个朋友曾问过我说他有个程序要连续运行20个小时,才能处理完将近上百万的数据。
这个“上百万的数据”到底是什么呢?其实就是一堆记录了数字和逗号的文本文件,嗯,大小差不多有1 MB.
这个文本文件实质上是记录了用户ID与他的好友数,然后他写的程序起到的作用就是检测这样的关系:“我的朋友的朋友认识我”。
听起来好像很复杂,但是处理一个1 MB的这种文件,却要连续运行20个小时,是夸张了点。事实上是他的程序不好,导致这么慢的。
那么是慢在了哪里呢?慢在了文件的读写上。
VS编译的程序(从VC6开始),在调用fopen、fread、fscanf等一系列f开头的C语言函数的时候,会使用文件缓存,这样当你重复读写一个文件的时候,它减少了你对硬盘的访问次数(也就是一次性给你读好,然后你要什么给你什么,而不是每次你要什么的时候,它都去找硬盘要。)而MinGW(2003版)编译出来的程序则不带这个功能,这意味着每次你进行一次文件的读写操作,它都要访问一次硬盘。因此文件缓存的功能就需要自己去实现了(当然这样也好,代码更透明了)。
文件缓存的存在,就是为了解决硬盘速度慢的问题。硬盘有多慢呢?先不说SSD,首先最常见的硬盘是HDD,机械硬盘,它有转动的盘片,上面镀了一层铁,然后靠的是那层铁的磁性来存储数据。硬盘一个磁道有多少扇区这个已经不重要了(以前是63扇区,现在是不同磁道有不同的扇区数,取决于硬盘厂商的考虑)。硬盘转一圈的速度,笔记本硬盘是5000分之1秒,而台式机则有7000分之1秒的速度,有些服务器硬盘能达到10000分之1秒的速度。但是决定硬盘速度的不仅仅是盘片的转速、磁道的扇区数等因素,还有一个因素是“寻道速度”,也就是磁头移动到正确的磁道的速度。读取一个连续的内容的时候,第一次读取就需要寻道,后面的数据就可以连续读取。而如果你每次读取的数据都在不同磁道,那么这个磁头就得蛋疼地往返于各个磁道,像个刷子一样刷过来刷过去,然后每次都只是为了读取某一个磁道的某一块区域,导致整个读取过程变得特别慢。
至于SSD,它虽然寻道快(因为它根本不需要寻道),但是因为受制于接口(有SATA、mSATA、NGFF、PCI等各种接口),速度也不会很快。而且它是使用Flash颗粒的永久擦写存储器,算是外存,速度再快也比不上内存的速度。比起机械硬盘,它们的优势虽然很明显,但是文件操作总是非常慢的,即使SSD也不得不考虑文件操作对程序速度的影响。
我的这个朋友,他的程序里就出现了大量读写文件的代码,而且都是对两个不同的文件进行操作。每次都只读取不到10个字节,然后又要写到别的文件。最后导致的结果,就是因为硬盘速度太慢,让整个程序速度都被拖慢了。
此外,另一个影响程序速度的因素是打印相关信息到屏幕上的操作。典型的例子有C语言的printf。类似的比如VB的Print,或者你直接创建个控件往里面写入字符串来输出信息给用户等。这个过程涉及到字符串的处理与绘制。而绘制一个字符串,系统需要先将一个一个的字符制作出来(比如Linux通过 FreeType读取TTF文件,并转换为位图),然后将制作好的位图显示到屏幕上。这个过程又涉及到显卡加速、显存的读写等。比如printf,当你的控制台程序调用了printf后,它会将字符串先进行一些处理,然后调用WriteConsole等API将字符串写入到控制台,而这个API则要将这个操作发送到CMD的窗口,而CMD是另一个进程(CMD.EXE),然后跨进程传递了数据以后CMD则要将你给它的字符串进行渲染、绘制,最后输出到控制台窗口。完事儿了以后,API返回到printf,然后printf返回到你调用它的代码那里。
如果能免去绘制字体的过程,程序将加快很多。我有一次需要用到VB的字符串处理,并将字符串输出到一个文本框,我发现当我把程序最小化后,整个处理过程只需要两秒。而如果我不把程序最小化,那么这个处理过程将耗费将近10分钟。由此可见,字符串的处理和绘制也是浪费程序速度的一个重要的原因。 |
|