随着国内Golang的火爆,phper的生存压力越来越大,在一次内部技术讨论中,gopher甚至提出,要什么php,写php的全部开掉,唉,码农何苦为难码农。

本文试图寻找一种有效实践,减少php web程序和golang之间的性能差距,摆脱php在公司往后只能写管理后台的悲惨命运。

做优化的思路

1、了解php语言特性

2、了解php的执行过程

3、压测分析性能

语言特性

PHP被称为脚本语言或解释型语言,它没有被直接编译为机器指令,而是编译为一种中间代码的形式,无法直接在CPU上执行。 所以PHP的执行需要在进程级虚拟机上(见Virtual machine中的Process virtual machines,下文简称虚拟机)。

PHP语言,包括其他的解释型语言,其实是一个跨平台的被设计用来执行抽象指令的程序。PHP主要用于解决WEB开发相关的问题。

诸如Java, Python, C#, Ruby, Pascal, Lua, Perl, Javascript等编程语言所编写的程序,都需要在虚拟机上执行。虚拟机可以通过JIT编译技术将一部分虚拟机指令编译为机器指令以提高性能。PHP未来有可能加入JIT支持。

使用解释型语言的优点:

  • 代码编写简单,能够快速开发
  • 自动的内存管理
  • 抽象的数据类型,程序可移植性高

缺点:

  • 无法直接地进行内存管理和使用进程资源
  • 比编译为机器指令的语言速度慢:通常需要更多的CPU周期来完成相同的任务(JIT试图缩小差距,但永远不能完全消除)
  • 抽象了太多东西,以至于当程序出问题时,许多程序员难以解释其根本原因

PHP的生命周期

Zend虚拟机分为两大部分:

  • 编译:将PHP代码转换为虚拟机指令(OPCode)
  • 执行:执行生成的虚拟机指令

zend执行过程

1
2
3
4
词法分析(zend_language_scanner),将PHP代码转换为语言片段(Tokens)
语法分析(zend_language_parser)将Tokens转换成简单而有意义的表达式
编译(compiler),将表达式编译成Opocdes,返回zend_op_array指针
Zend Engine(zend_vm_execute),顺次执行Opcodes,每次一条, 根据传入的zend_op_array指针,执行opcode并将结果返回输出

解释型语言性能问题也就是因为每次执行脚本,上述过程都会重复执行。因此,也就出现了APC, xcache, eAccelerator等缓存,不过现在官方主推的是opcache

什么是opcode缓存

当解释器完成对脚本代码的分析后,便将它们生成可以直接运行的中间代码,也称为操作码(Operate Code,opcode)。Opcode cache的目地是避免重复编译,减少CPU和内存开销。如果动态内容的性能瓶颈不在于CPU和内存,而在于I/O操作,比如数据库查询带来的磁盘I/O 开销,那么opcode cache的性能提升是非常有限的。也就是opcode cache能带来CPU和内存开销的降低

APC, xcache, eAccelerator,opcache 使用共享内存进行存储,并且可以直接从中执行文件,而不用在执行前“反序列化”代码

PHP-FPM的生命周期

模块初始化(master)

请求初始化 (worker)

执行脚本(worker)

请求关闭(worker)

模块关闭(master关闭)

由以上我们可以看到 php的优化思路:1、使用opcache去掉php生命周期的词法分析、语法分析、opcode生成环节  2、提升zend虚拟机性能 3、减少worker每次请求初始化的消耗

我们作为web开发者还能做什么优化呢?

1、使用轻量级框架

2、引入协程,解决多进程的调度消耗问题,解决IO阻塞问题

性能实验

几种框架比较压测

首先使用php内置web server做个测试

四核16G内存虚拟机,golang使用4个核,php使用单核

/usr/local/php-7.0.11/bin/php -S 10.20.1.12:8000 router.php -c php.ini

php.ini:

[opcache]
zend_extension = opcache.so
opcache.memory_consumption=
opcache.interned_strings_buffer=
opcache.max_accelerated_files=
opcache.revalidate_freq=
opcache.validate_timestamps=
opcache.fast_shutdown=
opcache.enable_cli=
opcache.enable=
[vld]
extension=vld.so

router.php

<?php
echo '{"errno":0,"errmsg":"success","data":"e"}';

siege -c 100 -r 10000 "http://10.20.1.12:8000/" -b

压测结果:

Transactions: 1000000 hits
Availability: 100.00 %
Elapsed time: 77.15 secs
Data transferred: 39.10 MB
Response time: 0.01 secs
Transaction rate: 12961.76 trans/sec
Throughput: 0.51 MB/sec
Concurrency: 97.56
Successful transactions: 1000000
Failed transactions: 0
Longest transaction: 0.24
Shortest transaction: 0.00

可以认为 php的虚拟机执行效率是可以的 ,使用golang的原生http模块echo helloworld 在24000 trans/sec 。

php的cpu利用率在100%,golang的利用率在 200% (设置了 runtime.GOMAXPROCS(4) 并没达到400%)

使用php-fpm方式挂载到nginx中去访问,直接请求index.php 并echo结果 trans: 7300 trans/sec

使用yaf controller方式 ,trans:5000 trans/sec , 损失了 32%的性能 ,略微尴尬

zan framework 3570 trans/sec

swoole!几年前测试,性能很不怎么样,如今,php7+swoole  25000 trans/sec  跟golang毫不逊色啊 !

dev压测

dev02启动一个qps 2w+的curl接口

dev03 4核16G机器,分别跑yaf 、golang、es(EasySwoole,之后换成yaf+swoole,性能差不多)、lua ,执行空接口、访问11的redis、访问dev02的curl接口

yaf 开启opcode,使用线上dynamic php-fpm配置,

es worker数设置为40(测试4核 40最佳)

案例
并发
请求数
失败数
QPS
性能指数(golang为基准)
yaf 空接口 200 100w  0 7013.11 trans/sec 24.5%
go 空接口 200 100w   0 28645.09 trans/sec 100%
es空接口 200 100w   0 27285.13 trans/sec 95%

yaf curl

200 100w   0 3475.33 trans/sec 26.3%
go curl 200 100w   0 13227.51 trans/sec 100%
es curl 200 100w   0 11178.18 trans/sec 84.5%
lua curl 200 100w 0 12528.19 trans/sec 94.7%
yaf redis read 200 100w 0 5389.09 trans/sec 32.6%
go redis read 200 100w 0 16550.81 trans/sec 100%
es redis read 200 100w 0 13917.88 trans/sec 84%

线上压测

当CPU提升到8核?

eris3v 压测 eris6v 的 yaf接口(access_log off ,减小写日志影响)

1、空接口
 siege -c 200 -r 4000 "10.110.18.72:8360/main/example" -b -q
Transaction rate:    20356.23 trans/sec

2、curl一次( lib httprequest写log)
siege -c 200 -r 4000 "10.110.18.72:8360/main/curl" -b -q
Transaction rate:    7560.01 trans/sec
 
3、curl一次( lib httprequest不写log)
siege -c 200 -r 4000 "10.110.18.72:8360/main/curl" -b -q
Transaction rate:    13807.39 trans/sec

4、读一次redis( zscore)
siege -c 200 -r 4000 "10.110.18.72:8360/member/in?rid=30510982&groupid=10000" -b -q
Transaction rate:    11677.13 trans/sec

5、读两次redis,  把测试3的逻辑在controller中执行两次
siege -c 200 -r 4000 "10.110.18.72:8360/member/in?rid=30510982&groupid=10000" -b -q
Transaction rate:    8463.79 trans/sec

线上环境压测发现,8核16G机器下,yaf+php-fpm的性能有大幅提升,空接口可以跑满8个核,如果不经过nginx日志,性能和swoole、golang仅有10%~20%左右差距,swoole受限于master调度,无法跑满8个核,只有一个核负载100%,其他空闲较大(多开master?使用层面暂时无法解决)。siege 不开启 -q quiet模式,在使用vpn或wifi情况下,有可能因为压测机到本机的同步output速度,影响压测结果,建议关闭。

性能分析

实验

问题简单化一下,我们测试一下在dev环境只有一个worker 只能利用单核情况下 原生php-fpm、php-fpm+yaf路由、 swoole+yaf的空跑接口性能差异(需要开启opcache)。

1、新建yaf项目

2、 使用 https://github.com/LinkedDestiny/swoole-yaf新建swoole+yaf项目,使用yaf作为路由

其中 yaf项目 可更改 src/public/index.php   只echo "hello world" ,不启动yaf 作为测试1 ,启动yaf 执行MainController中的exampleAction作为测试2,swoole+yaf项目作为测试3

siege -c 300 -r 3000  "10.20.1.13/Main/example" -b -q

 
90w请求
备注
 
测试1: php-fpm 5990 trans/sec    
测试2: yaf 2687 trans/sec    
测试3: swoole+yaf 18382 trans/sec

过nginx代理则变为8980 trans/sec,日志是性能杀手

golang也是一样,性能损失50%

 

分析

分别执行一次请求,使用strace 分别跟踪master和worker执行,

sudo strace -p 5450 -s 10000 -T  ,具体调用操作见附录

1) php-fpm

worker执行了24次系统调用 ,master没有操作,只是监控worker状态及重启

worker工作周期:

accept收到请求

1、fcgi_accept_request()  解析请求  fcgi_read_request() -> safe_read() ,调用了5次系统调用read() 才完成了fastcgi协议的解析

然后进入获取请求信息阶段,将请求的method、query string、request uri等信息保存worker进程的fpm_scoreboard_proc_s结构中

2、php_request_startup() 请求初始化

3、php_execute_script() 进入FPM_REQUEST_EXECUTING阶段,完成php脚本编译,执行操作 ,这个阶段虽然有opcache(已经对文件执行了open操作)仍然会做 getcwd chdir stat等系统操作去查找文件,然后执行 zend_execute_scripts ( zend_execute(op_array, retval);  ) , write 出结果

4、php_request_shutdown()   shutdown recvfrom 从主进程接收两次响应包, close  req文件描述符 ,这又是四次系统调用

重新等待下次请求

2)yaf

执行了38次系统调用, 24次是和fpm相同的 ,会额外stat open一次 app.ini文件,stat  Bootstrap.php、 include文件和controller文件,并做内存页映射操作

3) swoole+yaf

只执行了5次系统调用,发挥了常驻进程的优势, 其他系统调用在初始化时即完成,之后的请求只需要master accept 和epoll出请求, worker read ,在用户态处理后 sendto master即可完成,很简洁。

master

worker

结论

swoole+yaf因为是常驻进程,初始化只需要一次,在系统调用层面消耗非常少,单worker进程性能就非常强悍,但在多核多进程模型下,yaf和php-fpm又能依托多核硬件,追平性能差异,所以在机器预算有限情况下,比如1~4核,使用swoole+yaf ,相比yaf能大幅提升性能。在大部分web高性能接口场景,使用yaf或swoole就能够满足性能要求,且开发效率很高,并不必须要用golang。对于需要多线程、异步、长连接或者中间件、可靠分布式存储服务的场景还是选择golang比较靠谱,用swoole也有学习成本,不如只是用它最稳定成熟的地方。

附录

php-fpm系统调用:

times({tms_utime=0, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 1031139243 <0.000072>
 
poll([{fd=4, events=POLLIN}], 1, 5000)  = 1 ([{fd=4, revents=POLLIN}]) <0.000021>
 
read(4, "\1\1\0\1\0\10\0\0", 8)         = 8 <0.000018>
 
read(4, "\0\1\0\0\0\0\0\0", 8)          = 8 <0.000017>
 
read(4, "\1\4\0\1\2\276\2\0", 8)        = 8 <0.000017>
 
read(4, "\0177SCRIPT_FILENAME/home/shenguanpu/devspace/test_yaf/src/public/index.php\f\0QUERY_STRING\16\3REQUEST_METHODGET\f\0CONTENT_TYPE\16\0CONTENT_LENGTH\v\nSCRIPT_NAME/index.php\v\rREQUEST_URI/Main/example\f\27DOCUMENT_URI/index.php/Main/example\r-DOCUMENT_ROOT/home/shenguanpu/devspace/test_yaf/src/public\17\10SERVER_PROTOCOLHTTP/1.1\21\7GATEWAY_INTERFACECGI/1.1\17\fSERVER_SOFTWAREnginx/1.12.1\v\nREMOTE_ADDR10.20.1.19\v\5REMOTE_PORT35085\v\nSERVER_ADDR10.20.1.13\v\2SERVER_PORT80\v\34SERVER_NAMEshenguanpu.test_yaf.panda.tv\21\0HTTP_X_REQUEST_ID\17\3REDIRECT_STATUS200\t\rPATH_INFO/Main/example\17:PATH_TRANSLATED/home/shenguanpu/devspace/test_yaf/src/public/Main/example\t\34HTTP_HOSTshenguanpu.test_yaf.panda.tv\17\vHTTP_USER_AGENTcurl/7.44.0\v\3HTTP_ACCEPT*/*\0\0", 704) = 704 <0.000017>
 
read(4, "\1\4\0\1\0\0\0\0", 8)          = 8 <0.000034>
 
setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={30, 0}}, NULL) = 0 <0.000037>
 
rt_sigaction(SIGPROF, {0x798430, [PROF], SA_RESTORER|SA_RESTART, 0x3490c326a0}, {0x798430, [PROF], SA_RESTORER|SA_RESTART, 0x3490c326a0}, 8) = 0 <0.000027>
 
rt_sigprocmask(SIG_UNBLOCK, [PROF], NULL, 8) = 0 <0.000027>
 
getcwd("/home/shenguanpu/devspace/test_yaf", 4095) = 35 <0.000022>
 
chdir("/home/shenguanpu/devspace/test_yaf/src/public") = 0 <0.000049>
 
fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1, len=1}) = 0 <0.000030>
 
stat("/home/shenguanpu/devspace/test_yaf/src/public/index.php", {st_mode=S_IFREG|0775, st_size=221, ...}) = 0 <0.000025>
 
chdir("/home/shenguanpu/devspace/test_yaf") = 0 <0.000030>
 
times({tms_utime=0, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 1031139243 <0.000017>
 
setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0 <0.000017>
 
fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0 <0.000021>
 
write(4, "\1\6\0\1\0/\1\0Content-type: text/html; charset=UTF-8\r\n\r\ntest1\0\1\3\0\1\0\10\0\0\0\0\0\0\0\0\0\0", 72) = 72 <0.000086>
 
shutdown(4, SHUT_WR)                    = 0 <0.000023>
 
recvfrom(4, "\1\5\0\1\0\0\0\0", 8, 0, NULL, NULL) = 8 <0.000027>
 
recvfrom(4, "", 8, 0, NULL, NULL)       = 0 <0.000024>
 
close(4)                                = 0 <0.000069>
 
setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0 <0.000024>

yaf比php-fpm多了14次系统调用

stat("/home/shenguanpu/devspace/test_yaf/src/public/index.php", {st_mode=S_IFREG|0775, st_size=207, ...}) = 0 <0.000019>
(yaf内操作开始)
stat("/home/shenguanpu/devspace/test_yaf/src/conf/app.ini", {st_mode=S_IFREG|0775, st_size=364, ...}) = 0 <0.000030> 
open("/home/shenguanpu/devspace/test_yaf/src/conf/app.ini", O_RDONLY) = 5 <0.000023>
 
ioctl(5, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7ffd3bf1f2d0) = -1 ENOTTY (Inappropriate ioctl for device) <0.000016>
 
fstat(5, {st_mode=S_IFREG|0775, st_size=364, ...}) = 0 <0.000015>
 
mmap(NULL, 396, PROT_READ, MAP_PRIVATE, 5, 0) = 0x7f7d06803000 <0.000022>
 
fstat(5, {st_mode=S_IFREG|0775, st_size=364, ...}) = 0 <0.000015>
 
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7d06802000 <0.000017>
 
lseek(5, 0, SEEK_CUR)                   = 0 <0.000016>
 
munmap(0x7f7d06803000, 396)             = 0 <0.000022>
 
close(5)                                = 0 <0.000017>
 
munmap(0x7f7d06802000, 4096)            = 0 <0.000014>
 
stat("/home/shenguanpu/devspace/test_yaf/src/Bootstrap.php", {st_mode=S_IFREG|0775, st_size=2392, ...}) = 0 <0.000020>
 
stat("/home/shenguanpu/devspace/test_yaf/src/library/XLogKit.php", {st_mode=S_IFREG|0664, st_size=1933, ...}) = 0 <0.000024>
 
stat("/home/shenguanpu/devspace/test_yaf/src/controllers/Main.php", {st_mode=S_IFREG|0664, st_size=962, ...}) = 0 <0.000021>
(往下回到fpm)
chdir("/home/shenguanpu/devspace/test_yaf") = 0 <0.000022>

swoole+yaf

1、master
 
accept4(3, {sa_family=AF_INET, sin_port=htons(22468), sin_addr=inet_addr("10.20.1.19")}, [16], SOCK_CLOEXEC|SOCK_NONBLOCK) = 9 <0.000024>
 
epoll_ctl(8, EPOLL_CTL_ADD, 9, {EPOLLOUT, {u32=9, u64=9}}) = 0 <0.000019>
 
accept4(3, 0x7ffdf04ad430, [16], SOCK_CLOEXEC|SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable) <0.000019>
 
 
 
2、worker
 
read(4, "\3\0\0\0b\0\0\0\np\3\0GET /Main/example?test=1 HTTP/1.1\r\nHost: 10.20.1.13:9601\r\nUser-Agent: curl/7.44.0\r\nAccept: */*\r\n\r\n", 8192) = 110 <0.000029>
 
sendto(4, "\3\0\0\0\252\0\0\0\0\377\0\0HTTP/1.1 200 OK\r\nServer: swoole-http-server\r\nContent-Type: text/html\r\nConnection: keep-alive\r\nDate: Fri, 26 Jan 2018 08:17:44 GMT\r\nContent-Length: 17\r\n\r\nthis is a swoole!", 182, 0, NULL, 0) = 182 <0.000029>

参考文献:

php内核剖析

Linux系统调用列表

strace跟踪进程

fastcgi-protocol-specification

 
我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan

PHP码农在Golang压力下的生存之道-PHP性能优化实践的更多相关文章

  1. 码农也来关注下经济问题<美元加息>对我们的影响

    昨天凌晨三点,美联储宣布加息25个基点,这是今年美联储第四次加息,也是2015年12月份以来的第九次加息.基准利率又上调了25个基点,全球市场又要开始惴惴不安了. 要知道上一次美国基准利率上调25个基 ...

  2. 巧妇能为少米之炊(1)——Android下小内存下的生存之道

    常常听到身边用安卓的朋友抱怨手机卡顿,内存动不动就快没了.而Google声称在512M的内存下也能流畅执行Android 4.4.究竟它做了什么? 总结一下它主要做了四件事: 1.优化内核,使用Act ...

  3. Lazy<T>在Entity Framework中的性能优化实践(附源码)

    在使用EF的过程中,导航属性的lazy load机制,能够减少对数据库的不必要的访问.只有当你使用到导航属性的时候,才会访问数据库.但是这个只是对于单个实体而言,而不适用于显示列表数据的情况. 这篇文 ...

  4. windows党码农在linux下你最需要的软件列表TOP10

    NO 10.QQ 神奇的TX,经常更新接口,使得linux社区的模仿软件总是跟不上步伐,一整就不能登陆使用.可是老大,您怎么自从2009-01-04发布了第一版QQ for Linux 1.0 Bet ...

  5. Python码农福音,GitHub增加Python语言安全漏洞告警

    在 2017 年 GitHub 开始对托管在其网站的代码仓库和依赖库开始提供安全漏洞检查和告警,开始时候只支持 Ruby 和 JavaScript 语言的项目.根据 GitHub 官方数据显示截止目前 ...

  6. 【整理】待毕业.Net码农就业求职储备

    声明:本文题目来源于互联网,仅供即将从学校毕业的.Net码农(当然,我本人也是菜逼一个)学习之用.当然,学习了这些题目不一定会拿到offer,但是针对就业求职做些针对性的准备也是不错的.此外,除了技术 ...

  7. <开心一笑> 码农 黑客和2B程序员之间的区别

    笔记本电脑 码农: 黑客: 2B程序员: 求2的32次方: 码农: System.out.println(Math.pow(2, 32)); 黑客: System.out.println(1L< ...

  8. 经典算法C++版(参考一线码农博文)

    鉴于一线码农的算法博文基本通过C#完成,此处用C++再实现一遍,具体解法可参考其博文. 地址:http://www.cnblogs.com/huangxincheng/category/401959. ...

  9. 老码农教你在 StackOverflow 上谈笑风生

    作为一个高大上的码农,你肯定用到过 StackOverflow,必须的.会有人否定这个断言么?那他恐怕不是真正的码农,或者说还没入门.StackOverflow 对于码农的重要性,基本就和诸葛亮对刘备 ...

随机推荐

  1. AD转换

    一.AD转换的概念  AD转换的功能是把模拟量电压转换为数字量电压.DA转换的功能正好相反,就是讲数字量转换位模拟量. 二.芯片PCF8591介绍 PCF8591是一个单片集成.单独供电.低功耗.8- ...

  2. 将DedeCMS从子目录移动到根目录的方法

    http://www.commonie.com/a/chat/dedeskill/298.html 以前做了一个Wordpress的博客,后来觉得采用DedeCMS更好一点,所以就有了转向DedeCM ...

  3. php的底层原理

    PHP说简单,但是要精通也不是一件简单的事.我们除了会使用之外,还得知道它底层的工作原理. PHP是一种适用于web开发的动态语言.具体点说,就是一个用C语言实现包含大量组件的软件框架.更狭义点看,可 ...

  4. git只添加指定类型的文件的.gitignore规则

    #忽略根目录下的所有文件 * #忽略子目录下的所有文件 /* #包含目录 !*/ #指定不忽略的文件 !*.c !*.h #忽略根目录下的文件 /build/ /appveyor/ /pear/ /s ...

  5. SpringMVC之GET请求参数中文乱码

    server.xml 文件中的编码过滤器设置是针对POST请求的,tomacat对GET和POST请求处理方式是不同的,要处理针对GET请求的编码问题,则需要改tomcat,conf目录下的serve ...

  6. Android之MaterialDesign应用技术2-仿支付宝上滑搜索框缓慢消失

    PS:在这之前也就是上一篇介绍了MaterialDesign一些滑动删除.标题栏的悬浮效果等,如果没看过第一篇的小火鸡可以看一下,因为这篇是接着上一篇写的,有一些东西上一篇写过了这里就不在重复了(Ma ...

  7. maven系列--eclipse的m2插件

    工欲善其事,必先利其器.我是讨厌用CMD指令来操作maven,既然eclipse已经给我们提供了插件,那我们为什么不使用呢?而且我觉得eclipse的各种插件都挺好用的.好了废话不多说了,现在开始整理 ...

  8. JAVA中double类型运算结果异常的解决

    问题: 对两个double类型的值进行运算,有时会出现结果值异常的问题.比如: System.out.println(19.99+20); System.out.println(1.0-0.66); ...

  9. rpm命令的使用

    如果服务器配置了本地yum源,大部分的常用软件包都是有的.如果差了头文件或者什么so文件,可以按如下方式找:#以memory.h为例 [root@sz-cdn-centos7-1 tmp]# wher ...

  10. NodeJs实现他人项目实例

    1.简单实例,参考 https://github.com/alsotang/node-lessons/tree/master/lesson2 2.express一个新项目 ,但出现警告 发现少了nod ...