于是,最后我使用了一个有点儿低劣的技巧:cthreadlibtestview::oncreate 中的代码执行 100 次循环从 1 到 100,000 计数,并且取样通过该循环所需要的平均时间。结果被保存在成员变量 m_ibiasfactor 中,该成员变量是一个浮点数,它在处理器函数中被使用来决定毫秒如何被“翻译”成迭代次数。不幸的是,因为操作系统的高度戏剧性的天性,要决定实际上运行一个指定长度的计算要迭代多少次给定的循环是困难的。但是,我发现该策略在决定基于cpu的操作的计算时间方面,完成了非常可信的工作。
注意 如果您重新编译生成该测试应用程序,请小心使用最优化选项。如果您指定了 "minimize execution time" 优化,则编译程序将检测具有空的主体的 for 循环,并且删除这些循环。
终结器非常简单:当前时间被取样并保存在计算的 threadblockstruct 中。在测试结束之后,该代码计算执行 executetest 的时间和终结器为每一个计算所调用的时间之间的差异。然后,所有计算所消耗的时间由所有已完成的计算中最后一个计算完成时所消耗的时间所决定,而响应时间则是每一个计算的响应时间的平均值,这里,每一个响应时间,同样,定义为从测试开始该线程消耗的时间除以该线程的延迟因子。请注意,终结器在主线程上下文中串行化的运行,所以在共享的 iendindex 变量上的递增指令是安全的。
这些实际就是本测试的全部;其余的部分则主要是为测试的运行设置一些参数,以及对结果执行一些数学计算。填充结果到 microsoft excel 工作单中的相关逻辑将在"interacting with microsoft excel: a case study in ole automation."一文中讨论。
结果
如果您想要在您的计算机上重新创建该测试结果,您需要做以下的事情:
如果您需要改变测试参数,例如最大计算数或协议文件的位置,请编辑 thrdperf 示例工程中的 threadlibtestview.cpp,然后重新编译生成该应用程序。(请注意,要编译生成该应用程序,您的计算机需要对长文件名的支持。)
请确保文件 thrdlib.dll 在一个 threadlibtest.exe 能够链接到它的位置。
如果您想要使用 microsoft excel 来查看测试的结果,请确定 microsoft excel 已经正确地被安装在运行该测试的计算机上。
从 windows 95 或 windows nt 执行 threadlibtest.exe,并且从“run performance tests”菜单选择"run entire test set"。正常情况下,测试运行一次要花费几个小时才能完成。
在测试结束之后,检查结果时,既可以使用普通文本协议文件 c:\temp\results.fil ,也可以使用工作单文件 c:\temp\values.xls。请注意,microsoft excel 的自动化(automation)逻辑并不自动地为您从原始数据生成图表,我使用了几个宏来重新排列该结果,并且为您生成了图表。我憎恨数字(number crunching),但是我必需称赞 microsoft excel,因为即使象我一样的工作表妄想狂(spreadsheet-paranoid),也可以提供这样漂亮的用户界面,在几分钟之内把几列数据装入有用的图表。
我所展现的测试结果是在一个 486/33 mhz 的带有 16 mb ram 的系统收集而来的。该计算机同时安装了 windows nt (3.51 版) 和 windows 95;这样,在两个操作系统上的不同测试结果就是可比的了,因为它们基于同样的硬件系统。
那么,现在让我们来解释这些值。这里是总结计算结果的图表;后面有其解释。该图表应该这样来看:每一个图表的 x 轴有 6 个值(除了有关长计算的消耗时间表,该表仅含有 5 个值,这是因为在我的测试运行时,对于非常长的计算计时器溢出了)。一个值代表多个计算;我以 2、5、8、11、14 和 17 个计算来运行每一个测试。在 microsoft excel 结果工作表中,您将会找到对于基于cpu的计算和基于i/o的计算的线程的每一种计算数目的结果,延迟(delay bias)分别是 10 ms、30 ms、90 ms、270 ms,、810 ms 和 2430 ms,但是在该图表中,我仅包括了 10 ms 和 2430 ms 的结果,这样所有的数字都被简化,而且更容易理解。
我需要解释 "delay bias." 的含义,如果一个测试运行的 delay bias 是 n,则每一个计算都有一个倍数 n 作为其计算时间。例如,如果试验的是 delay bias 为 10 的 5 个计算,则其中一个计算将执行 50 ms,第二个将执行 40 ms,第三个将执行 30 ms,第四个将执行 20 ms,而第五个将执行 10 ms。并且,当这些计算被串行执行时,假定为最糟的情况,所以具有最长延迟的计算首先被执行,其他的计算按降序排列其后。于是,在“理想”情况下(就是说,计算之间没有重叠),对于基于cpu的计算来说,全部所需的时间将是 50 ms + 40 ms + 30 ms + 20 ms + 10 ms = 150 ms。
对于消耗时间图表来说, y 轴的值与毫秒对应,对于响应时间图表来说,y 轴的值与相对(relative turnaround)长度(即,实际执行所花费的毫秒数除以预期的毫秒数)相对应。
图 1. 短计算消耗时间比较,在 windows nt 下
图 2. 长计算消耗时间比较,在 windows nt 下
图 3. 短计算响应时间比较,在 windows nt 下
图 4. 长计算响应时间比较,在 windows nt 下
图 5. 短计算消耗时间比较,在 windows 95 下
图 6. 长计算消耗时间比较,在 windows 95 下
图 7. 短计算响应时间比较,在 windows 95 下
图 8. 长计算响应时间比较,在 windows 95 下
基于 i/o 的任务
以消耗时间和 turnaround 时间来衡量,基于 i/o 的线程当并发执行时比串行执行要好得多。作为计算得一个功能,对于并发执行来说,消耗时间以线性模式递增,而对于串行执行来说,则以指数模式递增(有关 windows nt 请参阅图 1 和 2,有关 windows 95 请参阅图 5 和 6)。
请注意,这个结论与我们前面对基于 i/o 的计算的分析是一致的,基于 i/o 的计算是多线程的优秀候选人,因为一个线程在等待 i/o 请求结束时被挂起,而这段时间该线程不会占用 cpu 时间,于是,这段时间就可以被其他的线程所使用。
对于并发计算来说,平均响应时间是一个常数,对于串行计算来说,平均响应时间则线性递增(请分别参阅图 3、4、7 和 8)。
请注意无论任何情况,只有少数几个计算执行的方案中,无论串行或并发的执行,无论测试参数如何设置,并没有什么明显的区别。
基于 cpu 的任务
正如我们前面所提到的,在一个单处理器的计算机中,基于 cpu 的任务的并发执行速度不可能比串行执行速度快,但是我们可以看到,在 windows nt 下线程创建和切换的额外开销非常小;对于非常短的计算,并发执行仅仅比串行执行慢 10%,而随着计算长度的增加,这两个时间就非常接近了。以响应时间来衡量,我们可以发现对于长计算,并发执行相对于串行执行的响应增益可以达到 50%,但是对于短的计算,串行执行实际上比并发执行更加好。
windows 95 和 windows nt 之间的比较
如果我们看一看有关长计算的图表(即,图2、4、6 和 8),我们可以发现在 windows 95 和 windows nt 下其行为是极其类似的。请不要被这样的事实所迷惑,即好象 windows 95 处理基于i/o的计算与基于cpu的计算不同于 windows nt。我把这一结果的原因归结为这样一个事实,那就是我用来决定多少个测试循环与 1 毫秒相对应的算法(如前面所述)是非常不精确的;我发现同样是这个算法,在完全相同的环境下执行多次时,所产生的结果之间的差异最大时可达20%。所以,比较基于 cpu 的计算和基于 i/o 的计算实际上是不公平的。
windows 95 和 windows nt 之间不同的一点是当针对短的计算时。如我们从图1 和5 所看到的,对于并发的基于i/o的短计算,windows nt 的效果要好得多。我把这一结果得原因归结为更加有效率得线程创建方案。请注意,对于长得计算,串行与并发i/o操作之间的差别消失了,所以这里我们处理的是固定的、相对比较小的额外开销。
对于短的计算,以响应时间来衡量(如图 3 和 7),请注意,在 windows nt 下,在10个线程处有一个断点,在这里更多的计算并发执行有更好的效果,而对于 windows 95 ,则是串行计算有更好的容量。
请注意这些比较都是基于当前版本的操作系统得到的(windows nt 3.51 版和 windows 95),如果考虑到操作系统的问题,那么线程引擎非常有可能被增强,所以两个操作系统之间的各自行为的差异有可能消失。但是,有一点很有趣的要注意,短计算一般不必要使用多线程,尤其是在 windows 95 下。
建议
这些结果可以推出下面的建议:决定多线程性能的最主要因素是基于 i/o 的计算和基于 cpu 的计算的比例,决定是否采用多线程的主要条件是前台的用户响应。
让我们假定在您的应用程序中,有多个子计算可以在不同的线程中潜在地被执行。为了决定对这些计算使用多线程是否有意义,要考虑下面的几点。
如果用户界面响应分析决定某些事情应该在第二线程中实现,那么,决定将要执行的任务是基于i/o的计算还是基于cpu 的计算就很有意义。基于i/o的计算最好被重新定位到后台线程中。(但是,请注意,异步单线程的 i/o 处理可能比多线程同步i/o要好,这要看问题而定)非常长的基于cpu的线程可能从在不同的线程中被执行获益;但是,除非该线程的响应非常重要,否则,在同一个后台线程中执行所有的基于 cpu 的任务可能比在不同的线程中执行它更有意义。请记住在任何的情况下,短计算在并发执行时一般会在线程创建时有非常大的额外开销。