端口热插拔框架为DPDK应用程序提供在运行时附加和分离端口的能力。由于该框架依赖于PMD实现,PMD无法处理的端口超出了该框架的范围。此外,在从DPDK应用程序分离端口后,该框架不提供从系统中移除设备的方法。对于由物理网卡支持的端口,内核需要支持PCI热插拔功能。
端口热插拔框架的基本要求包括:
rte_eth_dev_stop() 和 rte_eth_dev_close() API。这些函数将启动PMD的最终化序列。rte_eth_dev_attach() API将端口附加到DPDK应用程序,并返回已附加的端口号。在调用API之前,设备应该被用户空间驱动I/O框架识别。该API接收类似“0000:01:00.0”这样的PCI地址或者类似“eth_pcap0,iface=eth0”这样的虚拟设备名称。对于虚拟设备名称,格式与DPDK的一般“–vdev”选项相同。rte_eth_dev_detach() API从DPDK应用程序分离端口,并返回分离设备的PCI地址或设备的虚拟设备名称。“testpmd”支持端口热插拔框架。
此部分描述了DPDK框架中源代码的组织结构。
注意:在下面的描述中,RTE_SDK 是一个环境变量,指向解压缩tar包的基本目录。请参阅构建系统提供的有用变量的描述,了解其他变量的说明。
DPDK库和应用程序提供的 Makefiles 位于 $(RTE_SDK)/mk。
Config 模板位于 $(RTE_SDK)/config。这些模板描述了每个目标启用的选项。配置文件还包含许多DPDK库的可启用和禁用项,包括调试选项。用户应查看配置文件,并熟悉其中的选项。配置文件还用于创建一个头文件,该头文件将位于新的构建目录中。
库位于 $(RTE_SDK)/lib 的子目录中。按照惯例,我们将提供应用程序接口(API)的代码称为库。通常,它会生成一个存档文件(.a),但内核模块也应该放在同一个目录下。
lib 目录包含:
lib
+-- librte_cmdline # command line interface helper
+-- librte_distributor # packet distributor
+-- librte_eal # environment abstraction layer
+-- librte_ether # generic interface to poll mode driver
+-- librte_hash # hash library
+-- librte_ip_frag # IP fragmentation library
+-- librte_ivshmem # QEMU IVSHMEM library
+-- librte_kni # kernel NIC interface
+-- librte_kvargs # argument parsing library
+-- librte_lpm # longest prefix match library
+-- librte_malloc # malloc-like functions
+-- librte_mbuf # packet and control mbuf manipulation library
+-- librte_mempool # memory pool manager (fixedsized objects)
+-- librte_meter # QoS metering library
+-- librte_net # various IP-related headers
+-- librte_pmd_bond # bonding poll mode driver
+-- librte_pmd_e1000 # 1GbE poll mode drivers (igb and em)
+-- librte_pmd_fm10k # Host interface PMD driver for FM10000 Series
+-- librte_pmd_ixgbe # 10GbE poll mode driver
+-- librte_pmd_i40e # 40GbE poll mode driver
+-- librte_pmd_mlx4 # Mellanox ConnectX-3 poll mode driver
+-- librte_pmd_pcap # PCAP poll mode driver
+-- librte_pmd_ring # ring poll mode driver
+-- librte_pmd_virtio # virtio poll mode driver
+-- librte_pmd_vmxnet3 # VMXNET3 poll mode driver
+-- librte_pmd_xenvirt # Xen virtio poll mode driver
+-- librte_power # power management library
+-- librte_ring # software rings (act as lockless FIFOs)
+-- librte_sched # QoS scheduler and dropper library
+-- librte_timer # timer library
应用程序是包含 main() 函数的源代码。它们位于 $(RTE_SDK)/app 和 $(RTE_SDK)/examples 目录中。
app 目录包含用于测试DPDK的示例应用程序(自动测试)。
examples 目录包含展示如何使用库的示例应用程序。
app
+-- chkincs # test prog to check include depends
+-- test # autotests, to validate DPDK features
+-- test-pmd # test and bench poll mode driver examples
examples
+-- cmdline # Example of using cmdline library
+-- dpdk_qat # Example showing integration with Intel QuickAssist
+-- exception_path # Sending packets to and from Linux Ethernet device (TAP)
+-- helloworld # Helloworld basic example
+-- ip_reassembly # Example showing IP Reassembly
+-- ip_fragmentation # Example showing IPv4 Fragmentation
+-- ipv4_multicast # Example showing IPv4 Multicast
+-- kni # Kernel NIC Interface example
+-- l2fwd # L2 Forwarding example with and without SR-IOV
+-- l3fwd # L3 Forwarding example
+-- l3fwd-power # L3 Forwarding example with power management
+-- l3fwd-vf # L3 Forwarding example with SR-IOV
+-- link_status_interrupt # Link status change interrupt example
+-- load_balancer # Load balancing across multiple cores/sockets
+-- multi_process # Example applications with multiple DPDK processes
+-- qos_meter # QoS metering example
+-- qos_sched # QoS scheduler and dropper example
+-- timer # Example of using librte_timer library
+-- vmdq_dcb # Intel 82599 Ethernet Controller VMDQ and DCB receiving
+-- vmdq # Example of VMDQ receiving
+-- vhost # Example of userspace vhost and switch
注意:实际的 examples 目录可能包含除上述示例之外的其他示例应用程序。请查看最新的DPDK源文件以了解详情。
DPDK需要一个构建系统来进行编译等活动。本部分描述了DPDK框架中使用的约束和机制。
该框架有两个用例:
• 编译DPDK库和示例应用程序;该框架生成特定的二进制库、包含文件和示例应用程序。
• 使用已安装的二进制DPDK,编译外部应用程序或库。
以下提供了如何构建DPDK二进制的详细信息。
在安装后,会创建一个构建目录结构。每个构建目录包含包含文件、库和应用程序:
~/DPDK$ ls
app MAINTAINERS
config Makefile
COPYRIGHT mk
doc scripts
examples lib
tools x86_64-native-linuxapp-gcc
x86_64-native-linuxapp-icc i686-native-linuxapp-gcc
i686-native-linuxapp-icc
...
~/DEV/DPDK$ ls i686-native-linuxapp-gcc
app build hostapp include kmod lib Makefile
~/DEV/DPDK$ ls i686-native-linuxapp-gcc/app/
cmdline_test dump_cfg test testpmd
cmdline_test.map dump_cfg.map test.map
testpmd.map
~/DEV/DPDK$ ls i686-native-linuxapp-gcc/lib/
libethdev.a librte_hash.a librte_mbuf.a librte_pmd_ixgbe.a
librte_cmdline.a librte_lpm.a librte_mempool.a librte_ring.a
librte_eal.a librte_malloc.a librte_pmd_e1000.a librte_timer.a
~/DEV/DPDK$ ls i686-native-linuxapp-gcc/include/
arch rte_cpuflags.h rte_memcpy.h
cmdline_cirbuf.h rte_cycles.h rte_memory.h
cmdline.h rte_debug.h rte_mempool.h
cmdline_parse_etheraddr.h rte_eal.h rte_memzone.h
cmdline_parse.h rte_errno.h rte_pci_dev_ids.h
cmdline_parse_ipaddr.h rte_ethdev.h rte_pci.h
cmdline_parse_num.h rte_ether.h rte_per_lcore.h
cmdline_parse_portlist.h rte_fbk_hash.h rte_prefetch.h
cmdline_parse_string.h rte_hash_crc.h rte_random.h
cmdline_rdline.h rte_hash.h rte_ring.h
cmdline_socket.h rte_interrupts.h rte_rwlock.h
cmdline_vt100.h rte_ip.h rte_sctp.h
exec-env rte_jhash.h rte_spinlock.h
rte_alarm.h rte_launch.h rte_string_fns.h
rte_atomic.h rte_lcore.h rte_tailq.h
rte_branch_prediction.h rte_log.h rte_tcp.h
rte_byteorder.h rte_lpm.h rte_timer.h
rte_common.h rte_malloc.h rte_udp.h
rte_config.h rte_mbuf.h
构建目录特定于配置,包括架构 + 执行环境 + 工具链。可以有多个构建目录共享相同的源文件,但具有不同的配置。
例如,要使用默认配置模板config/defconfig_x86_64-linuxapp创建一个名为my_sdk_build_dir的新构建目录,可以执行以下操作:
cd ${RTE_SDK}
make config T=x86_64-native-linuxapp-gcc O=my_sdk_build_dir
这将创建一个新的 my_sdk_build_dir 目录。 之后,我们可以通过执行以下操作进行编译:
cd my_sdk_build_dir
make
这相当于:
make O=my_sdk_build_dir
my_sdk_build_dir 的内容是:


由于DPDK本质上是一个开发套件,最终用户的首要目标将是使用此SDK创建应用程序。要编译一个应用程序,用户必须设置 RTE_SDK 和 RTE_TARGET 环境变量。
export RTE_SDK=/opt/DPDK
export RTE_TARGET=x86_64-native-linuxapp-gcc
cd /path/to/my_app
对于新应用程序,用户必须创建自己的Makefile,其中包含一些.mk文件,如 ${RTE_SDK}/mk/rte.vars.mk 和 ${RTE_SDK}/mk/rte.app.mk。这在"构建您自己的应用程序"中有描述。
根据在Makefile中定义的或作为环境变量定义的选择的目标(架构、机器、执行环境、工具链),应用程序和库将使用适当的.h文件进行编译,并链接适当的.a文件。这些文件位于 ${RTE_SDK}/arch-machine-execenv-toolchain 中,内部由 ${RTE_BIN_SDK} 引用。
要编译他们的应用程序,用户只需调用 make。编译结果将位于 /path/to/my_app/build 目录中。
示例应用程序位于 examples 目录中。
在DPDK中,Makefiles 总是遵循相同的方案:
以下是一个非常简单的外部应用程序Makefile示例:
include $(RTE_SDK)/mk/rte.vars.mk
# binary name
APP = helloworld
# all source are stored in SRCS-y
SRCS-y := main.c
CFLAGS += -O3
CFLAGS += $(WERROR_FLAGS)
include $(RTE_SDK)/mk/rte.extapp.mk
根据用户Makefile末尾包含的 .mk 文件不同,Makefile 将具有不同的作用。请注意,不可能在同一个Makefile中构建库和应用程序。对此,用户必须创建两个单独的Makefile,可能位于两个不同的目录中。
无论如何,用户Makefile中必须尽快包含 rte.vars.mk 文件。
应用程序
这些Makefiles生成二进制应用程序。
• rte.app.mk:在开发套件框架中的应用程序
• rte.extapp.mk:外部应用程序
• rte.hostapp.mk:开发套件框架中的主机应用程序
库
生成 .a 库。
• rte.lib.mk:开发套件框架中的库
• rte.extlib.mk:外部库
• rte.hostlib.mk:开发套件框架中的主机库
安装
• rte.install.mk:不构建任何内容,仅用于在安装目录中创建链接或复制文件。这对包含开发套件框架中的文件很有用。
内核模块
• rte.module.mk:在开发套件框架中构建内核模块。
对象
• rte.obj.mk:对象聚合(将多个 .o 合并为一个)在开发套件框架中。
• rte.extobj.mk:对象聚合(将多个 .o 合并为一个)在开发套件框架外部。
其他
• rte.doc.mk:开发套件框架中的文档
• rte.gnuconfigure.mk:构建基于 configure 的应用程序。
• rte.subdir.mk:在开发套件框架中构建多个目录。
• RTE_SDK:DPDK源文件的绝对路径。在编译开发套件时,该变量由框架自动设置。如果编译外部应用程序,则必须由用户定义为环境变量。
• RTE_SRCDIR:源文件根目录的路径。在编译开发套件时,RTE_SRCDIR = RTE_SDK。在编译外部应用程序时,该变量指向外部应用程序源文件的根目录。
• RTE_OUTPUT:写入输出文件的路径。通常是 $(RTE_SRCDIR)/build,但可以通过 make 命令行中的 O= 选项进行覆盖。
• RTE_TARGET:标识正在构建的目标的字符串。格式为架构-机器-执行环境-工具链。在编译SDK时,目标由配置文件(.config)的构建系统推导出来。在构建外部应用程序时,用户必须在Makefile中或作为环境变量指定。
• RTE_SDK_BIN:引用
(
R
T
E
S
D
K
)
/
(RTE_SDK)/
(RTESDK)/(RTE_TARGET)。
• RTE_ARCH:定义架构(i686、x86_64)。与 CONFIG_RTE_ARCH 相同,但字符串周围没有双引号。
• RTE_MACHINE:定义机器。与 CONFIG_RTE_MACHINE 相同,但字符串周围没有双引号。
• RTE_TOOLCHAIN:定义工具链(gcc、icc)。与 CONFIG_RTE_TOOLCHAIN 相同,但字符串周围没有双引号。
• RTE_EXEC_ENV:定义执行环境(linuxapp)。与 CONFIG_RTE_EXEC_ENV 相同,但字符串周围没有双引号。
• RTE_KERNELDIR:该变量包含用于编译内核模块的内核源文件的绝对路径。内核头文件必须与将在目标机器(运行应用程序的机器)上使用的头文件相同。默认情况下,该变量设置为 /lib/modules/$(shell uname -r)/build,这在目标机器也是构建机器时是正确的。
一些变量可用于配置构建系统的行为。这些变量在“Development Kit Root Makefile Help”和“External Application/Library Makefile Help”中有文档记录。
CFLAGS += $(WERROR_CFLAGS)
配置目标需要指定目标的名称,使用 T=mytarget 参数是必需的。可用的目标列表位于 $(RTE_SDK)/config(去掉 defconfig_ 前缀)。
配置目标还支持指定输出目录的名称,使用 O=mybuilddir。这是一个可选参数,默认输出目录是 build。
make config O=mybuild T=x86_64-native-linuxapp-gcc
构建目标支持指定输出目录的名称,使用 O=mybuilddir。默认输出目录是 build。
all, build 或仅 make
在之前由 make config 创建的输出目录中构建 DPDK。
示例:
make O=mybuild
clean
清除使用 make build 创建的所有对象。
示例:
make clean O=mybuild
%_sub
仅构建一个子目录,而不管理其他目录的依赖关系。
示例:
make lib/librte_eal_sub O=mybuild
%_clean
仅清除一个子目录。
示例:
make lib/librte_eal_clean O=mybuild
Install
构建 DPDK 二进制文件。实际上,这会将每个支持的目标构建到单独的目录中。每个目录的名称是目标的名称。可以选择性地使用 T=mytarget 指定要安装的目标名称。目标名称可以包含通配符 * 字符。可用的目标列表位于 $(RTE_SDK)/config(删除 defconfig_ 前缀)。
示例:
make install T=x86_64-*
Uninstall
移除已安装的目标目录。
test
启动指定使用 O=mybuilddir 的构建目录的自动测试。这是可选的,默认输出目录是 build。
示例:
make test O=mybuild
testall
启动所有已安装目标目录(在 make install 之后)的自动测试。可以选择性地使用 T=mytarget 指定要测试的目标名称。目标名称可以包含通配符 * 字符。可用的目标列表位于 $(RTE_SDK)/config(删除 defconfig_ 前缀)。
示例:
make testall, make testall T=x86_64-*
depdirs
此目标在 make config 时隐式调用。通常情况下,除非在 Makefiles 中更新了 DEPDIRS-y 变量,否则无需用户调用它。它将生成文件 $(RTE_OUTPUT)/.depdirs。
示例:
make depdirs O=mybuild
depgraph
此命令生成依赖关系的 dot 图。它可以用于调试循环依赖问题,或者只是了解依赖关系。
示例:
make depgraph O=mybuild > /tmp/graph.dot && dotty /tmp/graph.dot
以下变量可以在命令行上指定:
在 SDK 根目录 $(RTE_SDK) 中调用了上述所有目标。可以在构建目录内部运行相同的 Makefile 目标。例如,以下命令:
cd $(RTE_SDK)
make config O=mybuild T=x86_64-native-linuxapp-gcc
make O=mybuild
等同于:
cd $(RTE_SDK)
make config O=mybuild T=x86_64-native-linuxapp-gcc
cd mybuild
# 现在不需要再指定 O= now
make
要在 DPDK 和示例应用程序中包含调试信息并将优化级别设置为 0 进行编译,应在编译之前设置 EXTRA_CFLAGS 环境变量,如下所示:
export EXTRA_CFLAGS='-O0 -g'
然后可以以通常的方式编译 DPDK 以及任何用户或示例应用程序。例如:
make install T=x86_64-native-linuxapp-gcc
make -C examples/<theapp>
本章描述了开发人员如何扩展 DPDK 以提供新的库、新的目标或支持新的目标。
要向 DPDK 中添加新库,请按照以下步骤进行:
for f in config/*; do \
echo CONFIG_RTE_LIBFOO=y >> $f; done
1、创建带有源代码的新目录:
mkdir ${RTE_SDK}/lib/libfoo
touch ${RTE_SDK}/lib/libfoo/foo.c
touch ${RTE_SDK}/lib/libfoo/foo.h
2、在 libfoo 中添加 foo() 函数:
void foo(void)
{
}
在 foo.h 中声明:
extern void foo(void);
3、更新 lib/Makefile:
vi ${RTE_SDK}/lib/Makefile
添加:
DIRS-$(CONFIG_RTE_LIBFOO) += libfoo
4、为此库创建新的 Makefile(从 mempool Makefile 派生):
cp ${RTE_SDK}/lib/librte_mempool/Makefile ${RTE_SDK}/lib/libfoo/
vi ${RTE_SDK}/lib/libfoo/Makefile
替换:
librte_mempool -> libfoo
rte_mempool -> foo
5、更新 mk/DPDK.app.mk:
如果启用了该选项,则在 LDLIBS 变量中添加 -lfoo。这将在链接 DPDK 应用程序时自动包含此标志。
6、使用新库构建 DPDK:
cd ${RTE_SDK}
make config T=x86_64-native-linuxapp-gcc
make
7、检查库的安装情况:
ls build/lib
ls build/include
30.1.1 示例:在测试应用程序中使用 libfoo
测试应用程序用于验证 DPDK 的所有功能。一旦您添加了一个库,就应该在测试应用程序中添加一个新的测试用例。
cd ${RTE_SDK}
make config T=x86_64-native-linuxapp-gcc
make
构建你自己的应用程序
当编译示例应用程序(例如,hello world)时,必须导出以下变量:RTE_SDK 和 RTE_TARGET。
~/DPDK$ cd examples/helloworld/
~/DPDK/examples/helloworld$ export RTE_SDK=/home/user/DPDK
~/DPDK/examples/helloworld$ export RTE_TARGET=x86_64-native-linuxapp-gcc
~/DPDK/examples/helloworld$ make
CC main.o
LD helloworld
INSTALL-APP helloworld
INSTALL-MAP helloworld.map
默认情况下,二进制文件生成在 build 目录下:
~/DPDK/examples/helloworld$ ls build/app
helloworld helloworld.map
将示例应用程序(Hello World)复制到一个新目录,作为开发的起点:
~$ cp -r DPDK/examples/helloworld my_rte_app
~$ cd my_rte_app/
~/my_rte_app$ export RTE_SDK=/home/user/DPDK
~/my_rte_app$ export RTE_TARGET=x86_64-native-linuxapp-gcc
~/my_rte_app$ make
CC main.o
LD helloworld
INSTALL-APP helloworld
INSTALL-MAP helloworld.map
自定义 Makefile
提供的 Hello World 示例应用程序的默认 Makefile 是一个很好的起点。
它包含以下内容:
在开头包含了 $(RTE_SDK)/mk/rte.vars.mk
在结尾包含了 $(RTE_SDK)/mk/rte.extapp.mk
用户需要定义一些变量:
APP:包含应用程序的名称。
SRCS-y:源文件列表(.c,.S)。
同样也可以以相同的方式构建一个库:
在开头包含 $(RTE_SDK)/mk/rte.vars.mk。
在结尾包含 $(RTE_SDK)/mk/rte.extlib.mk。
唯一的区别是 APP 应该替换为 LIB,其中包含库的名称。例如,libfoo.a。
一些变量可以被定义来自定义 Makefile 操作。以下是最常见的变量。有关详细信息,请参阅“开发工具包构建系统”章节中的“Makefile 描述”部分。
外部应用程序或库应包含来自 RTE_SDK 中的特定 Makefile,位于 mk 目录中。这些 Makefile 是:
必须定义以下变量:
构建目标支持指定输出目录的名称,使用 O=mybuilddir。这是可选的;默认输出目录为 build。
make O=mybuild
make clean O=mybuild
以下变量可以在命令行指定:
可以通过指定输出目录和源目录来从另一个目录运行 Makefile。例如:
export RTE_SDK=/path/to/DPDK
export RTE_TARGET=x86_64-native-linuxapp-icc
make -f /path/to/my_app/Makefile S=/path/to/my_app O=/path/to/build_dir
以下部分描述了 DPDK 中使用的优化以及新应用程序应考虑的优化方法。
它们还强调了在使用 DPDK 开发应用程序时应该和不应该使用的对性能影响较大的编码技术。
最后,它们介绍了使用英特尔的性能分析器进行应用程序分析以优化软件的方法。
本章提供了一些使用 DPDK 开发高效代码的技巧。欲获取更多通用信息,请参考英特尔® 64 和 IA-32 架构优化参考手册,这是编写高效代码的宝贵参考资料。
本节描述了在 DPDK 环境中开发应用程序时的一些关键内存注意事项。
在 DPDK 中通过 Linux* 应用环境可以使用许多 libc 函数,这可以简化应用程序的移植和配置平面的开发。然而,许多这些函数并非为性能而设计。像 memcpy() 或 strcpy() 这样的函数不应该在数据平面中使用。对于拷贝小结构体,最好使用编译器可以优化的更简单的技术。建议参考英特尔出版的 VTune™ Performance Analyzer Essentials 出版物获取建议。
对于频繁调用的特定函数,最好提供一个自制的经过优化的函数,应该声明为 static inline。
DPDK API 提供了一个经过优化的 rte_memcpy() 函数。
libc 的其他函数,如 malloc(),提供了一种灵活的方式来分配和释放内存。在某些情况下,使用动态分配是必要的,但不建议在数据平面中使用类似 malloc 的函数,因为管理碎片化的堆可能成本高昂,而且分配器可能没有针对并行分配进行优化。
如果确实需要在数据平面中进行动态分配,最好使用固定大小对象的内存池。这个 API 由 librte_mempool 提供。这个数据结构提供了一些增加性能的服务,如对象的内存对齐、无锁访问对象、NUMA 感知、批量获取/放置和每个 lcore 的缓存。rte_malloc() 函数使用了与内存池类似的概念。
多个 lcore 对同一内存区域的读写访问操作可能会产生大量数据缓存未命中,这是非常昂贵的。通常可以使用每个 lcore 变量,例如统计数据。针对此问题至少有两种解决方案:
如果同一缓存行中没有读写变量,那么只读变量可以在多个 lcore 之间共享而不会造成性能损失。
在 NUMA 系统上,最好访问本地内存,因为远程内存访问速度较慢。在 DPDK 中,memzone、ring、rte_malloc 和 mempool API 提供了在特定套接字上创建池的方法。
有时,复制数据以优化速度可能是个好主意。对于经常访问的只读变量,把它们仅保留在一个套接字中应该不是问题,因为数据将存在于缓存中。
现代内存控制器具有多个内存通道,可以并行加载或存储数据。根据内存控制器及其配置,内存分布在通道上的方式各不相同。每个通道都有带宽限制,这意味着如果所有内存访问操作都只在第一个通道上进行,可能会存在潜在的瓶颈。
默认情况下,内存池库将对象的地址分布在内存通道之间。
为了在 lcore 之间提供基于消息的通信,建议使用 DPDK ring API,它提供了无锁环实现。
这个环支持批量和突发访问,意味着可以使用一个昂贵的原子操作从环中读取多个元素(参见第 5 章“环库”)。使用批量访问操作可以极大地提高性能。
出队消息的代码算法可能类似于以下内容:
#define MAX_BULK 32
while (1) {
/* Process as many elements as can be dequeued. */
count = rte_ring_dequeue_burst(ring, obj_table, MAX_BULK);
if (unlikely(count == 0))
continue;
my_process_bulk(obj_table, count);
}
DPDK Poll Mode Driver(PMD)也能以批量/突发模式工作,允许在发送或接收函数的每次调用中对一些代码进行因式分解。
避免部分写入。当 PCI 设备通过 DMA 写入系统内存时,如果写操作是在完整的缓存行上而不是部分缓存行上,成本就会较低。在 PMD 代码中,已经采取了尽可能避免部分写入的措施。
传统上,吞吐量和延迟之间存在权衡。一个应用程序可以调整以实现高吞吐量,但平均数据包的端到端延迟通常会增加。同样地,该应用程序可以调整以实现平均较低的端到端延迟,但代价是较低的吞吐量。
为了实现更高的吞吐量,DPDK 试图通过批量处理每个数据包的成本来聚合。以 testpmd 应用程序为例,可以在命令行上将批量大小设置为 16(也是默认值)。这允许应用程序一次从 PMD 请求 16 个数据包。然后,testpmd 应用程序立即尝试传输所有接收到的数据包,即在本例中为所有 16 个数据包。
在网络端口的相应 TX 队列的尾指针更新之前,数据包不会被传输。在调整以实现高吞吐量时,这种行为是可取的,因为对 RX 和 TX 队列的尾指针更新的成本可以分摊到 16 个数据包上,有效地隐藏了相对较慢的 MMIO 写入 PCIe* 设备的成本。然而,当调整以实现低延迟时,这并不理想,因为第一个接收到的数据包必须等待其他 15 个数据包也被接收。在所有 16 个数据包都被传输进行处理之前,它无法被传输,因为网卡在 TX 尾指针更新之前不知道传输数据包。
为了在系统负载较重的情况下始终实现低延迟,应用程序开发人员应避免批量处理数据包。testpmd 应用程序可以从命令行配置为使用批量值为 1。这将允许一次处理一个数据包,提供较低的延迟,但代价是较低的吞吐量。
原子操作意味着在指令之前添加一个锁前缀,导致处理器在执行下一条指令期间断言 LOCK# 信号。在多核环境中,这对性能有很大影响。
通过避免在数据平面中使用锁机制可以提高性能。通常可以用其他解决方案代替,比如每个 lcore 变量。此外,某些锁定技术比其他技术更高效。例如,读-拷贝-更新(RCU)算法经常可以取代简单的读写锁。
小函数可以在头文件中声明为 static inline。这避免了调用指令的成本(以及相关的上下文保存)。然而,这种技术并不总是高效的;它取决于许多因素,包括编译器。
英特尔® C/C++ 编译器(icc)/gcc 内置的辅助函数 likely() 和 unlikely() 允许开发人员指示代码分支可能被执行或不会被执行。例如:
if (likely(x > 1))
do_stuff();
DPDK通过DPDK配置文件中的CONFIG_RTE_MACHINE选项支持针对CPU微架构的特定优化。优化程度取决于编译器针对特定微架构的优化能力,因此尽可能使用最新的编译器版本是可取的。
如果编译器版本不支持特定的功能集(例如Intel® AVX指令集),则构建过程会优雅地降级到编译器支持的最新功能集。
由于构建和运行时目标可能不相同,因此生成的二进制文件还包含一个在main()函数之前运行的平台检查,检查当前机器是否适合运行该二进制文件。
除了编译器优化之外,一组预处理器定义会自动添加到构建过程中(不考虑编译器版本)。这些定义对应于目标CPU应该能够支持的指令集。例如,为任何支持SSE4.2的处理器编译的二进制文件将定义RTE_MACHINE_CPUFLAG_SSE4_2,从而为不同平台启用编译时代码路径选择。
英特尔处理器提供性能计数器来监视事件。英特尔提供的一些工具可用于对应用程序进行性能分析和基准测试。参阅英特尔出版的《VTune性能分析器Essentials》获取更多信息。
对于DPDK应用程序,这仅限于Linux*应用程序环境中进行。
通过事件计数器应该监控的主要情况包括:
Figures
Tables
Figures
Tables