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

上周写了「想让服务器跑得快,并不是换个编程语言那么简单」,很多朋友的留言歪了楼:论性能,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 条评论
登录 后参与评论

相关文章

来自专栏媒矿工厂

编码,打包,CDN交付和视频播放器端的延迟优化

在本系列上一篇文章《定义和测量延迟》中,介绍了为什么延迟是OTT传输的一个问题以及如何测量端到端延迟中不同传输步骤所占的延迟比重。 本文接下来介绍可能的延迟优化...

1024
来自专栏Java技术栈

最新后端架构师技术图谱!附学习资料~

2122
来自专栏ImportSource

Java9里将会出现的5个新功能

1. Java + REPL = jshell 下一个版本的Java将使用一个名为jshell的新命令行工具,它将添加本机支持(native support)并...

3276
来自专栏pangguoming

Netty和Tomcat的区别、性能对比

Netty和Tomcat最大的区别就在于通信协议,Tomcat是基于Http协议的,他的实质是一个基于http协议的web容器,但是Netty不一样,他能通过编...

1123
来自专栏斑斓

软件系统的稳定性

软件系统的稳定性,主要决定于整体的系统架构设计,然而也不可忽略编程的细节,正所谓“千里之堤,溃于蚁穴”,一旦考虑不周,看似无关紧要的代码片段可能会带来整体软件系...

3476
来自专栏Java架构师学习

2017年终巨献阿里、腾讯最新Java程序员面试题,准备好进BAT了吗

Java基础 进程和线程的区别; Java的并发、多线程、线程模型; 什么是线程池,如何使用? 数据一致性如何保证;Synchronized关键字,类锁,方法锁...

3345
来自专栏企鹅号快讯

计算机高频知识点详解

重磅!福建农信社招863人,大专/往届均可报考! 福建农信社报考须知:我是笔试类还是免笔试类? 紧急通知 | 农信社笔试内容大变动 01 计算机中的信息表示 1...

1785
来自专栏无题

秒杀系统解决方案

从架构、产品、前端、后端四个层面针对秒杀场景(可以扩展到所有高并发场景)分别总结了一些解决方案。 要点总结: 1.架构:扩容,业务分离,数据分离 2.产品:下...

3284
来自专栏高性能服务器开发

9 百万用户级游戏服务器架构设计

所谓服务器结构,也就是如何将服务器各部分合理地安排,以实现最初的功能需求。所以,结构本无所谓正确与错误;当然,优秀的结构更有助于系统的搭建,对系统的可扩展性及可...

1284
来自专栏xdecode

后端架构师技术图谱

转自: GitHub/architect-awesome , 大体结构如下(更新时间: 2018-06-22)

2432

扫码关注云+社区