在书中的第七章《性能开销》中有两个小例子,由此也就引出了附录C时空开销模型中的两段比较牛逼的程序,我辈也只能学习学习其中的思想和编程手法了。
第一个例子是估计一个数据结构占用的字节数,书中的问题为两百万个如下的节点能否装入128MB的计算机中:
struct node {int i; struct node *p;};
按照一般的思路来讲,这样一个节点在32位机器上占用的内存为 4+4 = 8 字节,但是在书中,其占用作者机器的内存却为 48 字节,原因就在于作者使用了malloc函数来为记录动态分配空间,导致了每个结点多占用了40字节,这样两百万个节点就需要96MB的空间。附录C中的spacemod.cpp程序主要给我的启示是:
1.利用malloc或new运算符给数据结构分配空间时一般要比需要的空间大(从某种程度上说是造成了空间的浪费),而究竟比sizeof()后得到的需要的空间大多少则视不同的机器而定,书中作者的机器和我实验的机器多分配的空间字符数就不同。
2.作者的程序用到了指向新分配结构和指向原有结构的指针之差来表示新分配结构占用的空间大小,用连续指针的差别(作者用到了10个以保证结果的平均性),这一点值得学习,而把函数用宏的形式写出来。(为什么一定要用宏写出来呢?我想也许是调用时的参数为“数据结构类型”,而宏可以不做参数类型的检查,达到某种“泛型”的效果,调用起来也更方便。就这一点的猜想,后来作者也在书中用C++模板来实现,但是作者说到模板的人为因素会使度量结果差别很大所以没有用。)宏写出来真的很方便,而且调用起来显得很简洁。
程序和运行结果如下(代码全拷贝):
#include <iostream>
using namespace std;
#define MEASURE(T, text) {
cout << text << "\t";
cout << sizeof(T) << "\t";
int lastp = 0;
for (int i = 0; i < 11; i++) {
T *p = new T;
int thisp = (int) p;
if (lastp != 0)
cout << " " << thisp - lastp;
lastp = thisp;
}
cout << "\n";
}
// Must use macros; templates give funny answers
template <class T>
void measure(char *text)
{ cout << " measure: " << text << "\t";
cout << sizeof(T) << "\n";
}
struct structc { char c; };
struct structic { int i; char c; };
struct structip { int i; structip *p; };
struct structdc { double d; char c; };
struct structcd { char c; double d; };
struct structcdc { char c1; double d; char c2; };
struct structiii { int i1; int i2; int i3; };
struct structiic { int i1; int i2; char c; };
struct structc12 { char c[12]; };
struct structc13 { char c[13]; };
struct structc28 { char c[28]; };
struct structc29 { char c[29]; };
struct structc44 { char c[44]; };
struct structc45 { char c[45]; };
struct structc60 { char c[60]; };
struct structc61 { char c[61]; };
int main()
{ cout << "Raw sizeof";
cout << "\nsizeof(char)=" << sizeof(char);
cout << " sizeof(short)=" << sizeof(short);
cout << " sizeof(int)=" << sizeof(int);
cout << "\nsizeof(float)=" << sizeof(float);
cout << " sizeof(struct *)=" << sizeof(structc *);
cout << " sizeof(long)=" << sizeof(long);
cout << "\nsizeof(double)=" << sizeof(double);
cout << "\n\nMEASURE macro\n";
MEASURE(int, "int");
MEASURE(structc, "structc");
MEASURE(structic, "structic");
MEASURE(structip, "structip");
MEASURE(structdc, "structdc");
MEASURE(structcd, "structcd");
MEASURE(structcdc, "structcdc");
MEASURE(structiii, "structiii");
MEASURE(structiic, "structiic");
MEASURE(structc12, "structc12");
MEASURE(structc13, "structc13");
MEASURE(structc28, "structc28");
MEASURE(structc29, "structc29");
MEASURE(structc44, "structc44");
MEASURE(structc45, "structc45");
MEASURE(structc60, "structc60");
MEASURE(structc61, "structc61");
cout << "\nmeasure template (strange results)\n";
// Uncomment below lines to see answers change
measure<int>("int"); // 注意其中函数模板的调用方法,我还没见过函数模板这么用过,这应该是类模板吧
// measure<structc>("structc");
// measure<structic>("structic");
return 0;
}
运行的结果:
程序中间的指针有些跳跃。我实验的环境中用C++的模板测试前三个例子都没有得到(funny result),在new运算符导致的多余空间申请上,我的机器一般是8的倍数,而且和原有需要的空间比较接近,还是比较优化的,要比作者当时测试的机器强一些。
书中的第二个例子是估算一个基本运算需要的时间消耗。同样在附录C中有一个牛逼的程序,把已知的很多运算都考虑了进去,而且比较的宏运算,内联函数和函数的运算时间差异。其主要函数也是用宏写出来的,调用时界面也是很简洁整齐。