注:本文是参照了一些其他文章,原文地址点击这里。
首先根据这篇文章进行了性能瓶颈的分析
策略与方法
首先根据木桶原理,首先要找到最弱的地方,怎么找往上看↑。
想能优化需要考虑如下:
- 优化BIOS设置
- 有效的分配NUMA资源
- 优化Linux配置
- 运行l3-fwd程序验证以上的配置,和公布的性能对比
- 运行micro-benchmarks选出最佳的更性能组件(比如bulk enqueue/bulk dequeue相对于single enqueue/single dequeue)
- 从dpdk的例子中找一个接近于你想要写的程序,按照上面的默认配置来(比如TX buffers资源比RX多)
- 适配和更新应用程序,使用正确的优化标志编译
- 配置你选择的应用程序,以便有一个比较的基础
- 运行优化过的命令行参数
- 怎样为架构匹配更好的应用和算法?运行分析找到内存绑定?I/O绑定?CPU绑定?
- 应用相应的解决方案,软件内存预取,IO的阻塞模式,CPU绑定超线程或者不
- 重新运行分析,找出是前端pipeline影响还是后端pipeline影响
- 应用正确的解决方案。编写更加有效的代码-分支预测(likely),循环展开,编译优化等等
- 没有得到期望的性能,回到上面运行优化过的命令行参数,再来一次
- 记录容易记住的方法,分享到dpdk官网
优化BIOS设置
NUMA | ENABLED |
---|---|
Enhanced Intel® SpeedStep® technology | DISABLED |
Processor C3 | DISABLED |
Processor C6 | DISABLED |
Hyper-Threading | ENABLED |
Intel® Virtualization Technology for Directed I/O | DISABLED |
MLC Streamer | ENABLED |
MLC Spatial Prefetcher | ENABLED |
DCU Data Prefetcher | ENABLED |
DCU Instruction Prefetcher | ENABLED |
CPU Power and Performance Policy | Performance |
Memory Power Optimization | Performance Optimized |
Memory RAS and Performance Configuration -> NUMA Optimized | ENABLED |
请注意,如果要使用DPDK电源管理功能,必须启用EnhancedIntel®SpeedStep®技术。 此外,应启用C3和C6。
平台优化
平台优化包括配置内存和I/O(NIC卡)以利用affinity实现更低的延迟。
NUMA & Memory Controller
举个多socket插座的例子, 对于在CPU0上运行的线程,socket0访问local memory延迟较低,通过QPI(Intel® QuickPath Interconnect)访问remote memory延迟较高,尽量避免。
问题:
在BIOS中NUMA设置为DISABLED会发生神马?内存控制器通过socket交叉访问。例如,如下所示,CPU0正在读取256个字节(4个高速缓存行)。 由于BIOS NUMA设置为DISABLED状态,因为内存控制器交叉存取在256个字节之内的访问,从本地存储器读取128字节,从远程存储器读取128字节。远程内存访问最终跨越QPI链路。这样做的影响是访问远程内存的访问时间增加,从而导致性能降低。
解决方案:
如下图所示,BIOS设置NUMA = Enabled,所有访问都进入相同的socket(local)内存,没有QPI交叉。存储器访问的延迟较,低性能提高。
PCIe* Layout and IOU Affinity
Linux优化
使用isolcpus减少上下文切换
为了减少上下文切换的可能性,需要提示内核,禁止将其他用户空间任务调度到DPDK应用线程所在核。isolcpus Linux内核参数用于此目的。
例如,如果DPDK应用程序要在逻辑核心1,2和3上运行,则应将以下内容添加到内核参数列表中:
isolcpus = 1,2,3
注意:即使使用isolcpus提示,调度程序仍可以在隔离的核心上调度内核线程。请注意,isolcpus需要重新启动。
适配和更新应用程序
现在已经将相关示例应用程序标识为构建最终产品的起点,以下是要回答的下一组问题。
如何配置应用程序以获得最佳性能?
- 每个端口可以配置多少个队列?
- Tx资源可以分配为与Rx资源相同的大小吗?
- 阈值的最佳设置是什么?
建议:好消息是,示例应用程序不仅具有优化的代码流,而且优化的参数设置为默认值。建议在Tx和Rx的资源之间使用类似的比例。以下是Intel® Ethernet Controller 10 Gigabit 82599的参考和建议。对于其他NIC控制器,请参阅相应的数据手册。
每个端口可以配置多少个队列?
有关此主题的详细测试设置和配置,请参阅白皮书评估软件路由器的服务器网卡的适用性。
下面的图(从上面的白皮书)指示每个端口不使用多于2到4个队列,因为性能随着更高数量的队列而降低。
对于最佳情况,建议每个端口使用1个队列。 如果需要更多,可以考虑每个端口2个队列,但不能超过2个。
Tx资源可以分配为与Rx资源相同的大小吗?
为Tx和Rx分配相等大小的资源是一种自然的趋势。 然而,请注意,http://dpdk.org/browse/dpdk/tree/examples/l3fwd/main.c显示,Tx ring描述符的数量的最佳默认大小是512,而Rx ring描述符是128.因此,Tx ring描述符的数量是Rx ring描述符的4倍。
建议选择Tx ring描述符是Rx ring描述符的4倍,而不是让它们具有相等的大小。
阈值的最佳设置是什么?
例如,http://dpdk.org/browse/dpdk/tree/app/test/test_pmd_perf.c具有以下针对Intel Ethernet Controller 10 Gigabit 82599的优化默认参数。
更详细的说明,请参阅Intel Ethernet Controller 10 Gigabit 82599 data sheet。
Rx_Free_Thresh - 快速总结和关键点:PCIe处理(更新硬件寄存器)的成本可以通过处理批量的数据包(在更新硬件寄存器之前)摊销。
Rx_Free_Thresh - 详细:如下所示,由硬件接收的报文使用报文描述符的循环缓冲器来完成。在循环缓冲器中可以有多达64K-8个描述符。硬件维护卷影副本,包括已完成但尚未存储在内存中的那些描述符。
RDH(Receive Descriptor Head register)表示正在进行的描述符。
RDT(Receive Descriptor Tail register)表示超出硬件可以处理的最后一个描述符的位置。 这是软件写入第一个新描述符的位置。
在运行时,软件处理描述符,并在描述符完成后,RDT+1。 然而,在软件处理每个分组之后更新RDT具有成本,因为其增加了PCIe操作。
Rx_free_thresh表示DPDK软件在将其发送回硬件之前将保持的空闲描述符的最大数量。 因此,通过在更新RDT之前批量处理报文,我们可以减少该操作的PCIe成本。
使用您的配置的rte_eth_rx_queue_setup()函数中的参数进行微调
ret = rte_eth_rx_queue_setup(portid, 0, rmnb_rxd,
socketid, &rx_conf,
mbufpool[socketid]);
使用正确的优化参数编译
应用相应的解决方案:软件预取的内存,I/O阻塞,超线程或不超线程为CPU绑定的应用程序。
内存的软件预取有助于降低存储器延迟。
PREFETCHW:预期写数据时预取到高速缓存:CPU Haswell的新指令,帮助优化降低内存延迟并改善网络堆栈。
PREFETCHWT1:意图写入的预取提示T1(L1高速缓存):CPU Haswell的新指令,意图写入提示(使数据通过所有权请求进入“独占”状态),将数据提取到指定的高速缓存层级中的位置和位置提示。
有关这些说明的更多信息,请参阅Intel® 64 and IA-32 Architectures Developer’s Manual。
使用优化的命令行参数运行
优化应用程序命令行选项,提高affinity、locality和并发性。
coremask参数
coremask参数指定lcore运行DPDK应用程序。更高的性能,降低处理器间通信成本是关键。这样的沟通的核心是物理的邻居,应该选择coremask。
问题︰ DPDK的命令行coremask参数指定了lcore 0和lcore 1两个相邻芯。请注意,这些lcore号分别映射到指定的NUMA sockets上。不同的平台是不一样的,这个平台上可能lcore 0和1是邻居,有些平台可能不是。
如下图所示,在单socket,lcore 0和lcore 4是相同的物理core(core 0)的兄弟姐妹。所以lcore 0和 lcore 4之间的通信成本低于lcore 0和lcore 1之间的通信成本。
解决方案:不同的平台布局的时候需要注意使用不同的方案。
查看CPU布局的工具
通过tools目录的./cpu_layout.py能找出socket ID,物理core ID,lcore ID。根据这些信息来确定coremask参数怎么确定。
dual-socket机器我们看到的CPU布局如下:
物理core为[0, 1, 2, 3, 4, 8, 9, 10, 11, 16, 17, 18, 19, 20, 24, 25, 26, 27]
注意物理core 5, 6, 7, 12, 13, 14, 15, 21, 22, 23不在上面的范围中,这表明物理核心不一定是连续的。
怎么从cpu布局中找到哪些lcores是超线程的?
在下图中,lcore 1和lcore 37是socket 0的超线程,将通信任务分配给lcore 1和lcore 37比分配给其他的lcore有更低的消耗和更高的性能。
core 0留给Linux,别给DPDK用
DPDK初始化中,master core默认使用的是lcore 0。如下图所示
DPDK应用程序尽量不使用lcore 0,因为Linux 把lcore 0作为master core。避免使用l3fwd – c 0x1...,因为这会使用lcore 0。相反,应用程序可以使用lcore 1,命令行为l3fwd — — c 0x2......。
实际使用OVS-DPDK的情况下,控制平面线程绑定lcore 0,负责响应用户或SDN控制器发出的控制平面命令。所以,DPDK应用程序不应使用lcore 0,设置掩码的时候不激活lcore 0。
正确使用channel参数
要确保正确使用channel参数。例如,使用channel参数n = 3表示3通道内存系统。
DPDK Micro-benchmarks and auto-tests
auto-tests用于功能验证。以下是几个micro-benchmarks性能评价的例子。
怎么测试两个core的cache line往返消耗的时间
http://dpdk.org/browse/dpdk/tree/app/test/test_distributor_perf.c中的函数Time_cache_line_switch()就是用来测试两个core的cache line往返所需要的cpu cycles。
怎么测试报文处理时间
http://dpdk.org/browse/dpdk/tree/app/test/test_distributor_perf.c中的函数Perf_test()一次发送32个报文,最后工作线程记录收到最后一个报文的时间,然后计算出每个报文消耗的时间。
怎么检测single producer/single consumer(sp/sc)和multi-producer/multi-consumer(mp/mc)之间的性能差异
运行/app/test中的ring_perf_auto_test,会输出cpu cycles,来对比不同bulk下sp/sc和mp/mc的差别。
关键点:高bulk下,sp/sc有更高的性能。
注意,默认ring_perf_auto_test贯穿块大小8-32的性能测试,但是如果想自定义,需要修改数组bulk_size[]。下图输出了1、2、4、8、16、32的块大小。
2-Socket System – Huge Page Size = 2 Meg
hash_perf_autotest为每个测试项运行1000000次迭代,改变以下的参数并且为每个组合总结出每次操作所用的时钟滴答(Ticks/Op)
Hash Function | Operation | Key Size (bytes) | Entries | Entries per Bucket |
---|---|---|---|---|
a) Jhash, b) Rte_hash_CRC | a) Add On Empty, b) Add Update, c) Lookup | a) 16, b) 32, c) 48, d) 64 | a) 1024, b) 1048576 | a) 1, b) 2, c) 4, d) 8, e) 16 |
附录有详细的测试输出和命令,您可以使用来评估您的平台的性能。摘要结果制成表格下, 图︰
Sl No. | Focus Area to Improve | Use These Micro-Benchmarks and Auto-Tests |
---|---|---|
1 | Ring For Inter-Core Communication | 单独enqueue/dequeue和批量enqueue/dequeue之间的性能对比 分别对比不同的超线程、cores、sockets上的两个core之间的批量enqueue/dequeue 空ring上dequeue的性能test_ring_perf.c Single producer, single consumer – 1 Object, 2 Objects, MAX_BULK Objects – enqueue/dequeue Multi-producer, multi-consumer – 1 Object, 2 Objects, MAX BULK Objects – enqueue/dequeue 使用test_ring.c验证以上几项 test_pmd_ring.c分验证Tx Burst和Rx Burst |
2 | Memcopy | 使用test_memcpy_perf.c分别验证以下几项: Cache to cache Cache to memory Memory to memory Memory to cache |
3 | Mempool | 使用test_mempool.c验证以下几项的n_get_bulk 和n_put_bulk 1 core, 2 cores, max cores with cache objects 1 core, 2 cores, max cores without cache objects |
4 | Hash | 使用test_hash_perf.c验证Rte_jhash 和rte_hash_crc 的以下几项Add, Lookup, Update |
5 | ACL Lookup | test_acl.c |
6 | LPM | test_lpm.c |
7 | Packet Distribution | test_distributor_perf.c |
8 | NIC I/O Benchmark | Benchmarks Network I/O Pipe - NIC h/w + PMD Measure Tx Only Measure Rx Only Measure Tx & Rx |
9 | NIC I/O + Increased CPU processing | Increased CPU processing – NIC h/w + PMD + hash/lpm Examples/l3fwd |
10 | Atomic Operations/ Lock-rd/wr | test_atomic.c和test_rwlock.c |
11 | SpinLock | 使用test_spinlock.c验证以下两项: 申请全局锁,做些操作,释放全局锁 申请per-lcore锁,做些操作,释放per-core锁 |
12 | Software Prefetch | test_prefetch.c用法为:rte_table_hash_ext.c |
13 | Reorder and Seq. Window | test_reorder.c |
14 | ip_pipeline | 用packet framework创建一个pipeline,test_table.c ACL Using Packet Framework,test_table_acl.c |
15 | Reentrancy | test_func_reentrancy.c |
16 | mbuf | test_mbuf.c |
17 | memzone | test_memzone.c |
18 | Ivshmem | test_ivshmem.c |
19 | Virtual PMD | virtual_pmd.c |
20 | QoS | test_meter.c,test_red.c,test_sched.c |
21 | Link Bonding | test_link_bonding.c |
22 | Kni | 使用test_kni.c验证以下几项: 传输、从kernel接收、kernel请求 |
23 | Malloc | test_malloc.c |
24 | Debug | test_debug.c |
25 | Timer | test_cycles.c |
26 | Alarm | test_alarm.c |
编译优化
可以参考图书Pyster - Compiler Design and construction
性能优化和弱有序注意事项
背景︰Linux内核的同步原语包含所需的内存屏障如下所示(单处理器和多处理器的版本)
Smp_mb() | 内存屏障 |
---|---|
Smp_rmb() | 读内存屏障 |
Smp_wmb() | 写内存屏障 |
Smp_read_barrier_depends() | 强制后续的操作是依赖与之前的操作,保证有序性 |
Mmiowb() | 全局spinlocks保障有序的MMIO写操作 |
使用标准同步原语(spinlocks, semaphores, read copy update)的代码不必使用内存屏障,因为这些标准的同步原语已经使用了内存屏障。
挑战:如果你写的代码没有使用标准的同步原语,而又想优化,可以使用内存屏障。
考虑:X86提供了process ordering的内存模型,他是弱一致性的,可以任意打乱顺序,除非有内存屏障限制。
smp_mp(), smp_rmb(), smp_wmb()原语禁止编译器优化,避免内存重新排序优化跨过内存屏障的影响。
一些SSE指令是弱有序的(clflush and non-temporal move instructions),带有SSE指令的CPU mfence类似smp_mb(),lfence类似smp_rmb(),sfence类似smp_wmb()。
附录
Pmd_perf_autotest
使用/app/test/pmd_perf_autotest
评估你的平台性能
关键点:每个报文RX+TX的开销是54 cycles,4端口4内存通道
只查看RX的开销,运行程序pmd_perf_autotest
之前先运行set_rxtx_anchor rxonly
,同样只查看TX的开销,运行set_rxtx_anchor txonly
。
Packet Size = 64B # of channels n= 4
of cycles per packet | TX+RX Cost | TX only Cost | Rx only Cost |
---|---|---|---|
With 4 ports | 54 cycles | 21 cycles | 31 cycles |
下图是TX和RX的数据
Hash Table Performance Test Results
运行/app/test/hash_perf_autotest
评估系统性能。
Memcpy_perf_autotest Test Results
运行/app/test/memcpy_perf_autotest
评估平台系统性能,分别有32位对齐和未对齐的情况
Mempool_perf_autotest Test Results
Core Configuration | Cache Object | Bulk Get Size | Bulk Put Size | of Kept Objects |
---|---|---|---|---|
a) 1 Core b) 2 Cores c) Max. Cores | a) with cache object b) without cache object | a) 1 b) 4 c) 32 | a) 1 b) 4 c) 32 | a) 32 b) 128 |
运行/app/test/mempool_perf_autotest
评估平台系统性能
Timer_perf_autotest Test Results
of Timers Configuration | Operations Timed |
---|---|
a) 0 Timer b) 100 Timers c) 1000 Timers d) 10,000 Timers e) 100,000 Timers f) 1,000,000 Timers | - Appending - Callback - Resetting |
运行/app/test/timer_perf_autotest
评估平台系统性能