内容简介:做优化的思路1、了解php语言特性2、了解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使用单核
<!– p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; background-color: #cff5d6} span.s1 {font-variant-ligatures: no-common-ligatures} –>
/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=128 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=400000 opcache.revalidate_freq=600 opcache.validate_timestamps=0 opcache.fast_shutdown=1 opcache.enable_cli=1 opcache.enable=1 [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即可完成,很简洁。
结论
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>
参考文献:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Programming in Haskell
Graham Hutton / Cambridge University Press / 2007-1-18 / GBP 34.99
Haskell is one of the leading languages for teaching functional programming, enabling students to write simpler and cleaner code, and to learn how to structure and reason about programs. This introduc......一起来看看 《Programming in Haskell》 这本书的介绍吧!