当前位置: 首页 > news >正文

复杂 C++ 项目堆栈保留以及 eBPF 性能分析

在构建和维护复杂的 C++ 项目时,性能优化和内存管理是至关重要的。当我们面对性能瓶颈或内存泄露时,可以使用eBPF(Extended Berkeley Packet Filter)和 BCC(BPF Compiler Collection)工具来分析。如我们在Redis Issue 分析:流数据读写导致的“死锁”问题(1)文中看到的一样,我们用 BCC 的 profile 工具分析 Redis 的 CPU 占用,画了 CPU 火焰图,然后就能比较容易找到耗时占比大的函数以及其调用链。
在这里插入图片描述

个人博客原文地址 复杂 C++ 项目堆栈保留以及 eBPF 性能分析

这里使用 profile 分析的一个大前提就是,服务的二进制文件要保留函数的堆栈信息。堆栈信息是程序执行过程中函数调用和局部变量的记录,当程序执行到某一点时,通过查看堆栈信息,我们可以知道哪些函数被调用,以及它们是如何相互关联的。这对于调试和优化代码至关重要,特别是在处理性能问题和内存泄露时。

但是在实际的项目中,我们用 eBPF 来分析服务的性能瓶颈或者内存泄露的时候,往往会拿不到函数调用堆栈,遇到各种 unknown 的函数调用链。这是因为生产环境为了减少二进制文件的大小,通常不包含调试信息。此外,就算生产环境编译 C++ 代码的时候用了 -g 生成了调试信息,也可能拿不到完整的函数调用堆栈。这里面的原因比较复杂,本文将展开聊一下这个问题。

程序的堆栈信息

在计算机科学中,堆栈(Stack)是一种基本的数据结构,它遵循后进先出(LIFO)的原则。这意味着最后一个被添加到堆栈的元素是第一个被移除的。堆栈在程序设计中有很多用途,其中最常见的是在函数调用和局部变量存储中的应用。

在程序执行过程中,堆栈被用于管理函数调用,这称为“调用堆栈”“执行堆栈”。当一个函数被调用时,一个新的堆栈帧被创建并压入调用堆栈。这个堆栈帧包含:

  1. 返回地址:函数执行完成后,程序应该继续执行的内存地址。
  2. 函数参数:传递给函数的参数。
  3. 局部变量:在函数内部定义的变量。
  4. 帧指针:指向前一个堆栈帧的指针,以便在当前函数返回时恢复前一个堆栈帧的上下文。

当函数执行完成时,其堆栈帧被弹出,控制返回到保存的返回地址。堆栈在内存中的分布如下图:

函数调用堆栈内存分布图

DWARF 格式的堆栈信息

函数调用堆栈的信息在二进制文件中以 DWARF 格式保存。DWARF 是一种用于表示程序的调试信息的标准格式,广泛应用于Unix和Linux系统。它是一种非常灵活和可扩展的格式,能够表示丰富的调试信息,包括但不限于源代码行号、变量名、数据类型、堆栈帧以及它们的关系。

DWARF由一系列的“调试节”组成,每个节包含特定类型的调试信息。比如 .debug_info: 包含关于程序结构的信息,如变量、类型和过程。.debug_line: 包含源代码行号和地址信息的映射,这对于在调试器中定位源代码位置非常有用。可以在 DWARF 官网 上看到具体格式标准,比如当前的 Version 5 版本,有一个 PDF 记录详细的规范。

How debuggers work: Part 3 - Debugging information 这篇文章用实际代码,结合 objdump 和 readelf 工具,深入探讨了 DWARF 调试信息格式,值得一读。

对于 C++ 项目来说,为了在编译时生成包含 DWARF 调试信息的二进制文件,需要使用编译器的编译选项。对于 GCC 和 Clang 编译器,这通常是通过使用 -g 标志来完成的。下面是一个简单的示例代码:

// dwarf.cpp
#include <iostream>void say_hello() {std::cout << "Hello, World!" << std::endl;
}int main() {say_hello();return 0;
}

在生成的 ELF 二进制文件中,我们用 objdump 的 [-h|--section-headers|--headers] 选项,可以打印出所有的 section headers。如果用 -g 编译,生成文件包含 DWARF 调试信息,主要有 debug_aranges.debug_info 等section。没有 -g 选项的时候,生成的二进制文件则没有这些section。

编译带 DWARF 调试信息的 ELF section

如果二进制 ELF 文件带了 DWARF 信息,用 GDB 调试的时候,就可以设置函数行断点、单步执行代码、检查变量值,并查看函数调用堆栈等。此外,传统的性能分析工具 perf,也可以读取 DWARF 信息来解析函数调用堆栈,如下命令即可:

$ perf record --call-graph dwarf ./my_program

Frame Pointer 解析堆栈

虽然 DWARF 信息对于调试非常有用,但基于 eBPF 的工具不能读取 DWARF 里面的堆栈信息。在 eBPF 中使用另外方法读取堆栈信息,那就是帧指针(frame pointer),帧指针可以为我们提供完整的堆栈跟踪。帧指针是 perf 的默认堆栈遍历,也是目前 bcc-tools 或 bpftrace 唯一支持的堆栈遍历技术。

为了在生成的二进制文件中保留帧指针,要确保在编译程序时启用帧指针。这可以通过使用编译器标志来完成,例如在 GCC 中使用 -fno-omit-frame-pointer。下面是一个简单的示例代码:

// fp_demo_write.cpp
#include <unistd.h>
#include <chrono>
#include <thread>void functionA() {const char* message = "Inside functionA\n";write(STDOUT_FILENO, message, 16);// cout 的函数调用堆栈不在 main 中;// std::cout << "Inside functionA" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(10));
}void functionB() {functionA();const char* message = "Inside functionB\n";write(STDOUT_FILENO, message, 16);
}void functionC() {functionB();const char* message = "Inside functionC\n";write(STDOUT_FILENO, message, 16);std::this_thread::sleep_for(std::chrono::milliseconds(10));
}int main() {while (true) {functionC();}return 0;
}

-fno-omit-frame-pointer 编译后,可以用 profile 拿到 cpu 耗时的函数调用堆栈,之后用 FlameGraph 可以拿到 cpu 火焰图。

$ g++ fp_demo_write.cpp -fno-omit-frame-pointer -o fp_demo_write
$ profile -F 999 -U -f --pid $(pgrep fp_demo_write)  60 > fp_demo_write.stack
$ ../FlameGraph/flamegraph.pl fp_demo_write.stack > fp_demo_write.svg

这里 CPU 火焰图如下,可以看到整体函数调用链路,以及各种操作的耗时:
在这里插入图片描述

上面示例函数中,我们用 write(STDOUT_FILENO, message, 16); 来打印字符串,这里一开始用了c++的 std::cout 来打印,结果 cpu 火焰图有点和预期不一样,可以看到和 __libc_start_call_main 同级别的,有一个 unknown 函数帧,然后在这里面有 writestd::basic_ostream<char, std::char_traits<char> >::~basic_ostream() 函数。

在这里插入图片描述

理论上这里所有的函数都应该在 main 的函数栈里面的,但是现在并列有了一个 unknown 的调用堆栈。可能是和 C++ 标准库 glibc 的内部工作方式和缓冲机制有关,在使用 std::cout 写入数据时,数据不会立即写入标准输出,而是存储在内部缓冲区中,直到缓冲区满或显式刷新。这里的输出由 glibc 控制,所以调用堆栈不在 main 中。

如果想验证我们的二进制文件是否有帧指针的信息,可以用 objdump 拿到反汇编内容,然后看函数的开始指令是不是 push %rbp; mov %rsp,%rbp 即可。对于前面的例子,我们可以看到反汇编结果如下:

验证二进制汇编中有帧指针 rbp

GCC/G++ 编译器中,是否默认使用-fno-omit-frame-pointer选项依赖于编译器的版本和目标架构。在某些版本和/或架构上,可能默认保留帧指针。如果没有保留帧指针,生成的二进制汇编代码中就没有相关 rbp 的部分。在我的机器上,默认编译也是有帧指针的,用 -O2 开启编译优化后生成的二进制中就没有帧指针了,如下所示:

二进制汇编中没有帧指针 rbp

再用 profile 来分析的话,就拿不到完整的函数调用栈信息了,如下图:
在这里插入图片描述

在实际的项目开发中,建议在默认编译选项中加上 -fno-omit-frame-pointer,方便后面进行分析。在Linux 发行版 fedora 的 wiki 上可以看到有人就提议,默认开启 Changes/fno-omit-frame-pointer,并列举了这样做的好处以及可能的性能损失。

复杂 C++ 项目编译

上面的例子中都是编译一个简单的 cpp 文件,对于实际项目来说,可能有很多 cpp 文件,同时还有各种复杂的第三方库依赖。如何使最后编译的二进制文件保留完整的堆栈信息,就会变得有挑战。下面我们将重点来看,对于有复杂第三方依赖的项目,编译选项-fno-omit-frame-pointer 如何影响最终生成的二进制文件。

动态链接与静态链接

C++ 项目依赖第三方库有两种链接方式,静态链接和动态链接。静态链接是在编译时将所有库文件的代码合并到一个单一的可执行文件中,这意味着可执行文件包含了它所需要的所有代码,不依赖于外部的库文件。与静态链接不同,动态链接不会将库代码合并到可执行文件中。相反,它在运行时动态地加载库,这意味着可执行文件只包含对库的引用,而不是库的实际代码。

下面是静态链接和动态链接的一些特点:

特点静态链接动态链接
部署难度简单,只需分发一个文件较复杂,需要确保可执行文件能找到依赖的库
启动时间通常更快,因为没有额外的加载开销可能较慢,因为需要在运行时加载库
文件大小通常较大,因为包含所有依赖的代码通常较小,因为只包含对库的引用
内存占用通常较高,每个实例都有其自己的库副本通常较低,多个实例可以共享同一份库的内存
兼容性可以更好地控制版本,因为库是嵌入的,不受外部库更新的影响可能面临兼容性问题,如果外部库更新并且不向后兼容

对于一个大型 C++项目来说,具体选择哪种链接方式可能看团队的权衡。总的来说,项目模块之间所有可能的依赖关系可以归类为下图的几种情形:
在这里插入图片描述

图片由 Graphviz 渲染,图片源码如下:

digraph G {// 设置图的布局方向为从左到右// rankdir=LR;// 设置节点的形状和样式node [shape=box, style=filled, color=lightblue];// 设置边的样式edge [color=blue, fontcolor=black];// 定义节点和边main -> static_X;main -> static_A;main -> dynalic_Y;main -> dynalic_B;main -> utils_cpp;static_X -> static_X1;static_A -> dynalic_A1;dynalic_Y -> dynalic_Y1;dynalic_B -> static_B1;// 设置排名,使相关的节点在同一级{rank=same; static_X; static_A; dynalic_Y; dynalic_B;utils_cpp}
}

这其中最常见的依赖方式是静态链接库依赖其他静态链接库,动态链接库依赖其他动态链接库,后面的分析会基于这两种依赖关系。动态库 A 依赖静态库 B 是可行的,并且在某些情况下是有意义的。例如,如果静态库 B 包含一些不经常变化的代码,而动态库 A 包含一些经常更新的代码。不推荐在静态库 B 中依赖动态库 A,因为静态库通常被视为独立的代码块,不依赖于外部的动态链接。

静态链接的堆栈

接下来我们分析在静态链接情况下,如果中间有第三方依赖没有带编译选项 -fno-omit-frame-pointer,会带来怎么样的影响。

假设有一个 main.cpp 依赖了 utils.cpp 和静态库 static_A,静态库 static_A 依赖了静态库 static_B,这里static_A 编译的时候没带上 -fno-omit-frame-pointer,但是其他都带了-fno-omit-frame-pointer,最终生成的二进制文件中,各静态库和 cpp 文件中的函数会有帧指针吗?这种情况下 eBPF 和 BCC 的工具能最大程度地解析出堆栈信息吗?

我们在本地创建一个完整的示例项目,包含上面的各种依赖关系,代码结构如下,完整代码在 Gist 上:

$ FP_static_demo tree
.
├── main.cpp
├── Makefile
├── static_A
│   ├── static_A.cpp
│   └── static_A.h
├── static_B
│   ├── static_B.cpp
│   └── static_B.h
├── utils.cpp
└── utils.h

然后在编译生成的二进制文件中,发现 static_A 里面的函数没有帧指针,但是 static_B 和其他函数都有帧指针。运行二进制后,用 ebpf 的 profile 命令来分析 cpu 耗时堆栈,命令如下:

$ profile -F 999 -U -f --pid $(pgrep main)  60 > depend_main.stack
$ ./FlameGraph/flamegraph.pl depend_main.stack > depend_main.svg

在生成的 cpu 火焰图中,拿到的函数调用堆栈是错乱的,如下图:

在这里插入图片描述

正常如果没丢失帧指针的话,火焰图应该如下图所示,
在这里插入图片描述
通过上面的实验看到,profile 工具分析性能时,依赖帧指针来重建调用堆栈。即使只丢失中间某个依赖库的帧指针,整体函数的调用堆栈就会错乱,并不是只丢失这中间的部分函数调用堆栈。

还是上面的场景,如果我们在依赖的最底层 static_B 编译的时候不保存堆栈信息,但是其他部分都保存,那么生成的二进制文件中,只有 static_B 中的函数没有帧指针。再次用 profile 分析 cpu 堆栈,发现虽然只是最后一层函数调用没有帧指针,但是 BCC tools 分析拿到的堆栈信息还是有问题,如下图,printStaticAfunction_entry 被混到了同一层。这里多次运行,得到的堆栈信息图还可能不一样,不过都是错误的。
在这里插入图片描述

动态链接的堆栈

动态链接情况下,如果中间有第三方依赖没有带编译选项 -fno-omit-frame-pointer,理论上应该和静态链接一样,堆栈信息会错乱,不过还是写一个例子来验证下。还是上面的 main.cpp 和函数调用关系,把所有静态依赖改成动态依赖,重新改了下目录结构如下:

$ tree
.
├── dynamic_A
│   ├── dynamic_A.cpp
│   └── dynamic_A.h
├── dynamic_B
│   ├── dynamic_B.cpp
│   └── dynamic_B.h
├── main.cpp
├── Makefile
├── utils.cpp
└── utils.h

完整代码还是在 Gist 上。正常堆栈如下图:

在这里插入图片描述

修改 Makefile,只在编译 dynamic_A 的的时候忽略堆栈,生成的 CPU 火焰图如下:

在这里插入图片描述
修改 Makefile,只在编译 dynamic_B 的的时候忽略堆栈,生成的 CPU 火焰图如下:

在这里插入图片描述

和我们前面猜想一致,一旦丢失了部分堆栈信息,分析出来的堆栈图就会有错乱。

参考文章

Practical Linux tracing ( Part 1/5) : symbols, debug symbols and stack unwinding
How debuggers work: Part 3 - Debugging information
Understanding how function call works
Hacking With GDB

相关文章:

复杂 C++ 项目堆栈保留以及 eBPF 性能分析

在构建和维护复杂的 C 项目时&#xff0c;性能优化和内存管理是至关重要的。当我们面对性能瓶颈或内存泄露时&#xff0c;可以使用eBPF&#xff08;Extended Berkeley Packet Filter&#xff09;和 BCC&#xff08;BPF Compiler Collection&#xff09;工具来分析。如我们在Red…...

网安——计算机网络基础

一、计算机网络概述 1、Internet网相关概念及发展 网络&#xff08;Network&#xff09;有若干结点&#xff08;Node&#xff09;和连接这些结点的链路&#xff08;link&#xff09;所组成&#xff0c;在网络中的结点可以是计算机、集线器、交换机或路由器等多个网络还可以通…...

ZCC1923替代BOS1921Piezo Haptic Driver with Digital Front End

FEATURES • High-Voltage Low Power Piezo Driver o Drive 100nF at 190VPP and 250Hz with 490mW o Drives Capacitive Loads up to 1000nF o Energy Recovery o Differential Output o Small Solution Footprint, QFN & WLCSP • Low Quiescent Current: SHUTDOWN; …...

Kutools for Excel 简体中文版 - 官方正版授权

Kutools for Excel 是一款超棒的 Excel 插件&#xff0c;就像给你的 Excel 加了个超能助手。它有 300 多种实用功能&#xff0c;现在还有 AI 帮忙&#xff0c;能把复杂的任务变简单&#xff0c;重复的事儿也能自动搞定&#xff0c;不管是新手还是老手都能用得顺手。有了它&…...

PostgreSQL和MySQL有什么区别?

一、数据存储与管理方面 数据类型支持 PostgreSQL&#xff1a; 提供了非常丰富的数据类型。除了基本的整数、浮点数、字符、日期等类型外&#xff0c;对复杂数据类型的支持很出色。例如&#xff0c;它原生支持数组&#xff08;Array&#xff09;类型&#xff0c;可以方便地存储…...

比较之舞,优雅演绎排序算法的智美篇章

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文一、冒泡排序&#xff1a;数据海…...

C语言数据结构与算法(排序)详细版

大家好&#xff0c;欢迎来到“干货”小仓库&#xff01;&#xff01; 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;无人扶我青云志&#xff0c;我自踏雪至山巅&#xff01;&#xff01;&am…...

JAVA:利用 RabbitMQ 死信队列实现支付超时场景的技术指南

1、简述 在支付系统中&#xff0c;订单支付的超时自动撤销是一个非常常见的业务场景。通常用户未在规定时间内完成支付&#xff0c;系统会自动取消订单&#xff0c;释放相应的资源。本文将通过利用 RabbitMQ 的 死信队列&#xff08;Dead Letter Queue, DLQ&#xff09;来实现…...

pytest+request+yaml+allure搭建低编码调试门槛的接口自动化框架

接口自动化非常简单&#xff0c;大致分为以下几步&#xff1a; 准备入参调用接口拿到2中response&#xff0c;继续组装入参&#xff0c;调用下一个接口重复步骤3校验结果是否符合预期 一个优秀接口自动化框架的特点&#xff1a; 【编码门槛低】&#xff0c;又【能让新手学到…...

Elasticsearch实战指南:从入门到高效使用

Elasticsearch实战指南&#xff1a;从入门到高效使用 1. 引言&#xff1a;Elasticsearch是什么&#xff1f; Elasticsearch是一个分布式、RESTful风格的搜索和分析引擎&#xff0c;广泛应用于全文搜索、日志分析、实时数据分析等场景。它的核心特点包括&#xff1a; 高性能&…...

Open FPV VTX开源之嵌入式OSD配置

Open FPV VTX开源之嵌入式OSD配置 1. 源由2. 安装3. 配置步骤一&#xff1a;备份/etc/telemetry.conf步骤二&#xff1a;修改/etc/telemetry.conf步骤三&#xff1a;配置时区步骤四&#xff1a;重启摄像头 4. 实测5. 参考资料 1. 源由 穿越机模拟图传延迟通常在10ms左右。 最…...

2Hive表类型

2Hive表类型 1 Hive 数据类型2 Hive 内部表3 Hive 外部表4 Hive 分区表5 Hive 分桶表6 Hive 视图 1 Hive 数据类型 Hive的基本数据类型有&#xff1a;TINYINT&#xff0c;SAMLLINT&#xff0c;INT&#xff0c;BIGINT&#xff0c;BOOLEAN&#xff0c;FLOAT&#xff0c;DOUBLE&a…...

计算机网络之---公钥基础设施(PKI)

公钥基础设施 公钥基础设施&#xff08;PKI&#xff0c;Public Key Infrastructure&#xff09; 是一种用于管理公钥加密的系统架构&#xff0c;它通过结合硬件、软件、策略和标准来确保数字通信的安全性。PKI 提供了必要的框架&#xff0c;用于管理密钥对&#xff08;包括公钥…...

EF Core执行原生SQL语句

目录 EFCore执行非查询原生SQL语句 为什么要写原生SQL语句 执行非查询SQL语句 有SQL注入漏洞 ExecuteSqlInterpolatedAsync 其他方法 执行实体相关查询原生SQL语句 FromSqlInterpolated 局限性 执行任意原生SQL查询语句 什么时候用ADO.NET 执行任意SQL Dapper 总…...

GaussDB分布式数据倾斜处理

常规数据倾斜巡检 在库中表个数少于1W的场景&#xff0c;直接使用倾斜视图查询当前库内所有表的数据倾斜情况 SELECT * FROM pgxc_get_table_skewness ORDER BY totalsize DESC;在库中表个数非常多&#xff08;至少大于1W&#xff09;的场景&#xff0c;因PGXC_GET_TABLE_SKEWN…...

代码随想录Day34 | 62.不同路径,63.不同路径II,343.整数拆分,96.不同的二叉搜索树

代码随想录Day34 | 62.不同路径,63.不同路径II,343.整数拆分,96.不同的二叉搜索树 62.不同路径 动态规划第二集&#xff1a; 比较标准简单的一道动态规划&#xff0c;状态转移方程容易想到 难点在于空间复杂度的优化&#xff0c;详见代码 class Solution {public int uniq…...

vue.js辅助函数-mapMutations

在Vue.js中&#xff0c;使用辅助函数可以更方便地使用Vuex的mutation。而mapMutations就是Vuex提供的一个辅助函数&#xff0c;它可以将mutation映射到组件的methods中&#xff0c;使得我们可以在组件中直接调用mutation&#xff0c;而不需要手动进行commit。 mapMutations函数…...

Vue3组件设计模式:高可复用性组件开发实战

Vue3组件设计模式:高可复用性组件开发实战 一、前言 在Vue3中&#xff0c;组件设计和开发是非常重要的&#xff0c;它直接影响到应用的可维护性和可复用性。本文将介绍如何利用Vue3组件设计模式来开发高可复用性的组件&#xff0c;让你的组件更加灵活和易于维护。 二、单一职责…...

PHP 8.4 安装和升级指南

文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons&#xff1a;JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram&#xff0c;自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 &#xff1f; 5 IDEA必装的插件&…...

什么是 OpenResty

1、OpenResty简介 1.1 了解OpenResty OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台&#xff0c;其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。 简单地说OpenRes…...

设计模式——命令设计模式(行为型)

摘要 本文介绍了命令设计模式&#xff0c;这是一种行为型设计模式&#xff0c;用于将请求封装为对象&#xff0c;实现请求的解耦和灵活控制。它包含命令接口、具体命令、接收者、调用者和客户端等角色&#xff0c;优点是解耦请求发送者与接收者&#xff0c;支持命令的排队、记…...

『uniapp』添加桌面长按快捷操作 shortcuts(详细图文注释)

目录 手机环境适配说明安卓效果图代码 iOS(暂未实测,没有水果开发者)总结 欢迎关注 『uniapp』 专栏&#xff0c;持续更新中 欢迎关注 『uniapp』 专栏&#xff0c;持续更新中 手机环境适配说明 个别手机系统可能需要进行特别的权限设置,否则会无法使用 桌面快捷方式: 已知的有…...

什么算得到?什么又算失去?

目录 **一、什么是“得到”&#xff1f;****二、什么是“失去”&#xff1f;****三、得到与失去的悖论****四、如何超越得失二元论&#xff1f;****五、一个更本质的答案** 关于“得到”与“失去”的界定&#xff0c;本质上是对存在状态和主观认知的辩证思考。这两者并非绝对&a…...

8.7 基于EAP-AKA的订阅转移

8.7 基于EAP-AKA的订阅转移 以下场景描述如下情况&#xff1a; • 主ODSA设备应用程序被允许用于该类型主设备&#xff0c;且已获得服务提供商&#xff08;SP&#xff09;授权。 • 终端用户在存有活跃订阅的旧主设备上发起订阅转移请求&#xff0c;且可访问eSIM数据。 • 由于…...

HTML实战:爱心图的实现

设计思路 使用纯CSS创建多种风格的爱心 添加平滑的动画效果 实现交互式爱心生成器 响应式设计适应不同设备 优雅的UI布局和色彩方案 <!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"UTF-8"> <meta nam…...

机器学习有监督学习sklearn实战二:六种算法对鸢尾花(Iris)数据集进行分类和特征可视化

本项目代码在个人github链接&#xff1a;https://github.com/KLWU07/Machine-learning-Project-practice 六种分类算法分别为逻辑回归LR、线性判别分析LDA、K近邻KNN、决策树CART、朴素贝叶斯NB、支持向量机SVM。 一、项目代码描述 1.数据准备和分析可视化 加载鸢尾花数据集&…...

【HW系列】—安全设备介绍(开源蜜罐的安装以及使用指南)

文章目录 蜜罐1. 什么是蜜罐&#xff1f;2. 开源蜜罐搭建与使用3. HFish 开源蜜罐详解安装步骤使用指南关闭方法 总结 蜜罐 1. 什么是蜜罐&#xff1f; 蜜罐&#xff08;Honeypot&#xff09;是一种主动防御技术&#xff0c;通过模拟存在漏洞的系统或服务&#xff08;如数据库…...

[001]从操作系统层面看锁的逻辑

从操作系统层面&#xff0c;锁 (Lock) 是一种同步机制&#xff0c;用于控制多个线程或线程对共享资源的访问&#xff0c;防止竞态条件(race condition).常见的锁包括互斥锁&#xff08;mutex&#xff09;、读写锁(read-write lock)、自旋锁&#xff08;spinlock&#xff09;等。…...

[git每日一句]Your branch is up to date with ‘origin/master‘

这句话是 Git 版本控制系统的提示信息&#xff0c;意思是&#xff1a; "你当前所在的分支已经与远程仓库&#xff08;origin&#xff09;的 master 分支同步&#xff0c;没有需要推送的提交。" 详细解释&#xff1a; Your branch - 指你当前所在的本地分支 is up …...

数据结构(7)树-二叉树-堆

一、树 1.树的概述 现实生活中可以说处处有树。 在计算机里&#xff0c;有一种数据结构就是像现实中的树一样&#xff0c;有根&#xff0c;有分支&#xff0c;有叶子&#xff1b;一大片树就叫做森林。 这些性质抽象到计算机里也叫树&#xff0c;大致长这个样子&#xff1a; …...