软件性能调优:看数据,还是谈概念?

上周写了「想让服务器跑得快,并不是换个编程语言那么简单」,很多朋友的留言歪了楼:论性能,C语言甩Python数倍到数十倍,你说和编程语言没关?拜托,程序君只是说,不是换个编程语言那么简单。这种留言,我都是建议看了原文再说。还有朋友留言说她转到朋友圈里炸了锅,大家众口一词:不谈zero copy,谈什么服务器性能!优化系统调用有个毛用!

对此,我只能说,buzz word荼毒太深,以至于大家陷进去,遇到问题,先摆个伟光正的大道理出来。我想,很多开口闭口zero copy的主,也许连zero copy是何物都没搞清楚。

我们看wikipeidia上的定义:

"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another. This is frequently used to save CPU cycles and memory bandwidth when transmitting a file over a network.

很清楚,zero-copy是当数据需要从内存的A点拷到B的时候,CPU不参与这个过程,也就是不通过调用 memcpy(),实现内存拷贝。这个过程一般是device driver来完成,通过DMA(direct memory access)绕过CPU在外设(一般是NIC,也就是网卡)和主存中做乾坤大挪移。放一张图:

至于DMA是怎么工作的,怎么发起,怎么通知CPU,cache coherence怎么处理,这就不是本文要讲的内容。

当然,这是狭义的zero copy,一般更广义的zero copy还包括在DMA结束后的整个数据的生存周期里,数据尽可能地少拷贝(绝对不是不拷贝哦)。

我们看Kernel如何收包(简化版):

  • 驱动初始化时,为NIC分配ring buffer。
  • NIC收到报文后,找到下一个空闲的buffer,将其DMA到buffer指定的内存里,同时发送interrupt,告知Kernel。(第一次zero copy)
  • Kernel处理这个interrupt,将这个buffer据为己有做后续处理,然后新分配一个buffer,还给driver。(第二次zero copy)。
  • 接下来交给TCP stack处理(因为咱们讲的是服务器开发,一般是TCP服务器)。TCP是个stream based protocol,在这里,报文的排序,组包(把TCP头去掉,payload连接起来供application使用)等等,根据实现的不同,也许需要copy至少一次。另外,如果自己作为发送端,由于需要考虑潜在的retransmit,一般也会copy一份到retransmit queue里。(网络设备专门优化不在此讨论范围之内)
  • 之后交给application处理,这涉及到kernel space到user space的切换。将application的buffer和kernel的buffer映射起来不是一件简单的事情,所以这里一般也会有一次copy。

当然,现在有跳过kernel的stack,从NIC直接DMA到user space的技术,但那一般是网络厂商干的勾当。我觉得咱们做一个服务器软件,还是不要抢人家TCP stack的生意,否则你会把自己玩死。

基本上,这些动作都发生在application无法控制的kernel里。好处是:当你 recv() 的时候,你拿到了一个只包含有 TCP payload 的 重组好的,可以线性阅读的buffer。你只需要关心application level的逻辑。你能控制的,也只有之后,尽可能少的copy这个buffer。嗯,我们兜了这么一大圈,终于回到之前的问题:不谈zero copy,谈什么服务器性能,谈什么优化系统调用!

无奈的是,我们只能控制application level的zero copy。

假设我们写了一个 HTTP server。大多数HTTP请求头都不会太大,除非是post一个文件,或者应用服务器设置了巨大的cookie。我们假设平均而言整个请求大小是4k(这个假设没有价值,只是为了比对),而我们的 server 稍稍2b一些,除了正常处理中的copy之外,还会把整个请求 memmcpy() 一遍。为公平起见,我们不和那些占用时间比较长的系统调用比较,就和 gettimeofday() 比一比。主体代码如下:

(顺手写下的两行代码,如有纰漏,敬请原谅)

带着gprof编译运行,然后查看看结果:

(注意:这个结果你最好自己写代码测一下,不同的环境可能不一样,我用的是digital ocean最小的instance,ubuntu 14.04)

哟,貌似 memcpy() 没有想象的那么慢嘛,同样10million调用次数,比 gettimeofday() 慢一点点而已。

那问题来了,你是费尽心思去优化散落在各处小小的,基本上不可避免的copy呢,还是1s调用一次 gettimeofday(),而不是来一个包就调用一次,省却99.99%的调用呢?

你是会把发送response时分别两次发送header和body的两个 write() 合并成一个,来减少几十ms级的网络round trip的延迟,还是费尽心思去优化散落的copy呢?

你是会通过使用 stracegprof,以及 systemtap 等各种工具,追溯到真正性能所在的瓶颈,然后对症下药,还是不假思索地跳将出来:一切不谈zero-copy而论performance的服务器软件都是耍流氓!

当你看不起系统调用带来的损耗时,你是否又知道,当你苦苦追寻zero-copy的时候,kernel已经尽力在提供各种扩充的系统调用来尽可能让某些应用场景快起来?比如 sendfile()?如果你的response是个静态文件,你可以通过这个系统调用轻松实现zero-copy?

写这么些,不是证明我有多对,我的知识也有可能是错的。只是当我们遇到问题的时候,是真正测量还是人云亦云,吐几个buzz word就自认为解决问题了呢?

至少,我写上一篇文章的时候,我还拿strace亲测了一下nginx在首次访问和再次访问同一URI下系统调用的不同呢?

不管你怎么看待题图中的人物,但我喜欢:「我不是为了输赢,我只是认真」这句话。performance是认认真真不断测量和调整打造出来的,不是拍脑门想出来的。

原文发布于微信公众号 - 程序人生(programmer_life)

原文发表时间:2015-07-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏牛客网

Java实习总结网易百度小米美团阿里(均offer)

1033
来自专栏pangguoming

高性能Web服务端 PHP vs Node.js vs Nginx-Lua 的对比分析

1. ngx_lua nodejs php 比较 我在研究一阵子ngx_lua之后发现lua语法和js真的很像,同时ngx_lua模型也是单线程的异步的事件驱动...

5375
来自专栏大宽宽的碎碎念

如何深入理解开源项目从小代码集看起聚焦请先看文档关注资源的生命周期找一个好工具建立调试环境看代码很累,要坚持

3116
来自专栏jessetalks

初探领域驱动设计(1)为复杂业务而生

概述   领域驱动设计也就是3D(Domain-Driven Design)已经有了10年的历史,我相信很多人或多或少都听说过这个名词,但是有多少人真正懂得如何...

3096
来自专栏ThoughtWorks

给Java程序员的Angular快速指南 | 洞见

Spring + Angular 的全栈式开发,生产力高、入门难度低(此处省略一万字),是 Java 程序员扩展技术栈的上佳选择。

654
来自专栏数据和云

理论实践:循序渐进理解AWR细致入微分析性能报告

黄凯耀 (Kaya) ACOUG核心会员,高级技术专家 曾经工作于Oracle Real World Database Performance Group,一...

2798
来自专栏更流畅、简洁的软件开发方式

OO——从不知到知道一点,从迷茫到豁然开朗 (迟来的我的2002到2007)

前两天写了一个 “使用了继承、多态还有工厂模式和反射,但是还是没有OO的感觉。  ”,看到了很多同学的回复,自己又反思了几次,终于有所感悟,写下来做个记录。 ...

1977
来自专栏前端黑板报

HTTP2基础教程-读书笔记(一)

《HTTP/2基础教程》到手之后,用了两天时间把它粗略的看完了,大致知道了HTTP/2中的一些概念以及诞生的背景,现在再重读一遍同时做一些笔记。此书适合作为自己...

3307
来自专栏java进阶架构师

dubbo源码解析-远程暴露

本篇讲的是dubbo中比较重要的远程暴露,鉴于上一篇dubbo源码解析-本地暴露采用一图胜千言的写法好像读者并不太容易理解,加上之前写的别怕看源码,一张图搞定M...

731
来自专栏张善友的专栏

MS MVC框架漩涡中的MonoRail未来

上个星期,Hamilton向微软MVC团队通报了Castle团队从现实应用中获得的所有复杂和不直观的需求,并告知他们如何处理这些事情。另外他还开发了一些集成案例...

1745

扫描关注云+社区