记一次 Golang pkg 性能提升 38147125738.8 倍之旅

我正在使用 linux-open-ports 项目来获取系统上当前打开的端口列表。不过我注意到实现速度比我预期的要慢一点,所以我开始调查、分析、修改并使整个过程,使其性能提升了 38147125738.8 倍。下面我对整个过程做一个详细叙述。

1

 

基线

Go 提供了通过基准测试建立基线的工具,因此让我们利用它并编写一个基准测试:

package linuxopenportsimport "testing"func BenchmarkGetOpenPorts(b *testing.B) {	ports, err := GetOpenPorts()	if err != nil {		b.Fatal(err)	}	if len(ports) == 0 {		b.Fatal("no ports detected")	}}

这在我的系统上产生的结果:

goos: linuxgoarch: amd64pkg: github.com/intevel/linux-open-portscpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M GraphicsBenchmarkGetOpenPortsBenchmarkGetOpenPorts-16               1        1570580328 ns/opPASSok      github.com/intevel/linux-open-ports     1.588s

2

 

pkg 的概念

在基于 Linux 的系统中获取开放端口列表的方法非常容易理解:

  1. 获取所有连接及其 inode 的列表,这些存储在:

/proc/net/tcp/proc/net/tcp6/proc/net/udp/proc/net/udp6

以上所有内容均遵循相同的格式:

~ :: head -n1 < /proc/net/tcpsl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode 0: 3600007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000   991        0 15761 1 0000000000000000 100 0 0 10 5

这里没有进程 ID(PID),我们必须将所有找到的进程映射到系统上的 inode 相应进程 pids

  1. 读取每个/proc//fd 目录以访问其套接字 inode(格式的文件 socket:[])

  2. 返回端口及其 inode 和进程 ID 的列表

3

 

深入探索

linux-open-ports 导出一个函数 GetOpenPorts 和相应的类型 OpenPort:

type OpenPort struct {	Protocol string	Port     int	PID      int}func GetOpenPorts() ([]OpenPort, error) {    // ...}

GetOpenPorts 调用 findPIDByInode 每个文件中的每个 inode:

func GetOpenPorts() ([]OpenPort, error) {	var openPorts []OpenPort	uniquePorts := make(map[string]bool)	protocolFiles := map[string][]string{		"tcp": {"/proc/net/tcp", "/proc/net/tcp6"},		"udp": {"/proc/net/udp", "/proc/net/udp6"},	}	for protocol, files := range protocolFiles {		for _, filePath := range files {            // .. scanner setup and error handling			for scanner.Scan() {				fields := strings.Fields(scanner.Text())                // .. field checks and assignments				inode := fields[9]				pid := findPIDByInode(inode)                // ..			}		}	}	return openPorts, nil}

该 findPIDByInode 函数在每次调用时读取整个/proc 目录来查找 inode 所属的进程。

func findPIDByInode(inode string) int {	procDirs, _ := os.ReadDir("/proc")	for _, procDir := range procDirs {		if !procDir.IsDir() || !isNumeric(procDir.Name()) {			continue		}		pid := procDir.Name()		fdDir := filepath.Join("/proc", pid, "fd")		fdFiles, err := os.ReadDir(fdDir)		if err != nil {			continue		}		for _, fdFile := range fdFiles {			fdPath := filepath.Join(fdDir, fdFile.Name())			link, err := os.Readlink(fdPath)			if err == nil && strings.Contains(link, fmt.Sprintf("socket:[%s]", inode)) {				pidInt, _ := strconv.Atoi(pid)				return pidInt			}		}	}	return -1}func isNumeric(s string) bool {	_, err := strconv.Atoi(s)	return err == nil}

4

 

解析整数使用不当

第一个引起我注意的简单方法是 isNumeric 函数调用,它尝试解析目录名称并检查它是否为数字,以确保目录是进程的目录。使用 unicode.IsDigit 判断目录名称的第一个字节应该足够且更快:

func findPIDByInode(inode string) int {	procDirs, _ := os.ReadDir("/proc")	for _, procDir := range procDirs {		pid := procDir.Name()		if !procDir.IsDir() || !unicode.IsDigit(rune(pid[0])) {			continue		}		fdDir := filepath.Join("/proc", pid, "fd")		fdFiles, err := os.ReadDir(fdDir)		if err != nil {			continue		}		for _, fdFile := range fdFiles {			fdPath := filepath.Join(fdDir, fdFile.Name())			link, err := os.Readlink(fdPath)			if err == nil && strings.Contains(link, fmt.Sprintf("socket:[%s]", inode)) {				pidInt, _ := strconv.Atoi(pid)				return pidInt			}		}	}	return -1}
goos: linuxgoarch: amd64pkg: github.com/intevel/linux-open-portscpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M GraphicsBenchmarkGetOpenPortsBenchmarkGetOpenPorts-16               1        1108555474 ns/opPASSok      github.com/intevel/linux-open-ports     1.123s

基准测试记录从 1570580328 ns/op 到 1108555474 ns/op (1.57s 到 1.11s, 快了 0.46s 或者 1.41x ).

5

 

让我们缓存 Map 并获得 38147125738.8 倍的加速

了解情况的读者会注意到,/proc/每次遇到新的 inode 时迭代目录并不是那么明智。相反,我们应该只迭代一次,然后在 inode 到 pid 的 map 中查找找到的 inode:

func inodePIDMap() map[string]string {	m := map[string]string{}	procDirs, _ := os.ReadDir("/proc")	for _, procDir := range procDirs {		pid := procDir.Name()		if !procDir.IsDir() && !unicode.IsDigit(rune(pid[0])) {			continue		}		fdDir := filepath.Join("/proc", pid, "fd")		fdFiles, err := os.ReadDir(fdDir)		if err != nil {			continue		}		for _, fdFile := range fdFiles {			path := filepath.Join(fdDir, fdFile.Name())			linkName, err := os.Readlink(path)			if err != nil {				continue			}			if strings.Contains(linkName, "socket") {				// index 8:till end -1 because socket:[ is 8 bytes long and ]				// is at the end				inode := linkName[8 : len(linkName)-1]				m[inode] = pid			}		}	}	return m}

GetOpenPorts 必须更新该功能以匹配此更改:

func GetOpenPorts() ([]OpenPort, error) {	var openPorts []OpenPort	uniquePorts := make(map[string]bool)	protocolFiles := map[string][]string{		"tcp": {"/proc/net/tcp", "/proc/net/tcp6"},		"udp": {"/proc/net/udp", "/proc/net/udp6"},	}	cachedInodePIDMap := inodePIDMap()	for protocol, files := range protocolFiles {		for _, filePath := range files {            // .. scanner setup and error handling			for scanner.Scan() {				fields := strings.Fields(scanner.Text())                // .. field checks and assignments				inode := fields[9]				pid, ok := cachedInodePIDMap[inode]				if !ok {					continue				}                // ..			}		}	}	return openPorts, nil}

加速比是巨大的,从 1108555474 ns/op 到 0.02906 ns/op,对应于 38147125739.848587x 加速比。

  • 旧基准

goos: linuxgoarch: amd64pkg: github.com/intevel/linux-open-portscpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M GraphicsBenchmarkGetOpenPortsBenchmarkGetOpenPorts-16               1        1108555474 ns/opPASSok      github.com/intevel/linux-open-ports     1.123s
  • 新基准

goos: linuxgoarch: amd64pkg: github.com/intevel/linux-open-portscpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M GraphicsBenchmarkGetOpenPorts-16        1000000000               0.02906 ns/opPASSok      github.com/intevel/linux-open-ports     0.235s

6

 

总结

我会进一步研究这个问题,但上述更改已经带来了如此大的性能提升,我并没有打算就此打住。我的下一步将是删除 fmt.Sprintffor 循环主体中的。在此之前,我会启动一个 pprof 分析器并调查热点函数。

推荐


如何将 Docker 镜像的大小减少 99.82%


24 年最快的 REST API Web 服务器:Node.js、Go、Rust 和 C# (.NET) 基准测试


原创不易,随手关注或者”在看“,诚挚感谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/888338.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mean,median,mode,var,std,min,max函数

剩余的函数都放在这篇里面吧 m e a n mean mean函数可以求平均值 a a a为向量时&#xff0c; m e a n ( a ) mean(a) mean(a)求向量中元素的平均值 a a a为矩阵时&#xff0c; m e a n ( a , 1 ) mean(a,1) mean(a,1)求矩阵中各列元素的平均值&#xff1b; m e a n ( a , 2 )…

带Burst AOT Settings移植问题

报错 burst问题 Burst AOT Settings 是 Unity 的 Burst Compiler 的一部分&#xff0c;用于预编译程序集&#xff08;AOT&#xff0c;Ahead-Of-Time Compilation&#xff09;&#xff0c;以便在不支持 JIT&#xff08;即时编译&#xff09;的平台上运行&#xff0c;例如 iOS 和…

Android studio 签名加固后的apk文件

Android studio打包时&#xff0c;可以选择签名类型v1和v2&#xff0c;但是在经过加固后&#xff0c;签名就不在了&#xff0c;或者只有v1签名&#xff0c;这样是不安全的。 操作流程&#xff1a; 1、Android studio 对项目进行打包&#xff0c;生成有签名的apk文件&#xff…

数据结构—队列

目录 一、队列的定义 二、队列的顺序储存结构 2.1顺序队列的定义 2.2循环队列定义 2.3循环队列的基本操作 三、队列的链式储存结构 3.1链队列的定义 3.2链队列的基本操作 一、队列的定义 队列是一种线性表&#xff0c;其特殊性在于队列的基本操作是线性表的子表。队列…

【计算机网络】实验2:总线型以太网的特性

实验 2&#xff1a;总线型以太网的特性 一、 实验目的 加深对MAC地址&#xff0c;IP地址&#xff0c;ARP协议的理解。 了解总线型以太网的特性&#xff08;广播&#xff0c;竞争总线&#xff0c;冲突&#xff09;。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实…

PHP RabbitMQ连接超时问题

问题背景 Error: The connection timed out after 3 sec while awaiting incoming data 看到这个报错&#xff0c;我不以为意&#xff0c;认为是我设置的超时时间不够导致的&#xff0c;那就设置长一点 Error: The connection timed out after 300 sec while awaiting incom…

asp.net core过滤器应用

筛选器类型 授权筛选器 授权过滤器是过滤器管道的第一个被执行的过滤器&#xff0c;用于系统授权。一般不会编写自定义的授权过滤器&#xff0c;而是配置授权策略或编写自定义授权策略。简单举个例子。 using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCo…

缓存与数据库数据一致性 详解

缓存与数据库数据一致性详解 在分布式系统中&#xff0c;缓存&#xff08;如 Redis、Memcached&#xff09;与数据库&#xff08;如 MySQL、PostgreSQL&#xff09;一起使用是提高系统性能的常用方法。然而&#xff0c;缓存与数据库可能因更新时序、操作失误等原因出现数据不一…

Linux DNS解释器

作用 DNS&#xff08;Domain Name System&#xff09;是互联网上的一项服务&#xff0c;用于将域名和IP地址进行相互映射&#xff0c;使人 更方便的访问互联网 正向解析&#xff1a;域名->IP 反向解析&#xff1a;IP->域名 连接方式 DNS使用53端口监听网络 查看方法&a…

3.STM32通信接口之SPI通信---SPI实战(W25Q64存储模块介绍)《精讲》

上一节介绍了SPI的通信过程和方法&#xff0c;接下来就要进行STM32与外围模块通信了&#xff0c;这个模块是一块非易失型存储芯片&#xff0c;能够提供8MB的存储空间。接下来跟着Whappy脚步&#xff0c;进行探索新大陆吧&#xff01;【免费】W25Q64(中英文数据手册)资源-CSDN文…

嵌入式系统应用-LVGL的应用-平衡球游戏 part2

平衡球游戏 part2 4 mpu60504.1 mpu6050 介绍4.2 电路图4.3 驱动代码编写 5 游戏界面移植5.1 移植源文件5.2 添加头文件 6 参数移植6.1 4 mpu6050 4.1 mpu6050 介绍 MPU6050是一款由InvenSense公司生产的加速度计和陀螺仪传感器&#xff0c;广泛应用于消费电子、机器人等领域…

java将word docx pdf转换为图片(不需要额外下载压缩包,直接导入maven坐标)

(本代码实现的是将第1页转为图片&#xff0c;主要用于制作文件缩略图) pdf转图片容易 docx转图片麻烦&#xff0c;看其他博客可以直接导入maven坐标&#xff0c;但我知道那是需要付费且有时限的包 本着简单实用的心&#xff0c;我找到法子了 pdf转图片&#xff1a;有库直接转…

C#学写了一个程序记录日志的方法(Log类)

1.错误和警告信息单独生产文本进行记录&#xff1b; 2.日志到一定内存阈值可以打包压缩&#xff0c;单独存储起来&#xff0c;修改字段MaxLogFileSizeForCompress的值即可&#xff1b; 3.Log类调用举例&#xff1a;Log.Txt(JB.信息,“日志记录内容”,"通道1"); usi…

Qt—QLineEdit 使用总结

文章参考:Qt—QLineEdit 使用总结 一、简述 QLineEdit是一个单行文本编辑控件。 使用者可以通过很多函数,输入和编辑单行文本,比如撤销、恢复、剪切、粘贴以及拖放等。 通过改变 QLineEdit 的 echoMode() ,可以设置其属性,比如以密码的形式输入。 文本的长度可以由 m…

linux(centos) 环境部署,安装JDK,docker(mysql, redis,nginx,minio,nacos)

目录 1.安装JDK (非docker)1.1 将文件放在目录下&#xff1a; /usr/local/jdk1.2 解压至当前目录1.3 配置环境变量 2.安装docker2.1 验证centos内核2.2 安装软件工具包2.3 设置yum源2.4 查看仓库中所有docker版本&#xff0c;按需选择安装2.5 安装docker2.6 启动docker 并 开机…

电阻改善信号完整性

1.为什么电路端接电阻能改善信号完整性 由于电信号在PCB上传输&#xff0c;因此在PCB设计中可以把PCB走线认为是信号的通道&#xff0c;当该通道的 物理结构&#xff08;线宽&#xff0c;线到参考面的距离等&#xff09;发生变化&#xff0c;特别是有一些突变时&#xff0c;都会…

Java基础面试题,46道Java基础八股文(4.8万字,30+手绘图)

Java是一种广泛使用的编程语言&#xff0c;由Sun Microsystems&#xff08;现为Oracle Corporation的一部分&#xff09;在1995年首次发布。它是一种面向对象的语言&#xff0c;这意味着它支持通过类和对象的概念来构造程序。 Java设计有一个核心理念&#xff1a;“编写一次&am…

redisson-spring-data与Spring-Data-Redis的版本关系问题

redisson-spring-boot-starter https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter https://github.com/redisson/redisson/tree/master/redisson-spring-data#spring-data-redis-integration 将 Redisson 与 Spring Boot 库集成。依赖于Spring…

记录下nginx接口代理配置问题

其中api和api1是前面定义的upstream&#xff0c;ip相同只是端口不同。 一开始/api1/直接 像api一样 proxy_pass http://api1这样是不行的&#xff0c;因为会代理到 后端的 /api1/...接口&#xff0c;而后端实际接口地址是 /api/..... 所以必须像上面写法才能将外网的 /api…

高效流程图绘制:开发设计流程图利器

在选择画流程图的工具时&#xff0c;不同的项目和使用场景会决定最佳的工具。以下是几款常见的流程图工具&#xff0c;并结合具体项目使用场景提供建议&#xff1a; 1. Lucidchart 特点&#xff1a; 在线协作&#xff1a;支持多人实时协作&#xff0c;适合团队合作。模板丰富&…