在阅读比特币源码过程中发现了一个有趣的问题。
在文件src/serialize.h中,2012年9月提交的一个版本b019ea17ec7cc37d098982b4f0f4636e424ab4b8,
我们可以看到一些历史代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#ifdef TESTCDATASTREAM
// VC6sp6
// CDataStream:
// n=1000 0 seconds
// n=2000 0 seconds
// n=4000 0 seconds
// n=8000 0 seconds
// n=16000 0 seconds
// n=32000 0 seconds
// n=64000 1 seconds
// n=128000 1 seconds
// n=256000 2 seconds
// n=512000 4 seconds
// n=1024000 8 seconds
// n=2048000 16 seconds
// n=4096000 32 seconds
// stringstream:
// n=1000 1 seconds
// n=2000 1 seconds
// n=4000 13 seconds
// n=8000 87 seconds
// n=16000 400 seconds
// n=32000 1660 seconds
// n=64000 6749 seconds
// n=128000 27241 seconds
// n=256000 109804 seconds
#include <iostream>
int main(int argc, char *argv[])
{
vector<unsigned char> vch(0xcc, 250);
printf("CDataStream:\n");
for (int n = 1000; n <= 4500000; n *= 2)
{
CDataStream ss;
time_t nStart = time(NULL);
for (int i = 0; i < n; i++)
ss.write((char*)&vch[0], vch.size());
printf("n=%-10d %d seconds\n", n, time(NULL) - nStart);
}
printf("stringstream:\n");
for (int n = 1000; n <= 4500000; n *= 2)
{
stringstream ss;
time_t nStart = time(NULL);
for (int i = 0; i < n; i++)
ss.write((char*)&vch[0], vch.size());
printf("n=%-10d %d seconds\n", n, time(NULL) - nStart);
}
}
#endif

这段代码是用来测试CDatastream类与C++标准库std::stringstream的性能的。
在作者的测试结果中我们可以看到CDatastream的性能远远好于std::stringstream,然后我们再看一看编译平台:VC6SP6
Orz… 大写的懵逼 VC6…

之所以要说懵逼,是因为CDatastream是一个很底层的类,用于所有全节点之间数据同步的,也就是说,所有的消息需要封装成CDatastream的形式再进行发送。
再想一想比特币那几十G的全节点数据,是不是很爆炸。

那么我们在来看一看现在这段代码的性能对比:
我基于ubuntu 16.04 LTS 进行测试,gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.1)
机器的配置很一般一般,一般到什么程度呢?这是8年前的电脑,酷睿双核的哪种。

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CDataStream:
n=1000 0 seconds
n=2000 0 seconds
n=4000 0 seconds
n=8000 0 seconds
n=16000 1 seconds
n=32000 0 seconds
n=64000 1 seconds
n=128000 2 seconds
n=256000 5 seconds
n=512000 9 seconds
n=1024000 18 seconds
n=2048000 37 seconds
n=4096000 73 seconds

1
2
3
4
5
6
7
8
9
10
11
12
13
14
stringstream:
n=1000 0 seconds
n=2000 0 seconds
n=4000 0 seconds
n=8000 0 seconds
n=16000 0 seconds
n=32000 0 seconds
n=64000 0 seconds
n=128000 0 seconds
n=256000 0 seconds
n=512000 0 seconds
n=1024000 1 seconds
n=2048000 0 seconds
n=4096000 1 seconds

我在阿里云上也进行了测试, ubuntu 14.04 LTS, gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.1), 双CPU双Core

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CDataStream:
n=1000 0 seconds
n=2000 0 seconds
n=4000 0 seconds
n=8000 0 seconds
n=16000 1 seconds
n=32000 0 seconds
n=64000 1 seconds
n=128000 1 seconds
n=256000 3 seconds
n=512000 5 seconds
n=1024000 9 seconds
n=2048000 19 seconds
n=4096000 39 seconds

1
2
3
4
5
6
7
8
9
10
11
12
13
14
stringstream:
n=1000 0 seconds
n=2000 0 seconds
n=4000 0 seconds
n=8000 0 seconds
n=16000 0 seconds
n=32000 0 seconds
n=64000 0 seconds
n=128000 0 seconds
n=256000 0 seconds
n=512000 0 seconds
n=1024000 0 seconds
n=2048000 1 seconds
n=4096000 1 seconds

通过测试好像知道了些什么,不太清楚古老的VC6的标准库实现标准是什么,总之stringstream的性能应该是特别感人,才导致了satoshi先生不得不自己去打造一个看起来不那么搓的数据流类。
另外一个例证是 [Bitcoin-development] CDataStream 开发者的交流邮件,我们可以看到
基于 i686-apple-darwin11-llvm-g++-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.1.00 版本的测试性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CDataStream:
n=1000 0 seconds
n=2000 0 seconds
n=4000 0 seconds
n=8000 0 seconds
n=16000 0 seconds
n=32000 0 seconds
n=64000 1 seconds
n=128000 1 seconds
n=256000 2 seconds
n=512000 4 seconds
n=1024000 8 seconds
n=2048000 17 seconds
n=4096000 40 seconds

1
2
3
4
5
6
7
8
9
10
11
12
13
14
stringstream:
n=1000 0 seconds
n=2000 0 seconds
n=4000 0 seconds
n=8000 0 seconds
n=16000 0 seconds
n=32000 0 seconds
n=64000 0 seconds
n=128000 0 seconds
n=256000 0 seconds
n=512000 0 seconds
n=1024000 0 seconds
n=2048000 1 seconds
n=4096000 2 seconds

所以我们的推断就是市场上主流的C++编译器随着时间的推移以及无数开源贡献者的贡献,std::stringstream已经经过了极大的优化,远超VC6是自然而然的事情,
这点我们也可以从bitcoin/bitcoin 的 git history中可以看到:source link click me

1
2
3
4
5
6
7
8
9
10
Remove VC6 comment and pointless #ifdef'd benchmark code
We're in a wholly different world now, C++-compiler-wise.

Current std::stringstream implementations don't have the stated problem anymore,
and are just as fast as CDataStream.

The #ifdef'd block does not even compile anymore; CDataStream constructor changed,
and missing some std::. Also timing in whole seconds is also way too granular
to say anything sensible in such microbenchmarks. Just remove it,
it can always be found again in git history.

可以感受到作者深深的无奈。

当然performance是其中一个方面,在可定制的要求上也应该是很强烈的,这也促成了CDatastream类的一直延续。
在bitcoinv0.12版本的代码中,我们可以看到serialize.h的实现并没有特别大的变更,只是想一些数据类型做了规范化的重载,并且去掉了预编译宏与模板的交叉代码。
所以在bitcoind的最新代码上我没有再次进行CDatastream与std::stringstream的性能测试,感兴趣的可以试一下。
另外,如果也是想通过C++完全自己实现一个类bitcoin的系统,一个的可行建议是实现std::stringstream 的wrapper来实现数据的流化,来降低工作量。