0%

Python常用工具库

包管理/环境

  • uv: rust写的, 非常好用, 基本都迁移到这个了
  • pyinstaller: 打包可执行文件
  • jupyter notebook: 非常好用的交互式python环境

工具

  • mypy: 类型检查
  • FastAPI/Typer: 类似Fastapi一样快速构建Cli命令
  • rich: 支持表格/进度条/语法高亮等富文本输出
  • tqdm: 非常方便的进度条, 也可以在命令行中使用
  • pydantic: FastApi底层的类型解析, 非常方便 dict, json等格式化, 再也不用手撸__dict__
  • difflib: 文本差异比较
  • PyYAML: yaml解析器
  • sh: 命令行调用

API

  • FastAPI: 替代 Flask, 原生支持异步
  • aiohttp: 基于 asyncio 的异步 HTTP 网络库
  • requests: 人性化的 HTTP 请求库
  • grequests: requests 库 + gevent 用于异步 HTTP 请求
  • httpie: curl 的替代
  • Jinja2: 一个现代的, 对设计师友好的模板引擎
  • celery: 一个异步任务队列/作业队列, 基于分布式消息传递

GUI

  • pyqt
  • tkinter

机器学习/数据处理

  • pillow: 图形处理, Pillow 是一个更加易用版的 PIL

数据可视化

  • matplotlib: 基础绘图库,支持各种静态/交互式图表(折线图/散点图/柱状图等)
  • seaborn: 基于matplotlib的高级统计可视化,内置美观主题和复杂图表类型

数据处理与计算

  • numpy: 数值计算基础库,提供高效多维数组(ndarray)和数学函数
  • pandas: 数据结构化处理工具,DataFrame为核心,支持数据清洗/分析/IO操作
  • scipy: 科学计算工具包,包含优化/积分/插值/统计等高级数学模块

机器学习框架

  • scikit-learn: 传统机器学习库,包含分类/回归/聚类等算法及评估工具

开发环境

  • jupyter: 交互式笔记本环境,支持代码/文档/可视化混合编排

PyTorch生态

  • torch: PyTorch深度学习框架,支持动态计算图和GPU加速
  • torchvision: 计算机视觉工具库,包含数据集/模型架构/图像变换方法
  • torchaudio: 音频处理工具库,支持语音数据集和特征提取
  • pytorch-lightning: PyTorch轻量级封装,简化训练流程和分布式训练

强化学习专用库

  • stable-baselines3: 最主流的RL实现库(PPO/DQN/SAC等算法)

其他重要库

  • sb3-contrib: Stable-Baselines3扩展包(额外算法实现)
  • mujoco: 物理仿真引擎(复杂机器人控制环境)
  • openai: 调用llm模型的通用接口
  • gymnasium[all]: 一些游戏的env实现, 方便强化学习使用, 依赖swig&c++编译环境, 手动安装 : pacman -Sy swig

资料

基本概念

minikube

  • 一种本地部署k8s集群的方案, 底层使用docker/qemu/VirtualBox等驱动
  • 使用docker时, 使用了Docker in Docker 的方案, 在docker内部再拉起一个dockerd

k8s概念

  • kubectl: 一个工具, 把各种命令转换为http 请求, 转发给kube-apiserver, 并将结果反向转换
  • 控制平面(Control plane) : k8s集群的管理和调度
  • 节点(Node): 具体运行某个进程的机器/虚拟机, 部署了k8s控制需要的进程
  • Pod: k8s调度的最小单位, 由一个或者多个容器构成, 单个pod在单个node上运行, 可以通过localhost互相访问
  • ReplicaSet: 确保指定数量的 Pod 副本(Replicas)始终在集群中运行, 挂掉的自动拉起, 多出的自动删除, 一般由Deployment管理
  • Deployment: 提供声明式的 Pod 部署管理,支持滚动更新、版本回滚、暂停/恢复更新等高级功能, Deployment 是比 ReplicaSet 更高层的抽象,封装了 ReplicaSet 的创建和更新逻辑
    • Deployment 通过创建和管理多个 ReplicaSet 来实现版本控制。每次更新会生成一个新 ReplicaSet,旧 ReplicaSet 默认保留以供回滚
  • Service: 为 pod 提供一个稳定的 Endpoint, Service 位于 pod 的前面,负责接收请求并将它们传递给它后面的所有pod

k8s控制平面(Control plane components )

  • k8s集群的管理和调度

kube-apiserver

  • 控制面的管理入口, 接受http请求

etcd

  • k8s自身的数据存储

kube-scheduler

  • k8s的决策调度核心, 负责决策哪些pod要到那些node上运行等逻辑

kube-controller-manager

  • 负责运行控制器进程, 各种控制器都在这里, 常见的有:
    • Node Controller : 负责监控node截图, 故障时通知
    • ReplicaSet Coltroller: 负责确保 Deployment 或 ReplicaSet 指定的 Pod 副本数始终符合预期,自动创建或删除 Pod
    • Deployment Coltroller: 管理 Deployment 的生命周期(如滚动更新、回滚),通过控制 ReplicaSet 实现应用的无缝升级
    • Service Account Controller: 为每个命名空间创建默认服务账户,并确保其 Token 和配置存在。
    • Endpoint Controller: 维护 Service 与 Pod 的关联关系,动态更新 Endpoints 对象以反映当前匹配的 Pod IP 和端口。
    • Job/CronJob: 执行一次性任务(Job)或定时任务(CronJob),确保任务按预期完成或周期性运
    • PersistentVolume Controller: 绑定 PersistentVolume(PV)与 PersistentVolumeClaim(PVC),处理存储卷的生命周期(如创建/删除云存储)
    • DaemonSet Controller: 确保每个符合条件的节点运行一个指定的 Pod(如日志收集组件)。
    • StatefulSet Controller: 管理有状态应用的部署,保障 Pod 的唯一性、有序性和稳定的网络标识及存储

cloud-controller-manager

  • 云平台定制的一些控制器

节点和节点组件(Node components)

  • node 是具体负责运行进程的某个机器/虚拟机

kubelet

  • 接收一组通过各类机制提供给它的 PodSpec,确保这些 PodSpec 中描述的容器处于运行状态且健康

kube-proxy

  • 集群中每个节点(node)上所运行的网络代理, 实现service网络通信

Container runtime

  • 容器运行时, 符合CRI的容器实现

安装minikube

  • 提前安装好docker并启动, 用docker作为虚拟集群的驱动
1
2
3
4
5
6
7
8
rpm系列:
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-latest.x86_64.rpm
rpm -Uvh minikube-latest.x86_64.rpm

dpkg系列:
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_latest_amd64.deb
dpkg -i minikube_latest_amd64.deb

启动minikube(docker)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 非root用户把当前用户加入docker用户组
sudo usermod -aG docker $USER && newgrp docker

# root用户, 执行时额外加上`--force`强制执行

# docker作为驱动, 默认单节点方式
minikube start -d docker

# 额外启动两个node节点
minikube start -n3 -d docker

# 检查启动状态
minikube kubectl -- get pods -A

启动dashbord

  • 没有放在后台, 方便随时停止
1
2
3
4
5
# 可选: 启动额外的监控
minikube addons enable metrics-server

# 启动dashbord, 要拉取一些镜像, 比较慢
minikube dashboard --url
  • 把url 复制到本地浏览器中

远程Linux机器转发dashbord

  • dashbord默认没有暴漏在公网, 远程Linux启动的话只能在远程机器上访问
  • 如果允许开启ssh 隧道的话, 直接ssh遂穿到本地就行ssh -L 40439:localhost:40439 root@aaabbb
  • 或者把dashbord 直接暴漏在公网, 比较危险

x11转发远程窗口方式

  • 由于安全策略, 禁止任何ssh隧道, 这里直接使用x11转发浏览器窗口

远端开启x11转发

1
2
3
4
5
6
7
8
9
10
vim /etc/ssh/ssh_config

#打开下面的选项:
X11Forwarding yes
X11DisplayOffset 10
X11UseLocalhost yes

#重启sshd
systemctl restart sshd

远端安装firefox, xclock

1
dnf install xclock firefox

本地安装putty+VcXsrv

  • vcxsrv
  • putty
  • 或者 mobaxterm 整合了两个, 省去配置, 不过是商业软件, 注意版权
  • RDP/VNC等方式就要自己折腾了

配置VcXsrv

  • multiple windows 模式, display number 设置为0吧, 不自动探测了
  • 关闭访问控制(disable access control)
  • 启动后放在后台等待

配置putty并启动firefox, 打开远端的localhost网页

hello node 测试

1
2
3
4
5
6
7
# 创建
kubectl create deployment hello-node --image=registry.k8s.io/e2e-test-images/agnhost:2.39 -- /agnhost netexec --http-port=8080
# 暴漏服务
kubectl expose deployment hello-node --type=LoadBalancer --port=8080
# 查看地址
kubectl get services
minikube service hello-node

常用命令

  • 一个常用的命令: 同步文件夹到另一台机器, 限制1M/s的传输速度, 使用3600端口, 并显示进度
    • rsync -avzh -e "ssh -p 36000" --bwlimit 1000 --progress ./libgo root@127.0.00.1:/data/root
  • 本机同步文件夹, 基本替代cp命令实现增量同步, 简单好用
    • rsync -avh ./libgo ./libgo_1

选项说明

  • -a 类似 -r 递归并同步元信息
  • -v 产看同步结果
  • -z 传输时压缩, 通过网络时可以带上
  • -h 以可以阅读的方式输出
  • -e “ssh -p 36000” 使用ssh传输, 并使用36000端口
  • –bwlimit 1000 限制传输速度, 在一些线网服务器同步日志时很有用, 防止磁盘/网络被占满
  • –progress 显示进度, 注意: 只显示单个文件的

一些其他常用选项

  • -n 不真实执行, 只模拟下
  • –delete dest端多出来的文件删掉
  • –exclude {‘.h’,’libgo/third_party/‘} 排除部分文件
  • –include 增加文件, 注意: 一个文件同时被排除/包含命中, 第一个生效

问题与怀疑

  • 偶然出现一个问题, 单线程无锁寻路组件, 在多线程执行只, 性能劣化了500倍往上
    • 单次寻路每次在10ms左右, 但是16核机器开了32个线程后, 单次寻路劣化到了2s左右
      • 寻路请求有个前置队列, 排队处理, 理论上无论多大的QPS压力, 寻路核心都会安装固定的速度处理, 单次寻路时间理应不变
    • 业务逻辑是完全无锁的
    • 劣化后运行过程中CPU被完全吃满
    • 劣化后火焰图中有个奇怪的自旋锁占用
  • 寻路过程使用了dijkstra/jps/A*等多个算法, 只有dijkstra会劣化, 其他的都不会, 基本确认是dijkstra的问题
    • 进过排查发现有一个500*500左右的二维矩阵, 调试时直接输出到了std::cout, 服务器版本没有改为log组件控制
      • 矩阵中的每一行都直接使用std::cout输出
    • 服务器把std::cout 重定向到了 /dev/null, 所有平时看不到它的输出
    • 怀疑是std::cout输出时会自旋锁

验证

  • 构造一个1000*1000的二维矩阵, 分别使用单线程/多线程打印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 打印矩阵
void Print(std::ostream &oss, const std::string &rawGap,
const std::string &colGap) {
for (int curr_raw = 0; curr_raw < m_raw; ++curr_raw) {
for (int curr_col = 0; curr_col < m_col; ++curr_col) {
oss << m_data[curr_raw][curr_col] << colGap;
}
oss << "\n";
}
}
void Print() {
std::cout << "print double dim array" << std::endl;
Print(std::cout, "\n", "\t");
std::cout << "\n"
<< "end print double dim array" << std::endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void PrintMatrix(std::ostream& timeOs, MatrixType& m, int count)
{
for (int i = 0; i < count; ++i)
{
TimeGap gap{};
m.Print();
timeOs << "time use " << gap.gap() << "us " << gap.gapMs() << "ms" << std::endl;
}
}
TEST_CASE("cout_test")
{
auto m = std::make_shared<MatrixType>(1000, 1000);
for (int x = 0; x < 1000; ++x)
{
for (int y = 0; y < 1000; ++y)
{
m->Set(x, y, x * 100000 + y);
}
}
std::cerr << "single " << std::endl;
// 单线程
PrintMatrix(std::cerr, *m, 20);
std::cerr << "multi " << std::endl;

// 多线程
tars::TC_ThreadPool m_threadPool{};
m_threadPool.init(get_nprocs() * 2);
m_threadPool.start();
for (int i = 0; i < 1000; ++i)
{
m_threadPool.exec([m]() { PrintMatrix(std::cerr, *m, 1); });
}
m_threadPool.waitForAllDone();
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
./PathSearchServer --unittest --tc=cout_test 1>/dev/null
single
time use 76835us 77ms
time use 76088us 76ms
time use 74894us 75ms
time use 73033us 73ms
time use 74418us 74ms
time use 74521us 75ms
time use 73480us 73ms
time use 73446us 73ms
time use 73105us 73ms
time use 74032us 74ms
time use 72463us 72ms
time use 72522us 73ms
time use 72438us 72ms
time use 72267us 72ms
time use 72322us 72ms
time use 72225us 72ms
time use 72128us 72ms
time use 72110us 72ms
time use 72360us 72ms
time use 72302us 72ms
multi
time use 24006429us 24006ms
time use 24015438us 24015ms
time use 24016567us 24017ms
time use 24019720us 24020ms
time use 24056568us 24057ms
time use 24113107us 24113ms
time use 24133636us 24134ms
time use 24140334us 24140ms
time use 24141262us 24141ms
time use 24145302us 24145ms
time use 24193464us 24193ms
time use 24222469us 24222ms
time use 24223077us 24223ms
time use 24235170us 24235ms
time use 24245044us 24245ms
time use 24252006us 24252ms
time use 24258551us 24259ms
time use 24258875us 24259ms
time use 24277297us 24277ms
...
  • 可以看到
    • 在cpu数量*2的压力下, 矩阵打印速度慢了320倍左右, 刚好是线程数*10
    • cpu满负载运行

结论

  • liunx c 输出会加速
  • 如果输出速度过快, 单次输出时间过长, 会导致所有线程自旋等待锁
  • cpu满负载, 运行速度大幅度降低, 线程数越多,降低的越多
  • 可以从GLibc源码确认
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
size_t
_IO_fwrite (const void *buf, size_t size, size_t count, FILE *fp)
{
size_t request = size * count;
size_t written = 0;
CHECK_FILE (fp, 0);
if (request == 0)
return 0;
_IO_acquire_lock (fp);
if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
written = _IO_sputn (fp, (const char *) buf, request);
_IO_release_lock (fp);
/* We have written all of the input in case the return value indicates
this or EOF is returned. The latter is a special case where we
simply did not manage to flush the buffer. But the data is in the
buffer and therefore written as far as fwrite is concerned. */
if (written == request || written == EOF)
return count;
else
return written / size;
}
libc_hidden_def (_IO_fwrite)

  • Linux 源码编译python依赖:
  • apt install build-essential gdb lcov libbz2-dev libffi-dev libgdbm-dev liblzma-dev libncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev lzma lzma-dev tk-dev uuid-dev zlib1g-dev
  • yum -y install wget xz tar gcc make tk-devel sqlite-devel zlib-devel readline-devel openssl-devel curl-devel tk-devel gdbm-devel xz-devel bzip2-devel libffi-devel
  • 少了libffi-dev之类的可能可以编译, 但是运行报错
  • ./configure --prefix=/usr/local/python3 && make -j32 && make install

1
2
3
4
# cv 点, 圆
cv2.circle(img, (100, 50), 10, 150, -1)
# np 坐标点, 矩形
img[90:100, 50:60] = 0
  • numpy 数组访问, shape 返回等, 都是 [行, 列] 的方式
  • opencv 对传入点的处理, 是按照[列, 行]理解的
  • 混合使用时要注意坐标转换

1
2
3
4
add_library(before_main OBJECT ${BOOTSTRAP_DIR}/common/before_main_funcs/before_main.cpp )

/usr/bin/c++ -ldl -fPIC -ggdb3 -g3 -fno-omit-frame-pointer -g CMakeFiles/src_cpp_language__template.dir/main.cpp.o ../../CMakeFiles/before_main.dir/cpp_bootstrap/common/before_main_funcs/before_main.cpp.o -o ../../bin/src_cpp_language__template -lpthread /usr/lib/x86_64-linux-gnu/libgtest.a /usr/lib/x86_64-linux-gnu/libboost_regex.a /usr/lib/x86_64-linux-gnu/libboost_filesystem.a

1
2
3
4
5
6
7
    add_library(before_main STATIC ${BOOTSTRAP_DIR}/common/before_main_funcs/before_main.cpp )

list(APPEND LIBS_LIST "before_main")
list(APPEND LIBS_LIST "-Wl,--whole-archive ../../bin/libbefore_main.a -Wl,--no-whole-archive")

/usr/bin/c++ -ldl -fPIC -ggdb3 -g3 -fno-omit-frame-pointer -g CMakeFiles/src_cpp_language__template.dir/main.cpp.o -o ../../bin/src_cpp_language__template ../../bin/libbefore_main.a -Wl,--whole-archive ../../bin/libbefore_main.a -Wl,--no-whole-archive -lpthread /usr/lib/x86_64-linux-gnu/libgtest.a /usr/lib/x86_64-linux-gnu/libboost_regex.a /usr/lib/x86_64-linux-gnu/libboost_filesystem.a

总结

  • 测试cpo与tag_invoke时, 反汇编发现有些理应inline的函数, 并没有真的inline, 于是打算测试下inline的机制
  • gcc文档: https://gcc.gnu.org/onlinedocs/gcc/Inline.html#Inline
  • 总的来说, inline只是标记, 让编译器可以的话进行内联, 但是具体是否内联, 还是要看编译器决定
  • 我们就没有办法自己定了吗?
  • 一般编译时, 会通过-O0 -O1 -O3 之类的编译选项来控制
  • 通过gcc -Q --help=optimizers -O3命令 我们可以看到不同的编译选项下的开关
  • 当然, 也是可以在编译时强制指定编译选项
  • 也可以用inline void foo (const char) __attribute__((always_inline));格式来强制内联

编译选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# gcc -Q --help=optimizers -O3|grep -i inline
-ffold-simple-inlines [available in C++, ObjC++]
-finline [enabled]
-finline-atomics [enabled]
-finline-functions [enabled]
-finline-functions-called-once [enabled]
-finline-small-functions [enabled]
-flive-patching -flive-patching=inline-clone
-flive-patching=[inline-only-static|inline-clone] [default]

# gcc -Q --help=optimizers -O0|grep -i inline
-ffold-simple-inlines [available in C++, ObjC++]
-finline [disabled]
-finline-atomics [enabled]
-finline-functions [disabled]
-finline-functions-called-once [disabled]
-finline-small-functions [disabled]
-flive-patching -flive-patching=inline-clone
-flive-patching=[inline-only-static|inline-clone] [default]

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <cstdio>

#if true
#define print_i printf("%d", i);
#else
#define print_i
#endif
#define BODY() \
int i = 0; \
i += 10; \
print_i

void no_inline_func() { BODY() }
inline void inline_func() { BODY() }
class Holder {
public:
void no_inline_class_func() { BODY() }
inline void inline_class_func() { BODY() }
template <class T>
void template_no_inline_class_func() {
BODY()
}
template <class T>
inline void template_inline_class_func() {
BODY()
}
};
int main(int argc, char **argv) {
no_inline_func();
inline_func();
Holder h{};
h.no_inline_class_func();
h.inline_class_func();
h.template_no_inline_class_func<int>();
h.template_inline_class_func<int>();

return 0;
}

反汇编

-O3选项下, 基本开启了全部内联选项, 和代码裁减

  • 关闭print, BODY()的内容都可以裁掉了, i没有被用到, 属于无用分支
  • 基本被裁减干净了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0000000000001040 <main>:
1040: f3 0f 1e fa endbr64
1044: 48 83 ec 08 sub $0x8,%rsp
1048: e8 13 01 00 00 call 1160 <_Z14no_inline_funcv>
104d: 31 c0 xor %eax,%eax
104f: 48 83 c4 08 add $0x8,%rsp
1053: c3 ret
1054: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
105b: 00 00 00
105e: 66 90 xchg %ax,%ax

0000000000001160 <_Z14no_inline_funcv>:
1160: f3 0f 1e fa endbr64
1164: c3 ret

  • 开启print 也基本都内联了
  • 除了no_inline_func外, 全部原地展开了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
00000000000011e0 <_Z14no_inline_funcv>:
11e0: f3 0f 1e fa endbr64
11e4: ba 0a 00 00 00 mov $0xa,%edx
11e9: 48 8d 35 14 0e 00 00 lea 0xe14(%rip),%rsi # 2004 <_IO_stdin_used+0x4>
11f0: bf 01 00 00 00 mov $0x1,%edi
11f5: 31 c0 xor %eax,%eax
11f7: e9 54 fe ff ff jmp 1050 <__printf_chk@plt>
0000000000001060 <main>:
1060: f3 0f 1e fa endbr64
1064: 53 push %rbx
1065: 48 8d 1d 98 0f 00 00 lea 0xf98(%rip),%rbx # 2004 <_IO_stdin_used+0x4>
106c: e8 6f 01 00 00 call 11e0 <_Z14no_inline_funcv>
1071: 48 89 de mov %rbx,%rsi
1074: ba 0a 00 00 00 mov $0xa,%edx
1079: 31 c0 xor %eax,%eax
107b: bf 01 00 00 00 mov $0x1,%edi
1080: e8 cb ff ff ff call 1050 <__printf_chk@plt>
1085: 48 89 de mov %rbx,%rsi
1088: ba 0a 00 00 00 mov $0xa,%edx
108d: 31 c0 xor %eax,%eax
108f: bf 01 00 00 00 mov $0x1,%edi
1094: e8 b7 ff ff ff call 1050 <__printf_chk@plt>
1099: 48 89 de mov %rbx,%rsi
109c: ba 0a 00 00 00 mov $0xa,%edx
10a1: 31 c0 xor %eax,%eax
10a3: bf 01 00 00 00 mov $0x1,%edi
10a8: e8 a3 ff ff ff call 1050 <__printf_chk@plt>
10ad: 48 89 de mov %rbx,%rsi
10b0: ba 0a 00 00 00 mov $0xa,%edx
10b5: 31 c0 xor %eax,%eax
10b7: bf 01 00 00 00 mov $0x1,%edi
10bc: e8 8f ff ff ff call 1050 <__printf_chk@plt>
10c1: 48 89 de mov %rbx,%rsi
10c4: ba 0a 00 00 00 mov $0xa,%edx
10c9: 31 c0 xor %eax,%eax
10cb: bf 01 00 00 00 mov $0x1,%edi
10d0: e8 7b ff ff ff call 1050 <__printf_chk@plt>
10d5: 31 c0 xor %eax,%eax
10d7: 5b pop %rbx
10d8: c3 ret
10d9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)

-O0选项下

  • 基本关掉了所有内联选项
  • 无论是否是有需要的分支, 全部编译到目标中了
  • 这里对于没有内联的函数, 只保留了一个, 其他的都相似, 省略掉了
  • 开启打印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
000000000000128a <_ZN6Holder17inline_class_funcEv>:
128a: f3 0f 1e fa endbr64
128e: 55 push %rbp
128f: 48 89 e5 mov %rsp,%rbp
1292: 48 83 ec 20 sub $0x20,%rsp
1296: 48 89 7d e8 mov %rdi,-0x18(%rbp)
129a: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
12a1: 83 45 fc 0a addl $0xa,-0x4(%rbp)
12a5: 8b 45 fc mov -0x4(%rbp),%eax
12a8: 89 c6 mov %eax,%esi
12aa: 48 8d 05 53 0d 00 00 lea 0xd53(%rip),%rax # 2004 <_IO_stdin_used+0x4>
12b1: 48 89 c7 mov %rax,%rdi
12b4: b8 00 00 00 00 mov $0x0,%eax
12b9: e8 b2 fd ff ff call 1070 <printf@plt>
12be: 90 nop
12bf: c9 leave
12c0: c3 ret
12c1: 90 nop

00000000000011a7 <main>:
11a7: f3 0f 1e fa endbr64
11ab: 55 push %rbp
11ac: 48 89 e5 mov %rsp,%rbp
11af: 48 83 ec 20 sub $0x20,%rsp
11b3: 89 7d ec mov %edi,-0x14(%rbp)
11b6: 48 89 75 e0 mov %rsi,-0x20(%rbp)
11ba: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
11c1: 00 00
11c3: 48 89 45 f8 mov %rax,-0x8(%rbp)
11c7: 31 c0 xor %eax,%eax
11c9: e8 a6 ff ff ff call 1174 <_Z14no_inline_funcv>
11ce: e8 4b 00 00 00 call 121e <_Z11inline_funcv>
11d3: 48 8d 45 f7 lea -0x9(%rbp),%rax
11d7: 48 89 c7 mov %rax,%rdi
11da: e8 73 00 00 00 call 1252 <_ZN6Holder20no_inline_class_funcEv>
11df: 48 8d 45 f7 lea -0x9(%rbp),%rax
11e3: 48 89 c7 mov %rax,%rdi
11e6: e8 9f 00 00 00 call 128a <_ZN6Holder17inline_class_funcEv>
11eb: 48 8d 45 f7 lea -0x9(%rbp),%rax
11ef: 48 89 c7 mov %rax,%rdi
11f2: e8 cb 00 00 00 call 12c2 <_ZN6Holder29template_no_inline_class_funcIiEEvv>
11f7: 48 8d 45 f7 lea -0x9(%rbp),%rax
11fb: 48 89 c7 mov %rax,%rdi
11fe: e8 f7 00 00 00 call 12fa <_ZN6Holder26template_inline_class_funcIiEEvv>
1203: b8 00 00 00 00 mov $0x0,%eax
1208: 48 8b 55 f8 mov -0x8(%rbp),%rdx
120c: 64 48 2b 14 25 28 00 sub %fs:0x28,%rdx
1213: 00 00
1215: 74 05 je 121c <main+0x75>
1217: e8 44 fe ff ff call 1060 <__stack_chk_fail@plt>
121c: c9 leave
121d: c3 ret
  • 关闭打印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
0000000000001206 <_ZN6Holder17inline_class_funcEv>:
1206: f3 0f 1e fa endbr64
120a: 55 push %rbp
120b: 48 89 e5 mov %rsp,%rbp
120e: 48 89 7d e8 mov %rdi,-0x18(%rbp)
1212: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
1219: 83 45 fc 0a addl $0xa,-0x4(%rbp)
121d: 90 nop
121e: 5d pop %rbp
121f: c3 ret

000000000000115f <main>:
115f: f3 0f 1e fa endbr64
1163: 55 push %rbp
1164: 48 89 e5 mov %rsp,%rbp
1167: 48 83 ec 20 sub $0x20,%rsp
116b: 89 7d ec mov %edi,-0x14(%rbp)
116e: 48 89 75 e0 mov %rsi,-0x20(%rbp)
1172: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
1179: 00 00
117b: 48 89 45 f8 mov %rax,-0x8(%rbp)
117f: 31 c0 xor %eax,%eax
1181: e8 c3 ff ff ff call 1149 <_Z14no_inline_funcv>
1186: e8 4b 00 00 00 call 11d6 <_Z11inline_funcv>
118b: 48 8d 45 f7 lea -0x9(%rbp),%rax
118f: 48 89 c7 mov %rax,%rdi
1192: e8 55 00 00 00 call 11ec <_ZN6Holder20no_inline_class_funcEv>
1197: 48 8d 45 f7 lea -0x9(%rbp),%rax
119b: 48 89 c7 mov %rax,%rdi
119e: e8 63 00 00 00 call 1206 <_ZN6Holder17inline_class_funcEv>
11a3: 48 8d 45 f7 lea -0x9(%rbp),%rax
11a7: 48 89 c7 mov %rax,%rdi
11aa: e8 71 00 00 00 call 1220 <_ZN6Holder29template_no_inline_class_funcIiEEvv>
11af: 48 8d 45 f7 lea -0x9(%rbp),%rax
11b3: 48 89 c7 mov %rax,%rdi
11b6: e8 7f 00 00 00 call 123a <_ZN6Holder26template_inline_class_funcIiEEvv>
11bb: b8 00 00 00 00 mov $0x0,%eax
11c0: 48 8b 55 f8 mov -0x8(%rbp),%rdx
11c4: 64 48 2b 14 25 28 00 sub %fs:0x28,%rdx
11cb: 00 00
11cd: 74 05 je 11d4 <main+0x75>
11cf: e8 7c fe ff ff call 1050 <__stack_chk_fail@plt>
11d4: c9 leave
11d5: c3 ret

现象

  • 例如: libA 依赖 libX@v1, libB 依赖 libX@v2, v1和v2不兼容
  • 最好是不同的库能独立使用自己的依赖, 有需要再决断使用一个, 但大部分比较老的语言都不支持
  • 最近在写不同的项目时, 发现它们对同名不同版本的依赖处理方式不一样, 这里大概列下, 方便使用不同的方案

可以打包两个版本的依赖进去的

  • 以rust的包管理器cargo为例
  • 发生这种依赖的时候, cargo首先会决策是否可以使用用一个版本, 例如 1.2.4 和 1.2.* 就是可以兼容的
  • 可以兼容时, 就使用一个版本
  • 当不能兼容时, 就会把两个版本都打包进二进制中去
    • 这时候使用name mangling来保证符号不一样, 不冲突
    • 但这时候, 两个版本中同名的类也被认为是不同的类型, 不能互相传递
  • 类似的还有
    • js的npm
    • go比较特殊, 默认使用高版本, 但也支持打包两个版本到一个库中去, 通过replace依赖名实现

只能打包一个版本的

  • c++: 最复杂, 涉及到静态/动态库的绑定, 问题最多
  • Python(pipenv): 会抛出依赖报错, 需要对有问题的库强制指定版本, 覆盖后面的间接依赖
  • java(mvn/gradle): 通过配置调整, 忽略掉一些间接依赖, 排除一些库
  • c#: 通过配置忽略, 类似java