分类: LINUX
2014-12-23 19:02:23
一.综述
针对服务器网络性能进行优化的工作是极端复杂的,因为在这里有多个不确定因素影响的最终的性能,在这里并不是简单的1+1+2=4的问题,而很可能会是1+1+2=-1。粗略的说,影响性能因素的元素大致有:IOAT配置,网卡驱动版本,操作系统版本,主板芯片组-芯片型号/主板布线,应用程序架构...
本文尝试针对如此复杂的配置元素做一个概述。并尽力列举好东西互相打架(内战 or 竞选?)的情形。
二.各种相互影响的元素
0.IOAT概述
ioat很复杂,由一系列特性组成,包括IntMod,quick-data,rss,dca,offload,dmac,MSI-X等等,每一种特性都有其应用场合。注意到这些特性在多cpu环境中是相互影响的,如果不假思索的堆积这些特性,反而会弄巧成拙!首先先看一下这些特性的大致原理,详细原理需要参阅8257X以及Intel IOAT的datasheet:
0.1.interrupt moderation:
IntMod的原理很简单:
首先 Intmod有一个假设,它假设使用千兆网卡传输的都是密度很大的包,因此每到一个包都产生一个中断的话,会造成中断很频繁,严重情况下会有中断风暴,这类似于一次DOS;
其次, 鉴于之前的软件解决方式NAPI的效益有一个难以突破的上限,因此有必要提出一种硬件的解决方案,这就是IntMod;
再次, 由于还要兼顾不连续小包的情况,也即密度很小的小包接收的情况,因此IntMod不能做死,要可配置,并且最好支持自适应, 这就是最终的Intel千兆网卡8257X系列的IntMod版本。(详情参阅我的《 网卡性能分析-Intel8257X芯片手册读后感 》)
0.2.RSS:
多接收队列支持的核心。这个原理也很简单。在硬件层次将数据包分类,然而不进行任何策略化转发,它只是提供了一个接口,使得驱动程序等软件可以pick up出不同的队列中的数据包,然后由软件实施策略。以往的单一队列网卡,驱动程序和协议栈软件需要进行大量计算才能将数据包归并到某一个数据流(比如netfilter的ip_contrack的实现逻辑),现在使用RSS机制,驱动程序可以直接获取结果了。 我们常常认为,按照分层的思想,网卡硬件是不应该认识数据流的,它只应认识数据帧,然则Intel的千兆网卡突破了这个理论原则,这也是理论应用于工业的常用方法。
0.3.DMAC:
同上面的IntMod原则一样,dmac也是为了增加吞吐量,降低中断开销提出的方案,它将dma请求聚集到一定程度才真正请求一次,在降低dma中断次数的同时也减少了总线事务开销,然而可能因此增加了延迟。
0.4.DCA:
dma我们都很熟悉,dca原理一样,只不过是“直接cache访问”。PCIe网卡传输到内存的数据首先被cpu系统总线的snoop周期得到,如果cache命中则直接操作cache不再操作memory了。可是DCA在多cpu的情况下十分复杂,有时由于软件设计不当“cache一致性”的开销会抵消掉相当大一部分dca的性能提升。
0.5.OffLoad:
这个设计原则也是在底层增加了上层逻辑,虽然理论上很丑陋,然而这是工业界的做法,只为提高性能。offload将很多消耗cpu的操作分担给了网卡芯片,比如tcp分段,校验码计算,udp分段,小分段在接收时的重组...
0.6.MSI-X:
PCIe的特性之一。总之,MSI-X使用软件来分配中断号,这样就避免了由于硬件中断线有限而导致的中断同步化。准确地说,ioat中的msi-x特性可以为每一个rss接收队列都分配一个中断号,大大优化了中断性能和dma性能。
1.接收包是大包还是小包的问题
1.1.大包情况:
如果网卡接收的数据包普遍都是大包,并且是短间隔的连续接收,那么82575/6网卡的interrupt moderation将会带来很好的效果,从而避免了中断风暴。它设置一个阀值N,每秒只发送N个中断给cpu,在中断还没有发送期间,网卡可以积累一些数据包,当下一个中断发送的时候,集中由cpu处理。
1.2.小包情况:
如果网卡接收的数据包是小包,并且密度很小,那么使用阀值N将会严重加大包延迟,因此如果存在大量小包需要处理,关闭IntMod。这种情况发生在大量tcp用户连接的情况,如果关闭了IntMod,cpu利用率会上升,如果你的cpu不是很差,应该还是可以负担的,如果很差,那么就不配使用Intel的82574以上的网卡,换cpu和主板吧。
1.3.自适应
如果能将IntMod调节成自适应,理论上是很好的,也就是说当连续大包接收的时候,逐渐增加IntMod的阀值,如果接收密度变小,则相应减小之。然而如果大密度的大包中间夹杂着少量的小包,则大包的性能提升将会吃掉对小包的处理。因此这种自适应效果在这种大小不等的混合包环境下并不是很好。
实际上压根儿就不能指望网卡里面实现的硬件自适应能适应各种情况,毕竟硬件的灵活性远远不如软件。
1.4.LLI选项
幸运的是,82575/X网卡实现了一个“检波”机制,它实现了基于包特征来决定是否立即发送中断的机制。 不幸的是,并不是各个版本的驱动程序都支持这个配置。这又是硬件和软件不协调的例子。
2.转发和本地处理的问题
2.1.转发情况:
如果转发,且存在大于两块的网卡,则将所有中断固定在一个cpu上 ,或者根据路由表,将统计意义上的入口和出口的网卡中断绑在同一个cpu上 ,原因是dca在单个cpu上的效果最好(没有cache一致性开销),加之对于转发的数据包,数据包的处理全部在软中断中进行,和进程无关,因此最好将数据包从进入协议栈到出去协议栈的过程全部布在同一个cpu上。 但这不太适合运行动态路由协议的机器。这是因为DCA的效果完全取决于cache的使用方式。
RSS实际上在转发的情况下,最好不同队列中断不同cpu,并且注意数据包在本地的“card1-routing-card2”使用同一个cpu处理是最佳的。(附:两次可能的跳跃:1.硬件接收包到发送中断可能会在多cpu上跳跃;2.软中断处理完唤醒用户态进程可能存在进程在多cpu上跳跃,无论哪个跳跃都不好,都会部分抵消访问cache的收益 )
2.2.本地处理情况:
rss在本地处理的包上起的作用更大。但是前提是用户态应用程序一定要配合这种多cpu多接收队列的场景。如果固定的一个队列中断固定的一个cpu,但是应用程序却没有运行在这个cpu上,那么虽然协议栈的处理充分使用了dca,最终将数据送达用户态的时候,可能还会导致cache刷新(如果进程被唤醒在该处理协议栈软中断的cpu上,则谢天谢地,遗憾的是,我们对是否如此无能为力,一切希望要寄托在linux内核进程调度器的开发者们身上),反之,如果我们将一个进程绑定在一个固定的cpu上,那么多个队列中断固定的多个cpu就没有了意义,因此最好巧妙设计多线程程序,每一个cpu上运行一个,每一个线程处理固定一个或者几个队列。然而这很难,毕竟rss机制只是保证了同一个流hash到一个queue,然则并没有任何配置指示它必须hash到哪个队列,hash的结果和源地址/源端口/目的地址/目的端口/算法有关。
2.3.lro问题:
转发的情况下,如果出口的mtu过小,然而入口启用了lro,那么重组后的大包在发出之前会被重新分段。因此对于转发的情况,要禁用掉lro。对于本地处理的情况,启用lro无疑是不二的选择。然而如果一半是本地处理,一半是routing的,那该怎么办呢?实际上毫无办法!
3.多cpu和多队列的问题
3.1.操作系统级别的中断的负载均衡
转发:
多个cpu均衡处理每一个接收队列,对于tcp的情况,会造成tcp段的并行化,在tcp协议层面上会产生重组和重传开销,特别是tcp端点不支持sack的情况下。如果没有开启lro,对于大量大包的tcp分段,情况会更加严重,虽然tcp的mss测量会减轻这种影响,但是终究无法弥补!
本地处理:
如果是发往本地用户态进程的包,即使中断在多cpu上负载均衡了,dca也会有效,但是由于无法确定进程将会在哪个cpu上被唤醒,因此对于到达同一进程的数据包而言,中断的负载均衡将还是会导致cache刷新
3.2.一个结论: 在最保险的情况下,退回到单cpu模式(通过grub的kernel参数maxcpus=1),ioat的性能会体现出来,如果开启了多个cpu,有时候性能反而下降!
3.3.多个流和单个流
如果服务器处理多个流,ioat的作用将更好的发挥,反之如只处理一个流,性能反而会因ioat而下降。特别是IntMod的影响尤大,IntMod本来是为增加吞吐量而引入的特性,它在增加总吞吐量的同时也会增加单个流的延迟。对于只有一个流的环境而言,增大了单包的延迟,也就意味着单流吞吐量的降低。
4.使用bonding进行流过滤
4.1.bonding驱动可以在软件层面上实现队列分组, 前提是使用bonding的负载均衡模式且使用layer2+3 xmit_hash_policy(与之相连的switch同时也需要相应的配置,使之支持和本机相同的均衡模式),有了软件的这种准备,网卡硬件将会更好的处理多队列,因为接收包都是从switch发来的,而switch已经有了和本机一致的xmit_hash_policy策略,这样8257X接收到的数据包就已经被switch过滤过了。虽然将多个8257X绑在一起同时配置xmit_hash_policy策略看起来有些复杂,然而这种过滤方式无疑使8257x的RSS更加确定化了,我们说,如果硬件无法完成复杂策略的设置,那么软件来帮忙! 在RSS中,硬件的策略确实很粗糙,它只管到接收队列对应的中断,而无法和应用程序发生关联,使用了bonding之后,这种关联可以通过软件来确立,需要修改的仅仅是为bonding增加一个ioctl参数或者sysfs属性。
5.驱动程序版本问题
并不是说硬件有什么特性,软件马上就能实现其驱动,如果真的是这样,驱动程序就不需要有版本号了。另外,选择驱动的时候,一定要知道自己网卡的型号,这个比较容易把人弄晕,我们所说的8257X指的是网卡控制器的型号而非网卡芯片的型号,这些控制器可以独立存在,也可以集成在主板的各个“桥”中。而网卡芯片(adapter)的型号则诸如以下的描述:PRO/1000 PT Dual Port。往往一个控制器可以支持很多的网卡芯片。
5.1.e1000驱动
见5.2
5.2.e1000e驱动
和e1000一样,e1000e驱动没有集成IOAT的大多数特性,除了一些offload特性。这说明如果想使用ioat特性以提高性能,这几个驱动明显是不行的,不过,e1000系列驱动实现了NAPI,这是一种软件方案来平衡cpu和网卡芯片的利用率。另外这些驱动实现以太网卡的全部传统特性,比如速率协商方式,双工方式等等。
需要注意的是,驱动和网卡芯片是对应的,然而并不是一一对应的,一个驱动往往对应一系列的芯片,总的来讲e1000对应的基于PCI和PCI-X的千兆卡,而e1000e对应的是支持PCIe的千兆卡
5.3.igb-1.2驱动
igb系列网卡驱动对应的是82575/82576等千兆卡,当然这些卡支持了IOAT的大部分特性。1.2版本的igb驱动支持以下的配置:
a.InterruptThrottleRate: 定义了Intmod的行为,可选值为:0,1,3,100-100000。如果是0,那么和传统网卡一样,收到包后立即中断cpu,如果是1或者3,那么就是自适应模式,网卡会根据包的接收情况自动调整中断频率,然而正如我上面的分析,自适应模式有时工作的并不是很好。如果是100以上的一个数,那么中断频率将设置成那个值。
b.LLIPort : 此配置和下面的两个配置都是LLI的配置,可以执行手动“检波”而并不使用IntMod的自适应机制。此配置可以配置一个端口,到达该端口的包可以马上中断cpu。
c.LLIPush: 使能或者禁用,使能的话,所有小包到来,马上中断cpu,然而文档中并没有说明其判断的标准和使用的算法。
d.LLISize: 配置一个大小,所有小于这个大小的包到来,立即中断cpu。
e.IntMode: 注意和IntMod的区别,该选项仅仅是告知驱动如何来配置中断的模式,具体来说无非就是基于中断线的中断,还有MSI以及MSI-X的中断。
f.LROAggr: 配置一个阀值,大于该值将不再实施lro。
虽然82575/6实现了RSS,这个1.2版本的驱动却没有体现其配置,因此有必要升级驱动,毕竟RSS是IOAT的重头戏。
5.4.igb-3.0驱动
该版本的驱动是比较新的,除了支持1.2版本的配置之外,还支持以下配置(暂不考虑虚拟化和NUMA的特性):
a.RSS: 配置多接收队列,可选值为0-8,默认为1,如果是0,则动态配置队列数量,数量选择最大队列和cpu数量的最小值,若非0,则配置成那个值。
b.QueuePairs: 配置在接收和发送方面是否使用同一个中断。
c.DMAC: 配置是否启用聚集dma。
6.Linux内核版本问题
对于内核相关的问题,有两点需要注意,其一是多路IO模型(多路IO模型对于routing的包没有影响,只影响本地处理的包),其二是本地唤醒。
6.1.Linux 2.4内核
2.4内核没有epoll,只能使用select模型,或者打上epoll补丁,使用select模型在大量连接的时候会有相当大的开销耗费在select本身上,然而8257X千兆卡的优势正是处理大量连接 ,因此最好升级内核到2.6
6.2.Linux 2.6.23之前的内核
在多cpu情况下,这些版本的内核在唤醒进程p的时候,如果需要在本cpu上唤醒,则将所有工作全部交给调度器来完成,如果调度器并不认为下一个运行的进程是p,那么p仍然需要等待,等待会导致cache变凉!我们知道8257X千兆卡使用了dca直接操作cache,并且可以巧妙的设置中断和cpu的亲和性,保证从中断到协议栈的传输层处理都在一个cpu上进行,如果唤醒等待数据的进程p后,p仍然在该cpu上运行并且马上,那无疑对cache的利用率是最高的。然而无法保证内核调度器一定会这样做。因此如果该内核作为网关产品的一部分只运行网络应用的话,最好修改一下内核调度器代码,添加一个本地唤醒内核API。也即在data_ready回调函数中提升要唤醒进程的动态优先级,并且强制唤醒在本cpu,然后调用resched_task。
6.3.Linux 2.6.23之后2.6.30之前的内核
这些版本的内核调度器使用了CFS算法,对于本地唤醒而言要好很多。
6.4.Linux 2.6.30之后2.6.33之前的内核
优化了网络协议栈和cfs算法。然而还是可以有针对网络性能优化的定制。Linux主线内核只是一个通用的内核,完全可以通过定制从而使其在某个子领域性能达到最优化。
6.5.Linux 2.6.33之后的内核
新版本的内核源码树中集成了Intel I/OAT的驱动
7.cpu,网卡,芯片组的协调
7.1.PCI和PCI-X
采用这种总线的网卡是比较old的,我们知道,pci总线分为不同的标准,比如33-MHz,66-MHz,133-MHz,100-MHz等,当我们看到网卡是133M的,而pci桥则是33M的,那么很明显,我们需要换主板了。这个速率在pci设备的配置空间中,通过lspci -vv可以看到:
Status: Cap+ 66MHz-...
7.2.PCIe
对于PCIe而言,由于它是串行的,不再以速率作为标准了,而是以lane作为标准,常见的有x4,x8,x16等,什么是lang呢?lang实际上就是一对4条的线缆,分别发送和接收,其中发送两根,接收两根,这两个线缆信号是差分的,因此可以抗干扰。如果我们发现网卡是x16的,而它的上游桥是x4的,那么很显然,我们找到了瓶颈。虽然一般不会出现这种情况,但是还是确认一下好,通过lspci -vv可以看到lane信息:
LnkCap: Port #3, Speed 2.5GT/s, Width x4,...
然则如何得到主板pci/pcie设备的拓扑图呢?又是怎么知道谁是网卡/网卡控制器的上游呢?见附录。
7.3.多核cpu和多cpu以及超线程cpu
不要把多核cpu,超线程cpu和多cpu混淆, 在cache的利用上,三种架构的表现完全不同,这涉及到有没有共享cache的问题。另外超线程cpu有时候确实会降低而不是提高性能,因为超线程cpu本质上只有一套计算资源,切换开销将可能影响性能,比如,有的操作系统的调度器会将一个进程唤醒到同cpu的另一个超线程上,这就要进行一次切换。Linux内核对此进行了一些处理最大限度避免这种情况,详见调度器的dependent_sleeper函数,然而不能依赖这种处理,并不是每个版本的内核都能有效避免超线程抢占带来的切换问题的。因此最好还是使用多核cpu或者多cpu,并且在绑定用户进程的时候,最好使共享数据的进程绑在一个cpu的多个核上,这样它们就可以使用共享的cache了(当然前提是你要知道你的cpu是怎样布局cache的)。
超线程cpu在线程密集的情况下,性能反而会下降,比关闭超线程的性能还低下,原因在于大量的开销用于同cpu的smt之间的切换了!
7.4.cpu利用率和ioat
ioat中的offload,IntMod,dmac都可以降低cpu利用率,将计算移入网卡芯片,如果你发现网络一路畅通,cpu利用率很低,那很有可能是过度使用这些ioat特性了,既然不是cpu的瓶颈,网卡又很强悍,网络上又没有瓶颈,那一定是没有配置好软件环境。
7.5.芯片组,cpu,网卡控制器芯片,网卡芯片,内存控制器,内存大小,操作系统,网线质量只有“阻抗匹配”才能获得好的性能, 否则任何一个环节都可能成为瓶颈,甚至降低性能,即1+1=-1。
三.建议配置
A:官方建议配置
肯定是Intel的官方建议是最好的了。Intel实际上提供的是一整套解决方案,如果我们全部买它们的硬件,部署它们的软件工具,并请Intel的工程师现场安装调试,那无疑能获得最佳的性能,如果某人通过自己的尝试超越了并且大大超越了这一官方最佳性能,那这个人最终的结果很可能会被Intel挖走...
然而上述的情况未免太过理想,要耗费很多资金,再者,Intel也不是将垄断做到如此决绝的地步,实际上在Intel的网站上还是能找到很多的性能相关的文档的。以下几点就是Intel官方给出的建议以及须知:
1.Reduce Interrupt Moderation Rate to Low, Minimal, or Off
Note Decreasing Interrupt Moderation Rate will increase CPU utilization.
建议:ethtool -C eth1 rx-usecs 0 |设置igb参数:InterruptThrottleRate=1,1,1,1
2.Enable Jumbo Frames to the largest size supported across the network (4KB, 9KB,or 16KB)
Note Enable Jumbo Frames only if devices across the network support them and are configured to use the same frame size.
3.Disable Flow Control.
Note Disabling Flow Control may result in dropped frames.
4.Increase the Transmit Descriptors buffer size.
Note Increasing Transmit Descriptors will increase system memory usage.
5.Increase the Receive Descriptors buffer size.
Note Increasing Receive Descriptors will increase system memory usage.
6.Tune the TCP window size
Note Optimizing your TCP window size can be complicated as every network is different. There are many documents that are available on the Internet that describe the considerations and formulas that you should use in determining your window size. Using your preferred search engine you can find a significant amount of information on TCP window size tuning by searching for "TCP tuning".
7.停止系统的中断的负载均衡,手工配置基于RSS的中断
8.最保险的选择:Boot the System Into Single-CPU Mode
Note all the advantages of multi-core systems are eliminated.
建议:配置kernel启动参数:maxcpus=1.
B:其它配置
很明显,系统内核的网络参数是一定要调整的,问题是调整到哪个值,这完全取决于你的系统。
1.sysctl调整网络参数
2.别让bonding扯了后腿
bonding是好的,bonding+千兆卡能带来性能最优化,然而别忘了,另一端还有switch,扯后腿的可能就是switch。
3.IntMod配置成一个超过100的数字,并且设置LLI实行手动“检波”。
四.结论
充分利用所有的芯片性能的前提是重新组合配置你的软件环境,这是一个很复杂的工作。这是一个求拥有N(N>100)个自变量的函数f(x1,x2,...xN)的极值问题,并且我们还不明确知道函数体是什么,只能根据上述的论述给出个大概。采用向量分析的方式是徒劳的,因为这些自变量不是正交的,相反,它们是互相影响的,最后也只能是通过采集性能数据的方式找出一个满意的结果了。
总而言之,IOAT是个好东西,然而却不能随意堆积。
附录:
一.获取主板的pci拓扑
0.你要理解sysfs,并且相信sysfs
1.安装tree命令
2.执行tree /sys/devices/pci0000/:00/ > ~/pci-topology
3.执行lspci -vvv > ~/pci-dev
4.分析~/pci-topology和~/pci-dev
一个实例:
执行2后的文件片断如下(跟windows注册表确实很像,但注册表的核心是uuid,而sysfs的核心是kobject):
...
-- 0000:00:02.0
| |-- 0000:00:02.0:pcie01
| | |-- power
| | | `-- wakeup
| | |-- subsystem -> ../../../../bus/pci_express
| | `-- uevent
| |-- 0000:00:02.0:pcie02
| | |-- power
| | | `-- wakeup
| | |-- subsystem -> ../../../../bus/pci_express
| | `-- uevent
| |-- 0000:01:00.0
| | |-- broken_parity_status
| | |-- class
| | |-- config
| | |-- device
| | |-- driver -> ../../../../bus/pci/drivers/e1000e
| | |-- enable
| | |-- irq
| | |-- local_cpulist
| | |-- local_cpus
| | |-- modalias
| | |-- msi_bus
| | |-- net
| | | `-- eth0
| | | |-- address
| | | |-- addr_len
| | | |-- broadcast
| | | |-- carrier
| | | |-- device -> ../../../0000:01:00.0
| | | |-- dev_id
| | | |-- dormant
| | | |-- duplex
| | | |-- features
| | | |-- flags
| | | |-- ifalias
| | | |-- ifindex
| | | |-- iflink
| | | |-- link_mode
| | | |-- mtu
| | | |-- operstate
| | | |-- power
| | | | `-- wakeup
| | | |-- speed
| | | |-- statistics
| | | | |-- collisions
| | | | |-- multicast
| | | | |-- rx_bytes
| | | | |-- rx_compressed
| | | | |-- rx_crc_errors
| | | | |-- rx_dropped
| | | | |-- rx_errors
| | | | |-- rx_fifo_errors
| | | | |-- rx_frame_errors
| | | | |-- rx_length_errors
| | | | |-- rx_missed_errors
| | | | |-- rx_over_errors
| | | | |-- rx_packets
| | | | |-- tx_aborted_errors
| | | | |-- tx_bytes
| | | | |-- tx_carrier_errors
| | | | |-- tx_compressed
| | | | |-- tx_dropped
| | | | |-- tx_errors
| | | | |-- tx_fifo_errors
| | | | |-- tx_heartbeat_errors
| | | | |-- tx_packets
| | | | `-- tx_window_errors
| | | |-- subsystem -> ../../../../../../class/net
| | | |-- tx_queue_len
| | | |-- type
| | | `-- uevent
...
然后在~/pci-dev中寻找00:02.0(桥)以及01:00.0(网卡),注意,pcie的信息包含在pci节点中,可以看出,sysfs几乎列出了你想要的所有信息,very good。
二.文档
1.大量调优建议以及技术白皮书
2.各类网卡/控制器芯片的说明:
注意:Select a Family->Network Connectivity|Select a Line->Intel@Server Adapters|Select a Product->[你的芯片]即可获得大量文档和更多的链接。
3.各类网卡控制器芯片文档
注意:和2一样,选择你需要的。
4.所有网卡资料总的入口
5.I/OAT资料
6.Linux内核更新文档以及大量链接
注意:版本号跟在最后,比如需要2.6.39的资料,URL最后为Linux_2_6_39
7.Linux内核开发文章
8.内核官方网站
9.各种网卡驱动程序的README文件
10.《Improving Measured Latency in Linux for Intel 82575/82576 or 82598/82599 Ethernet Controllers》链接丢失
11.《Assigning Interrupts to Processor Cores using an Intel 82575/82576 or 82598/82599 Ethernet Controller》链接丢失
12.各种网卡芯片的datasheet,链接在4中可以找到
13.Cisco技术支持,包含大量网络设计的文档
14.Cisco的blog,内容就不用说了,经常看
http://blogs.cisco.com/
15.《The Transition to 10 Gigabit Ethernet Unified Networking: Cisco and Intel Lead the Way》名副其实的!
16.《User Guides for Intel Ethernet Adapters》链接在Intel官网