Cameron Laird 提供了一些有用的示例这些示例对于很有可能在您自己的应用程序开发中发生的各种性能问题而言是很合适的模型
性能突破似乎有两种不同的实现方式简单的和困难的这并非陈词滥调这两者之间的界限极其清晰
当您听说了某些简单的方式时您拍手歎道噢对极了或太棒了虽然在许多情况下实现这些方式的第一个应用需要相当聪明的头脑但是它们易于理解
另一种方式涉及了仔细的测量专门的知识和大量的调优工作这些过程通常是令人痛苦的也是值得的要视本地条件(比如硬件的具体情况)而定
优秀的程序员可以用困难或简单方式进行工作由于性能对 Linux 程序员而言是非常重要的主题因此我提供了四个来源于实际(编程)生活中的故事它们按困难和简单方式配成两对
始终从需求入手
死亡和交税都是不可避免的对于开发人员而言与此差不多同等重要的是仔细考虑需求这是每种编程都会提到的一个主题
尽管性能的确很重要但是处理这种需求的最佳方法往往并不是显而易见的我经常会碰到一个软件难题它大致符合这样的模式程序正处于使用之中它的功能是正确的但是某个用户使用了一下然后报告说它太慢了需要加速某团队成员很快添加了监视器这降低一点性能但是使用户能一直知晓长期运行的计算还需多少时间于是满意度就增加了
我在 世纪 年代末首次注意到这种现象此后每十年就可以看到一回我觉得从某种意义上讲自从计算开始它就一直在发生关键并不在于最终用户容易受骗或者可以被光鲜的装饰转移注意力相对我们狭隘专业的性能概念而言他们只是有更广泛的目的在许多情况下当他们指出计算需要更快一点时他们真正的意思是他们需要更快更可靠地知道该计算将要进行多久得知精确的时间信息后最终用户可以在计算机延迟期间快乐地安排其它任务
建议参与处理性能的每个人都进行热身练习首先阅读或重读您最喜欢的有关需求管理方面的参考资料下面的参考资料一节提供了几个有用的起点
其次在您的工具箱中放置一些进度监视器进度栏或秒表当您发现让用户等得太久时可以迅速地将其插入应用程序中这些根本不用付出太多如下面这两个示例所示
两个倒计时示例
执行一个长时间的计算同时又要让用户知晓其进度这构成了应用程序设计中一个非常有趣的问题它是个并发性或多任务的实例许多开发人员都相信作为正确的解决方案并发性需要有精巧的机制特别需要有复杂的线程代码
其实不然下面的参考资料一节列出了一篇较早的 developerWorks 专栏文章我在其中概述了并发性涉及的许多技术此外至少自 年以来基本上已经在所有 UNIX 主机上实现了简单的并发编程在 年这一年 ksh 对其协同进程(coprocess)进行了标准化如果您将下面清单 中的 ksh 源代码保存为 exksh然后运行它您会看到倒计时显示十九八等等然后看到 ksh 子进程的结果All done实际运用中您可能会对长期运行的化学计算或数据库检索使用类似这样的操作启动操作作为子进程但是让用户始终知道操作的进展或完成之前还剩多少时间更新显示只用了几行源代码
清单 示例倒计时显示的 ksh 源代码
(echo This models a longlasting process; sleep ;
echo All done) |&
for ((i = ; i > ; i))
do
printf %d seconds before completion \r $i
sleep
done
read p line;# Discard the title line
read p line
echo
echo Output from the subprocess is $line
为用户提供实时信息只需要进行适量的编码即使基于字符的应用程序也可以做到这一点如果图形用户界面(GUI)更适合您的情况那也很简单许多工具箱都内置了忙碌等待或进度显示如果您还没有这样一个工具箱那么几行 Tk 或另一个新式的 GUI 工具箱就足以制作有点类似的倒计时钟面或进度栏下面是一个在几个页面试用过的从头开始做的示例
清单 示例倒计时显示的 Tk 源代码
proc countdown {seconds} {
init
countdown_kernel $seconds
}
proc countdown_kernel seconds {
hands $seconds
if !$seconds return
after [list countdown_kernel [incr seconds ]]
}
proc draw_hand {angle decorations} {
eval c create line $::size $::size [get_xy $angle] $decorations
}
proc end_coordinate difference {
set hand_length [expr $::size * ]
return [expr $::size + $hand_length * $difference]
}
proc get_xy angle {
return [list [end_coordinate [expr sin($angle)]][end_coordinate [expr cos($angle)]]]
}
proc hands seconds {
catch {c delete withtag hands}
set twopi
set seconds_angle [expr $seconds * $twopi / ]
draw_hand $seconds_angle width tags hands
set minutes_angle [expr $seconds_angle / ]
draw_hand $minutes_anglewidth capstyle projecting tags hands
}
proc init {} {
catch {destroy c}
set ::size
set full_diameter [expr * $::size]
pack [canvas c width $full_diameter height $full_diameter]
set border
set diameter [expr * $::size $border]
c create oval $border $border$diameter $diameterfill white outline black
}
countdown
这个倒计时显示了分针和秒针如图 所示其信息内容与前面的程序相同
图 用 Tk 编码的模拟倒计时时钟的外观
//gif >
请记住信息可以代替功能您的最终用户对应用程序的内部状态越了解他们对您的要求就越少只要让您的程序显示它们是如何工作的就可以解决许多明显的性能问题
排序很难
前面介绍的是简单的课程没有专门的编程背景知识的平民百姓也可以理解前面的段落但是排序却属于困难这一类而且这肯定是困难的Donald Knuth 的一整卷有关计算的经典系列都致力于研究排序和搜索(请参阅参考资料以获取对 Knuth 撰写的 The Art of Computer Programming 的评论的链接)
结果证明由于深层次的原因排序是许多性能难题的核心所在性能只是大规模计算的问题人类一次只能理解几个方面因此对于那些大得要花掉很长时间才能掌握的问题我们采用的方式是用某种方式把它们组织起来或使其结构化排序是这些方式中最常见的一种可以对已排序列表进行二元搜索而不进行线性搜索例如这使得在纽约市电话号簿中进行的手工查找得到了简化将一个百万级大小的问题简化成一个一百万的对数(也就是大约为 )级的问题
那是典型的排序工作高级算法通常比低级算法快一千倍或更多值得进行巧妙的排序
但是最聪明的做法就是避免进行整体排序或者至少将排序仅限于在不受注意的场合进行这在数据库管理系统中很常见其它好处之一就是它们允许构造插入时索引需要排序结果时可以直接从索引中读取这一操作的代价就是创建或插入元素的时间稍微有点长但是如果应用程序具有典型的工作流的话用户觉察不到这一点
Knuth 的参考资料描述了其它许多策略比如用来在特定的条件下加快排序操作的BoyerMoore或RabinKarp统一这些策略的一个公共原则是解决通用问题比解决比较特定的问题更容易数学家对此很熟悉如果将代数中的常见问题考虑成复数而不是实数时那么这些问题就更容易处理尽管复数算法较难
这就是我对装饰排序去除装饰(decoratesortundecorateDSU)的看法假定您拥有这样的数据集
Jane Jones >
Dan Smith>
Kim Black>