首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >你的代码还能更快!请合理设计使用和定义你的数据结构

你的代码还能更快!请合理设计使用和定义你的数据结构

作者头像
dpdk-vpp源码解读
发布2024-11-23 08:29:42
发布2024-11-23 08:29:42
18400
代码可运行
举报
文章被收录于专栏:DPDK VPP源码分析DPDK VPP源码分析
运行总次数:0
代码可运行
最近在项目开发中,需要

VPP(Vector Packet Processing)和 Redis(Remote Dictionary Server,一种内存中的数据存储系统)一样,为了满足不同场景下的性能优化和功能扩展需求,自定义了底层数据结构。这些自定义的数据结构不仅简化了代码设计,还提高了内存效率。然而,为了真正发挥这些数据结构的优势,我们需要深入了解其内存分布的原理,并合理运用这些知识,从而提升转发性能。在公众号专栏《Vppinfra基础库代码详解》中我们介绍了基础库数据结构的使用场景和底层内存分布情况。在项目的开发中,强烈建议大家认真阅读源码。如果时间有限,也可以阅读我的公众号专栏文章。

在接触 VPP(Vector Packet Processing)之前,我曾在华为 NP(网络处理器)部门担任软件工程师。那时的主要职责是进行 DFX(Design for Excellence,通常指设计以提高可测试性、可维护性等)工作,具体任务包括定时检测芯片底层内部数据是否与期望值一致。若检测到不一致,则需要进行数据回刷或触发芯片的软重启,以恢复数据并确保业务正常运行。这类开发工作主要考验的是逻辑实现能力,通常只需要遵循芯片手册的要求编写代码即可。

DFX(Design for Testability) 在芯片设计中,“DFX”特指为了便于测试而进行的设计,尤其是为了方便对芯片内部状态进行检测和验证。这涉及到在设计阶段就考虑到如何方便地对芯片进行测试,确保芯片的功能正确性和可靠性。 FMEA(Failure Modes and Effects Analysis,失效模式与影响分析)是一种系统化的方法,用于识别产品设计中的潜在故障模式、评估风险、优化设计和改进过程控制。

而VPP(Vector Packet Processing)软件专为高性能网络处理而设计和开发。它对程序员的基本功提出了极高的要求,涉及对多种底层硬件特性的深入理解和应用,如 Cache、指令流水线、超标量处理(Superscalar)、SIMD(单指令多数据流)、分支预测及内存屏障等。此外,还需要熟练掌握并运用编译器和操作系统的基础知识。而我作为非科班出身,对这些知识一点不了解。通过阅读下面的书籍《深入浅出DPDK》才算入门。建议新入门的新伙伴一定要去阅读这本宝典。

下面就简单我看到一些问题。bihash结构使用:代码中为了保存pool内存池的索引,使用了bihash结构。而bihash中key是通过接口索引直接获取的固定数值X。在文章《VPPinfra---bihash简介》中了解bihash是为了解决高并发大容量数据而设计一种键值(key/value)存储数据结构。bihash使用前是需要预分配合理固定内存的,而且还需要额外内存存储hash桶数据。

Bihash实现的优势包括:

  • 哈希表容量可动态扩展,最高测试可达100M条记录数; bihash采用两级数据结构,一级是桶,二级是页;桶的大小是在初始化的时候确定的,不可以动态修改,这种结构的好处就是在哈希冲突时,可以通过扩页来解决,避免大量数据搬移带来的性能损耗。
  • 查找性能不会随着表中记录数量的增加而剧烈下降; 在bihash初始化时,需要设置合理的bucket的数量及内存大小。根据经验来看,bucket的大小=用户总的计算量/4 (一页记录4个kv对,这个也是根据需要可调整的);所设置的内存量应该可以轻松地包含所有的记录,并允许hash冲突。bihash哈希的内存和vpp主内存mainheap是分开的。
  • 读操作不需要锁; 实际上在bihash 查询操作中存在自旋锁+内存屏障,但是这种概率也是非常低的(代码中有分支预测处理),是在查询到当前桶并且存在添加和删除的时候,才存在锁。

而当前场景只需要使用线性存储结构数据来实现就可以的。线性查找O(1)性能。

代码语言:javascript
代码运行次数:0
运行
复制
u32 *get_poolid_by_interface_vec;

在内核的邮件列表中提交一个patch中,作者重新调整了struct file内部字段的顺序,使经常访问的字段保持在同一个cache line上,可以在单numa下的高并发场景性能提升30%-50%。

建议各位详细阅读邮件内容: https://lore.kernel.org/lkml/ZHfKmG5RtgrMb6OT@dread.disaster.area/T/

patch修改代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 21a981680856..01c55e3a1b96 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -962,23 +962,23 @@ struct file {
     struct rcu_head   f_rcuhead;
     unsigned int     f_iocb_flags;
   };
-  struct path    f_path;
-  struct inode    *f_inode;  /* cached value */
-  const struct file_operations  *f_op;
 
   /*
    * Protects f_ep, f_flags.
    * Must not be taken from IRQ context.
    */
   spinlock_t    f_lock;
-  atomic_long_t    f_count;
-  unsigned int     f_flags;
   fmode_t      f_mode;
+  atomic_long_t    f_count;
   struct mutex    f_pos_lock;
+  unsigned int    f_flags;
   loff_t      f_pos;
   struct fown_struct  f_owner;
   const struct cred  *f_cred;
   struct file_ra_state  f_ra;
+  struct path    f_path;
+  struct inode    *f_inode;  /* cached value */
+  const struct file_operations  *f_op;

在 UnixBench 的系统调用测试中,由于伪共享(false sharing)导致了性能退化。在高并发测试场景中,锁和原子成员(包括 file::f_lock、file::f_count 和 file::f_pos_lock)高度竞争且频繁更新。perf c2c 识别出一个受影响的读取访问,即 file::f_op。为了防止伪共享,file 结构的布局进行了如下调整:

(A) f_lock、f_count 和 f_pos_lock 被放置在一起,共享同一个缓存行。(B) 大多数时候用于读取的成员(包括 f_path、f_inode 和 f_op)被放置到一个单独的缓存行。(C) f_mode 与 f_count 放置在一起,因为它们经常同时使用。

由此可见,数据结构的设计和定义对软件性能有着重大影响。请大家在定义数据结构和进行设计时务必慎重考虑。后期的性能优化往往需要花费数十倍甚至上百倍的努力才能取得显著提升。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-10-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DPDK VPP源码分析 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档