13.2 预先计算
预先计算和缓存联系紧密。当缓存某个计算的结果时,需要付出的代价是在对性能有重大影响的关键路径上完成一次计算。如果采用预先计算,那么甚至连这一次计算也可免了。将预先计算放置在影响性能的关键路径之外(例如初始化阶段),就可以避免在性能关键路径上进行代价高昂的计算。
回到Web服务器实现的示例中,Web服务器将相当的时间花费在字符串处理上,如果可以提高字符串处理的速度,那么服务器的性能将得到显著提升。将字符串或字符转换为大写是经常会遇到的字符串处理任务。如果浏览器给服务器发送一个"Accept:"请求报头,服务器必须能识别,而不管报头的字母是大写还是小写,不必对"accept:","AcCePt:"或其他大小写混合的字符串进行区分。(accept报头告诉服务器可以接收的文档类型,例如text/html,image/gif……)
由于函数memcmp(header, "ACCEPT:", 7)区分大小写,因此在调用memcmp()函数前应先将报头字符串转化为大写格式。方法如下:
- for (char *p = header; *p ; p++) { //将报头转化为大写格式
- *p = toupper(*p);
- }
-
- if (0 == memcmp(header, "ACCEPT:", 7)) {
- ...
- }
在影响性能的关键路径上,反复调用toupper()的代价是无法接受的,即便将其作为内联函数实现,仍将包含如下形式的条件语句:
- return (c >='a' && c<='z') c
- ('a'-'A') : c;
这是针对ASCII的实现。而针对EBCDIC字符集(EBCDIC字母表的顺序不是不连续的)的实现会更加复杂。
如果希望避免为可能的库函数调用或内联条件语句的开销,可以预先计算出每个可能出现的字符相应的大写值:
- void initLookupTable()
- {
- for (int i = 0; i < 256; i++) {
- uppercaseTable[i] = toupper(i);
- }
- }
由于uppercaseTable在起始阶段完成初始化,所以islower()和toupper()代价是无须考虑的,其性能也就不成问题。
现在只需执行两条指令即可将字符转化为大写格式,这将显著提高字符串操作的效率:
- for (char *p = header; *p ; p++) {
- *p = uppercaseTable[*p];
- }
-
- if (0 == memcmp(header, "ACCEPT:", 7)) {
- ...
- }
通过这种方式,可以预先计算出在性能关键路径上出现的其他字符处理函数查询表:islower()、isspace()、isdigit()等等。