Hash 的工程优势: port range 匹配

article/2025/8/1 7:20:07

昨天和朋友聊到 “如何匹配一个 port range”,觉得挺有意思,简单写篇散文。

回想起十多年前,我移植并优化了 nf-HiPAC,当时还看不上 ipset hash,后来大约七八年前,我又舔 nftables,因为用它可直接写多维树匹配,更早之前,Linux 内核 Hash 路由查找算法被 trie 替代时,我就写文捧过一番,我是有多么看不上 Hash,似乎任何一棵 Tree 都吊打 hash。

多年以后反思,如果再让我做同样的需求,我定会 Hash 做到底,不会再动 Tree 的主意。Hash 实现最简单,并可随内存线性扩展,在大多数场景下也不会比 Tree 差,只有纯理工科背景且未被工程教训毒打过的副经理才会拎出 Tree 更好的例外作为替换 Hash 的理由。

匹配 port range,想当然的算法是构建区间查找树,当年看 Linux 内核 mmap 实现时看到过 Linux 文件映射就是用区间树实现,所以但凡和区间关联的,我都会去安利区间树,也因此当年优化 nf-HiPAC 时也就是选定了它,但在实现过程中遇到了极大的困难,于是我就偷了个懒。

有意思的是,这个偷懒很值得。

我没有用实际 rule 中的 port range 直接构建区间树,而将整个 port 空间分为一致 500 个端口一组的等距区间,然后将实际 rule 的 port range 往上盖,在等距边界处做 split。比如 rule 的 range 1234~1400,则 1000~1500 等距区间就被分为了 3 部分,这 3 部分作为匹配 1000~1500 后的二级匹配,执行一个线性遍历就完了。

我当时非常看不起线性遍历,所以一直耿耿于怀,我当时不知道,几十以内的线性遍历其实是无伤遍历,更何况个位数。巧的是我当时能力不足以让我继续优化成 Tree,就保留了 “二分等距区间查找 + 线性遍历” 的 “拙劣” 实现,没想到它工作得非常好。

回想起来,这种等距区间分割,让规则中的 port range 适配它的做法,依然属于横竖颠倒,拨云见日。我对这种方法论的掌握已经深入了内心,下意识而为之。

现如今,我连等距区间树也要扔掉,用 hash 替代等距区间树,就是另一个新的算法:
在这里插入图片描述
Hash 相比树的缺点在于要遍历冲突链表,比如 Linux 内核里的 hlist。假设 hash 算法足够优秀(没得说,都不差),hlist 中元素的个数是可随着内存增加减小的,而树的操作无论多好的 CPU 始终是 log 级。

虽然 Hash 要额外处理冲突,但树也不是一步到位的,加上树的平衡,锁等复杂操作,在通用软件场景下,其性价比远小于 Hash。假设 50000 并发,2000 个 hash bucket,平均只需要遍历 25 次,而采用二叉树需要 15 次操作,但算上其插入,平衡操作,是要比 hash 更昂贵的。二叉树只在更大并发时才凸显优势,其操作量随数据量 log 增长,而线性增长的 hash 操作亦可通过增加内存抵消操作数量。但如若真遇到海量并发,通用软件本身就成了瓶颈。

确保下面的区域:
在这里插入图片描述
显然,x 会随内存增加向右线性延展,,而 log 曲线 y = α ⋅ log ⁡ 2 x y=\alpha\cdot\log_2{x} y=αlog2x 则会因 CPU 性能提升而下压。纯算法分析,log 下压更明显,但纯算法分析,Hash 简直就是 O(1),所以二者并非简单的时间空间较劲二选一,而是呼吁架构的改变。

其实说白了就是 Hash 简单易实现没 bug,特别是频繁增删查的场景,一个反面案例来自 David Miller 对 IPv6 路由查找的 “优化”,惹了不少麻烦(详见 3.10 内核中 IPv6 的缺陷),要是用 Hash 实现,不可能有这事。

说点形而上。

Hash 和 Tree 实际上代表了计算在空间和时间分别展开的两个方向,Hash 的 O(1) 约束下,1 倍的规模增量可带来1 次操作增量,需要 1 倍的内存来抵消这 1 次的操作以维持 O(1),操作时间不变,对于二叉树,1 倍的规模增量同样带来 1 次操作增量(每增加 1 层,叶子节点数量增加 1 倍),但这次操作无法抵消,时间永远随规模增加而单调递增,然而由于二叉树在操作过程中天然保序,就意味着其不真需要额外预留 1 倍空间以支撑规模。

时间不可压缩,但空间可压缩,这是时空的本质不同,接受一个随规模递增得慢的时间,去压缩空间,这是所有机器的实现方式。背后是一个选择问题,我们是选择在时间中等待,去压缩空间,还是选择线性扩展空间,只为不等待,还是那个 “矩” 的长宽权衡。

直白说,空间不可无限扩展,我们倾向于压缩它,我们设计的机器也是,而时间总是流逝不可压缩,我们倾向于高效利用它,我们设计的机器也是。物件越来越多时,我们倾向于将它们分类叠放以便查找而不是在更大的空间平摊以便一眼瞅,空间的扩张总有极限,而时间却无限延展。空间是共享的,我们的一生时间却是私人的。

但我们还是希望有套稍微大一点哪怕极简的房子,在多数场景,它比紧凑的装修和储物分类更简单,更令人舒适,这还不够的话,就看看我们智人的历史,看看我们作为自己是如何学会 “集约(字面意思)” 的,我们的所谓文明,就是这种方式,所以我们设计的机器也是这种方式,但在大多数时间,我们却是用相反的方式:
在这里插入图片描述

首先要地盘,其次不要太大,这就是选择 hash 的理由。

浙江温州皮鞋湿,下雨进水不会胖。


http://www.hkcw.cn/article/SFbOFpGBYI.shtml

相关文章

力扣HOT100之动态规划:198. 打家劫舍

这道题之前刷代码随想录的时候做过,这一次直接一遍过了,还是按照动规五部曲: 1.确定dp[i]的含义:将下标为0 ~ i的房子纳入考虑范围时所能取到的最大收益 2.确定递推公式:dp[i] max(dp[i - 2] nums[i], dp[i - 1]); 3.dp数组初始化:dp[0] n…

基于VU37P的高性能采集板卡

基于VU37P的高性能采集板卡是一款最大可提供20路ADC接收通道的高性能采集板卡。每路A/D通道支持1GS/s的采样率,分辨率为14bit,模拟输入带宽可达500MHz,交流耦合,输入阻抗50欧姆。 产品简介 可提供20路ADC接收通道的高性能采集板…

使用ssh-audit扫描ssh过期加密算法配置

使用ssh-audit扫描ssh过期加密算法配置 安装检查ssh的加密算法配置修改ssh的加密算法配置 安装 # pip3安装ssh-audit pip3 instal ssh-audit检查ssh的加密算法配置 # 检查ssh的配置 ssh-audit 192.168.50.149修改ssh的加密算法配置 # 查看ssh加密配置文件是否存在 ls /etc/c…

身份证信息OCR识别提取

要实现Python中的身份证OCR识别,可以采用以下步骤和工具(结合开源库和API服务),以下是两种主流方案: 方案1:使用第三方OCR API(推荐百度/腾讯云) 百度OCR API 示例 注册并获取API …

C++之string的模拟实现

string 手写C字符串类类的基本结构与成员变量一、构造函数与析构函数二、赋值运算符重载三、迭代器支持四、内存管理与扩容机制五、字符串操作函数六、运算符重载总结 手写C字符串类 从零实现一个简易版std::string 类的基本结构与成员变量 namespace zzh { class string { …

Linux的调试器--gbd/cgbd

1.引入 #include <stdio.h> int Sum(int s, int e) {int result 0;for(int i s; i < e; i){result i;}return result; } int main() {int start 1;int end 100;printf("I will begin\n");int n Sum(start, end);printf("running done, result i…

云原生 Cloud Native Build (CNB)使用初体验

云原生 Cloud Native Build&#xff08;CNB&#xff09;使用初体验 引言 当“一切皆可云”成为趋势&#xff0c;传统开发环境正被云原生工具重塑。腾讯云CNB&#xff08;Cloud Native Build&#xff09;作为一站式开发平台&#xff0c;试图解决多环境协作难题。 本文将分享c…

硬件工程师笔记——运算放大电路Multisim电路仿真实验汇总

目录 1 运算放大电路基础 1.1 概述 1.1.1 基本结构 1.1.2 理想特性 1.2 运算放大分析方法 1.2.1 虚短 1.2.2虚断 1.2.3 叠加定理 2 同向比例运算放大电路 2.1 概述 2.1.1 基本电路结构 2.1.2 电路原理 2.2 仿真分析 2.2.1 电压增益 2.2.2 相位分析 3 反向比例运…

系统思考:经营决策沙盘

今年是我为黄浦区某国有油漆涂料企业提供经营决策沙盘培训的第二年。在这段时间里&#xff0c;我越来越感受到&#xff0c;企业的最大成本往往不在生产环节&#xff0c;而是在决策错误上所带来的长远影响。尤其是在如今这个复杂多变的环境下&#xff0c;企业面临的挑战愈发严峻…

Java线程:并发/并行区别、线程生命周期、乐观锁/悲观锁

并发、并行 进程 正在运行的程序(软件)就是一个独立的进程线程是属于进程的&#xff0c;一个进程中可以同时运行很多个线程进程中的多个线程其实是并发和并行执行的 并发 进程中的线程是由CPU负责调度执行的&#xff0c;但CPU能同时处理线程的数量有限&#xff0c;为了保证…

等保测评-Mysql数据库测评篇

Mysql数据库测评 0x01 前言 "没有网络安全、就没有国家安全" 等保测评是什么&#xff1f; 等保测评&#xff08;网络安全等级保护测评&#xff09;是根据中国《网络安全法》及相关标准&#xff0c;对信息系统安全防护能力进行检测评估的法定流程。其核心依据《信…

mysql的Memory引擎的深入了解

目录 1、Memory引擎介绍 2、Memory内存结构 3、内存表的锁 4、持久化 5、优缺点 6、应用 前言 Memory 存储引擎 是 MySQL 中一种高性能但非持久化的存储方案&#xff0c;适合临时数据存储和缓存场景。其核心优势在于极快的读写速度&#xff0c;需注意数据丢失风险和内存占…

QNAP MEMOS 域名访问 SSL(Lucky)

注意&#xff1a;下述是通过ssh、docker-compose方式安装docker的&#xff0c;不是直接在container station中安装的哈&#xff01;&#xff01;&#xff01; 一、编辑docker-compose.yml文件 用“#”号标识的&#xff0c;在保存文件的时候建议去掉&#xff0c;不然有时候会出…

BioID技术在宿主-病原体相互作用领域的应用

细菌感染是全球公共卫生的重大威胁&#xff0c;而抗生素耐药性的提升使我们迫切需要深入了解宿主 -病原体相互作用。细菌病原体通过分泌效应蛋白&#xff0c;操纵宿主细胞以建立感染。这些效应蛋白通过与宿主蛋白相互作用&#xff0c;改变宿主细胞功能&#xff0c;但传统研究方…

解析楼宇自控系统:分布式结构的核心特点与优势展现

在建筑智能化发展的进程中&#xff0c;楼宇自控系统作为实现建筑高效运行与管理的关键&#xff0c;其系统结构的选择至关重要。传统的集中式楼宇自控系统在面对日益复杂的建筑环境和多样化的管理需求时&#xff0c;逐渐暴露出诸多弊端&#xff0c;如可靠性低、扩展性差、响应速…

SAP Business One:无锡哲讯科技助力中小企业数字化转型的智慧之选

数字化转型&#xff0c;中小企业的必经之路 在当今竞争激烈的商业环境中&#xff0c;数字化转型已不再是大型企业的专利&#xff0c;越来越多的中小企业开始寻求高效、灵活的管理系统来优化业务流程、提升运营效率。作为全球领先的企业管理软件&#xff0c;SAP Business One…

Python基于Django的校园打印预约系统(附源码,文档说明)

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…

身份证发给别人怎么加水印?赛文奥特曼身份证添加水印教程

我们经常需要使用身份证照片进行身份验证、资料提交等操作。然而&#xff0c;直接将身份证照片发送给他人或上传到网络存在一定的信息泄露风险。为了更好地保护个人隐私&#xff0c;我们可以使用 简鹿水印助手 这款工具&#xff0c;在身份证照片上添加专属水印&#xff0c;从而…

Express教程【002】:Express监听GET和POST请求

文章目录 2、监听post和get请求2.1 监听GET请求2.2 监听POST请求 2、监听post和get请求 创建02-app.js文件。 2.1 监听GET请求 1️⃣通过app.get()方法&#xff0c;可以监听客户端的GET请求&#xff0c;具体的语法格式如下&#xff1a; // 1、导入express const express req…

ESP32-C3 Vscode+ESP-IDF开发环境搭建 保姆级教程

1.背景 最近esp32的芯片很火&#xff0c;因为芯片自带了WIFI和BLE功能&#xff0c;是物联网项目开发的首选芯片&#xff0c;所以&#xff0c;我也想搞个简单的esp32芯片试试看。于是&#xff0c;我设计了一个简单的板子。如下 这块板子很简单&#xff0c;主要的电路来自于乐鑫…