Go vs PHP CLI 在特定项目环境下的性能对比
最近在规划一个电影推荐程序,其中一个核心部分是通过用户行为来为用户推荐感兴趣的电影,考虑到计算量比较大,所以我想找一个比较"快"的语言,主要是通过命令行模式调用,只要够快就行。我的第一目标是Go,鬼使神差的我做了这么一次Go Vs PHP Cli的测试,结果可以说和我想象的完全不一样,故把数据整理出来方便大家参考。
{ No.1 基本信息 }
场景: 为用户推荐感兴趣电影的App,本次测试主要考虑推荐算法的实现
流程: 用户使用App选择推荐条件,通过RESTful API写入推荐任务队列(DB),启动命令行算法进程进行运算并将结果写回DB, 最后API将结果传递至App反馈给用户
数据: 电影数据量在10w级,关联数据关系复杂: 分类,语言,地区,主演,导演,编剧等
{ No.2 测试环境 }
- MBP Pro笔记本一台(正在度假⛱️,手边资源有限,偷个懒), CPU 2.3GHz, Memery 16GB
- Go v1.13 无框架直接使用各种 packs(github.com/go-sql-driver/mysql 等)
- php v5.6.24 and v7.1 使用Yii 2.0框架
- 数据库MySQL v5.7.15, 本次测试不考虑数据库带来的影响
{ No.3 测试思路 }
推荐逻辑主要循环进行以下两种类型操作:
- 从数据库读取数据,90%以上的数据库操作都是读取
- 对数据进行分析和计算
数据库操作和数据分析的比例大概在7:3。所以我构思了如下的测试样本:
在指定范围内生成一个随机数,做为主键对location表进行单次读取,读取后的结果取其中一个字段(数字类型)和随机数相加求和(其间模拟一些条件判断和循环),送入下一轮,不断随机读取求和,分别进行1000,5000,50000次循环,来查看耗时情况(所有测试均进行10次求平均,来降低偏差)
{ No.4 单线程模式 }
首先是单线程模式, 单线程模式是我最喜欢的模式,因为去除了一些干扰因素,可以更好的建立基准量,在后续的测试中会考虑更复杂的模式,但单线程依然是一个重要参考标准。
Lang | 1000 times | 5000 times | 50000 times |
Go | 0.1451s | 0.7075s | 7.0355s |
PHP5 CLI | 0.2519s | 1.4001s | 14.3123s |
PHP7 CLI | 0.1061s | 0.4939s | 4.9861s |
Points:
- 所有PHP代码均以Yii 2.0 DAO模式运行,未使用AR模式, AR的运行速度会稍微慢一些, 测试中用到的Go代码为DAO模式
- 所有测试结果都是进行相同测试10次,然后取平均数所得,以求尽量减少误差,下同
这里的测试结果让我很意外,老实说我想象中的PHP7 CLI模式应该接近表中的PHP5 CLI模式,Go应该更快一些,但实际跑下来,PHP7比Go快了50%,当然也可能是我的测试方案对Go不是很公平,但我的需求确实如此
{ No.5 PHP5 CLI vs Web }
这是一个后补测试,在我完成了所有测试之后,我非常好奇Web模式会比CLI模式慢多少,所以我使用了PHP5的CLI和Web模式在单线程场景下进行了对比
Mode | 1000 times | 5000 times | 10000 times |
PHP5 CLI | 0.2519s | 1.4001s | 2.6424s |
PHP5 Web | 0.3940s | 1.9377s | 4.6751s |
Points:
- 因为web模式有最大内存限制,所以把第三组测试重复数量从50000降低到了10000
相同代码Web运行效率低于CLI, 大概差50%左右, 这倒是完全符合预期
{ No.6 PHP7 CLI DAO vs AR }
Yii 2.0框架有2种数据库操作模式, DAO(Data Access Object 通过SQL命令直接操作数据库模式) 和 AR(Active Record 使用类似ORM模型实现数据库访问),AR会比DAO慢因为多了一层数据模型及相关操作,但AR开发效率更快同时也带来了很多额外功能,所以这里的意义在于平衡开发效率和运行效率
Mode | 1000 times | 5000 times | 50000 times |
PHP7 CLI AR | 0.1643s | 0.7092s | 7.1816s |
PHP7 CLI DAO | 0.1061s | 0.4939s | 4.9861s |
通过数据可以得出AR模式较DAO模式多出大概40%的性能消耗,当然这仅仅是在读取这一单一场景下,在较复杂的实际项目中影响因素会更多。但实话实说用40%的性能来换取AR带来的便利我是愿意的,要知道AR所能带来的开发便利非常巨大,这里无法完全展示。同时也提醒一下,在重要版本和复杂场景使用DAO是必须的,在开发初期(MVP)和非重要版本使用AR一样是非常有益的。
{ No.7 多线程并行测试 }
在实际生产场景中无论是Go还是PHP CLI我们都会切换到多线程并行模式来运行,所以我设计了如下的实验场景:
- Go 多线程并行测试,使用go 关键词开启goroutines, 进行多线程并行测试
- PHP 多线程并行测试,我的方案是通过iTerm2的多窗口同步执行命令来完成(等于是手动多线程),程序输出开始和结束时间,找到最早的开始时间和最晚的结束时间,差值就是我们需要的总运行时间
- 两种语言的并行测试虽并不100%相同,但对我来说,在一定程度上可以做为参考依据了,毕竟在单线程模式下Go比PHP7慢了50%,我很好奇多线程并行的结果会如何
- 最后我追加了Go的iTerm2 多窗口手动多线程并行测试
Mode | 1000 *5 times | 5000*5 times | 50000*5 times |
Go with goroutines | 0.2801s | 1.4198s | 14.5827s |
Go with 5 iterm sessions | 0.3211s | 1.4439s | 14.0207s |
PHP7 CLI with 5 iterm sessions | 0.192s | 0.9562s | 9.4134s |
Points:
- 在gorountines测试中我尝试为Go 设置了 runtime.GOMAXPROCS, 希望通过这个设置来开启更佳的并行效率,但是实际情况是,设置后并没有任何改善,当设置为最大核心数的时候和未设置效率相同,我想Go v1.13版本应该是默认最大使用内核数, 这里只是通过设置数值2 4 8 16 32 来做测试,并无法确定默认一定是最大值,这里对我来说意义不大所以没有深究
- 在gorountines测试中同样尝试了runtime.Gosched,但没有对最终结果产生任何影响
- 我在这里设置 runtime.GOMAXPROCS and runtime.Gosched 只是为了让Go达到最佳的并行效率(注意并行并非并发),我并没有足够的Go开发经验,所以这里可能会有疏漏,将来我会根据版本迭代进行修复
果不其然PHP7的CLI模式表现的更好一些,差不多有40%的性能优势(如果使用AR模式会增加40%的性能消耗,达到和Go相同的运行效果)
{ No.8 结论 }
老实说, 最终的测试结果让我非常非常的意外, 在测试之初我想的是Go DAO会比PHP7 CLI DAO快 30%-40%, 但是实际测试下来 DAO模式的PHP7 CLI居然比Go DAO快了40%, AR模式下PHP7 CLI和Go DAO效率相同, 同时PHP还有一层框架加持,一定程度上效率会慢于原生PHP,因为我并不准备用原生PHP来做开发,所以这里不做考量.
最终在App 1.0版本中我决定使用PHP7 CLI DAO模式来实现推荐算法(前期版本使用PHP7 CLI AR模式来做验证性实验), 这个模式是我之前几年一直在使用的一种模式,非常之熟悉,开发成本和效率也会更好,除此之外也节约了新语言学习的成本(就是填坑的过程啦)。
当然这里需要再次重申,这次测试有其天然的片面性,无法证明Go和PHP7的优劣,只能说明在我设计的测试样本中PHP7 更快,仅此而已,将来我也会花更多的时间去学习Go, Go也确实足够让人惊艳 It's already on my list.
{ No.9 未覆盖的测试内容 }
以下内容并未在本次测试中覆盖到,后续版本测试我会尝试进行统计:
- CPU 使用率
- Memery使用率
- 多线程并行测试的瓶颈所在
{ No.10 后续计划 }
在1.0版本开发完成并投入生产环境之后我会,在做一次回归测试,整个测试完成以后我一直隐约的觉得应该再深入一些,但是时间有限(毕竟我还在度假中哈), 所以只能定一个后续的计划了, ok to be continues....
{ Ref. }
留言