我最近在测试用 Go 来操作 SQLite 的性能,用的是这个库( https://github.com/mattn/go-sqlite3 )。
我的测试代码在这个这个仓库的代码,我参考了这个仓库的代码,其测试流程是:
${numberOfCores} * 2
个工人的 dispatcher 。1024 * 1024 * 20
个 People 的实例,一共大约二千万个。我本来以为,用的 dispatcher 会令性能提高,但是我发现读取数据的时间非常长:
root@fw0016589:/home/user/src/github.com/zzxgzgz/SQLite_Multithreading_Go# ./main
2022/03/16 14:41:39 Hello world!
2022/03/16 14:41:39 All 112 workers are running, now you may dispatch jobs.
2022/03/16 14:41:54 Gernated 20971520 people!
2022/03/16 14:50:48 Inserting 20971520 people took 8m54.187231461s
2022/03/16 16:42:39 To query 20971520 people, it took time: 1h51m50.105310237s
读取的 QPS 只有可怜的 20,971,520 / (111 * 60 + 51) = 3,124.947
后来我在网上找到了这个帖子,用帖子里面的代码(补上了帖子里面没有分享的 struct ),测试得到的结果好了很多:
./main
SQLite start
insert span= 62 read span= 107 avg read= 0.0107
这个测试与我的类似,它是插入和读取一千万条数据,然后它的 qps 是 10,000,000 / 107 = 93,457.94
,比我的代码快了约 30 倍。
但是,这个离我的目标还有一些距离,这个帖子声称它可以单机(性能非常高的机器)四百万 qps 。我运行了帖子里提供的性能测试(C++代码),它在我的机器上能达到一百二十万以上的 qps:
Thu Mar 17 10:30:58 PDT 2022 Starting: -vms unix-excl -locking_mode NORMAL (./perftest, /home/user/src/github.com/Expensify/Bedrock/perftest/test.db)
./perftest -csv -numa -numastats -mmap -linear -vms unix-excl -locking_mode NORMAL -testSeconds 60 -maxNumThreads 256 -dbFilename /home/user/src/github.com/Expensify/Bedrock/perftest/test.db
Enabling NUMA awareness:
numa_available=0
numa_max_node=1
numa_pagesize=4096
numa_num_configured_cpus=56
numa_num_task_cpus=56
numa_num_task_nodes=2
numThreads, maxQPS, maxQPSpT
1, 46024, 46024
2, 91274, 45637
3, 137404, 45801.3
4, 180705, 45176.2
5, 225147, 45029.4
6, 272936, 45489.3
7, 314279, 44897
8, 364338, 45542.2
9, 405119, 45013.2
10, 448852, 44885.2
11, 494748, 44977.1
12, 537469, 44789.1
13, 591314, 45485.7
14, 637572, 45540.9
15, 678796, 45253.1
16, 730028, 45626.8
17, 770238, 45308.1
18, 815726, 45318.1
19, 858698, 45194.6
20, 907344, 45367.2
21, 951636, 45316
22, 994060, 45184.5
23, 1041419, 45279.1
24, 1083378, 45140.8
25, 1128111, 45124.4
26, 1169421, 44977.7
27, 1216605, 45059.4
28, 1257847, 44923.1
29, 1260620, 43469.7
30, 1266371, 42212.4
31, 1268080, 40905.8
32, 1266702, 39584.4
33, 1275697, 38657.5
34, 1285441, 37807.1
35, 1279162, 36547.5
36, 1285150, 35698.6
我的问题是:
这个帖子有点长了,谢谢你花时间来阅读。
1
tinkerer 2022-03-24 05:23:51 +08:00
性能差距可能并不在于你的 golang 代码优化上,而是 cgo 本身的损耗,包括 c 类型 与 golang 类型的转换和 ffi 。c++ 与 C 库的交互几乎没有中间损耗,不像 golang 有自己的 runtime 。
鉴于你好像想要近似于 c++ 版的性能,那你可能需要用 c/c++ 写你的程序,因为 golang 的性能本身就比不上 c/c++。 |
2
tinkerer 2022-03-24 05:30:17 +08:00
关于问题 1 ,因为 sqlite 的一切都是直接与文件的交互,不存在真正意义上的多线程操作,而是排队干活。
|
3
xupefei 2022-03-24 07:40:06 +08:00 via iPhone
这测试真的靠谱?磁盘 IO 在 ACID 的前提下真的能达到这种水平吗?
|
4
lloovve 2022-03-24 08:59:40 +08:00
最近也在研究 go sqlite ,我感觉不是 golang 的问题,golang 和 c 差距不可能这么大,感觉可能是驱动的问题,从下面链接看也不是 cgo 的问题
https://golangexample.com/sqlite-http-server-performance-benchmark/ |
5
wtfdsy 2022-03-24 09:51:28 +08:00
不太懂 GO ,提供个思路,我用 C++用 sqlite 追求极限性能的时候都是把数据库往内存弄一份用 inmemory 模式的
|
6
zzxgz OP 谢谢各位的回复!
@tinkerer #2 我也同意你的观点。[这个回答]( https://stackoverflow.com/a/4060838/8883222) 说,只要同一时间只有一个 writer ,那么应该也是没问题的(1 writer n readers)。如果真的是 cgo 的问题的话,那么这个问题在 Go 里面可能就暂时没有解决办法了,可能只能用 C++达到一百万 qps 的性能了。 @xupefei 应该是可以的,我觉得 Bedrock 的测试挺好的。想请教一下,其他的常用的数据库大约能达到怎么样的性能呢?会比一百万 qps 低很多吗? @lloovve 谢谢你分享的链接,看起来这个人测试的结果都在 10 万 qps 以下,[这个帖子]( https://turriate.com/articles/making-sqlite-faster-in-go)也测试了`mattn/sqlite3`,得出的 qps 也是类似的水平。 @wtfdsy 谢谢你的分享![这个测试]( https://www.cnblogs.com/liughost/p/6698205.html)它用的也是`inmemory`模式(`mode=memory`),我把它用内存模式和不用内存模式都测了一次,性能上的差距不是很大,主贴里面的测试结果(`avg read = 0.0107`)是没有用内存模式的,用内存模式的结果大约是(`avg read = 0.009xxx`)。 现在看起来,用 C++(或者 C )跟用 Go 的性能差别有点大啊,但是我们是希望能用 Go 来达到这个性能目标的(百万级或者不差太远的 qps ),不知道各位还有没有其他的想法呢? 对了,其实我也在`mattn/go-sqlite3`这个仓库里面提了[差不多的的问题]( https://github.com/mattn/go-sqlite3/issues/1022),但是回复我的人好像也是没啥头绪。 |
7
zzxgz OP 刚刚发现了一个测试的错误,并在 append 里补上了重新测试的结果。看来用 Go 现在最多也只有 5 万 qps 了,离一百万 qps 差得太远了。
|
8
xupefei 2022-03-25 02:52:17 +08:00 via iPhone
@zzxgz 你想想硬盘的 IOPS 能上百万吗?你的测试里可是有写操作的。在 ACID 的前提下数据库理论写速度不可能超过硬盘 IO 。
那如果百万 IOPS 指的是关掉 ACID 的内存数据库,那你的测试方法就没意义,结果自然也没意义了。 |
9
zzxgz OP |
11
xupefei 2022-03-25 06:48:41 +08:00 via iPhone
你的实验如果全是读操作,那么数据库完全是多余的。还不如各写一个循环算次数
|