拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

《计算机教育中缺失的一课》中文版调试与性能分析实战指南:日志、调试器与性能剖析工具链

《计算机教育中缺失的一课》中文版调试与性能分析实战指南:日志、调试器与性能剖析工具链 文档教程教育【免费下载链接】missing-semester-cn.github.iothe CS missing semester Chinese version项目地址https://gitcode.com/gh_mirrors/mi/missing-semester-cn.github.io点击查看免费下载本篇文章基于本仓库_2020/debugging-profiling.md2020 年讲座「调试及性能分析」中文版整理扩展而成并辅以仓库内 static/files/logger.py 与 static/files/sorts.py 等配套示例源码进行深化。通过本文你将掌握一套完整的调试 性能分析方法论从打印调试与结构化日志、系统日志查询、pdb/gdb 调试器到静态分析、CPU/内存剖析、火焰图可视化与系统资源监控可直接套用到日常开发排障与性能优化场景中。编程界有一条金科玉律代码不能完全按照你的想法运行它只能完全按照你的写法运行。让写法符合想法非常困难而调试与性能分析正是弥合这一差距的核心技能。本仓库收录的 2020 年讲座《调试及性能分析》系统地传授了两类技术处理代码中bug的调试技术和处理程序性能问题CPU、内存、I/O 资源消耗的性能分析技术。本文将原讲座内容完整展开并结合作业配套的示例源码深入讲解每个工具的实战用法。调试代码打印调试法与日志最有效的 debug 工具就是细致的分析配合恰当位置的打印语句。 —— Brian Kernighan《Unix 新手入门》调试代码的第一种方法往往是在发现问题的地方添加一些打印语句然后不断重复此过程直到获取足够的信息、找到问题的根本原因。这种打印调试法printf debugging简单直接在大部分场景下都够用。第二种方法是使用**日志logging**而非临时添加打印语句。日志本质上就是更讲究的打印与临时打印相比有如下优势你可以将日志写入文件、socket甚至是发送到远端服务器而不仅仅输出到标准输出日志支持严重等级例如INFO、DEBUG、WARN、ERROR等使你可以根据需要过滤日志对于新发现的问题很可能你的日志中已经包含了足以定位问题的信息——因为日志是在编程时就主动埋下的诊断数据。仓库配套示例带颜色的日志程序仓库的 static/files/logger.py 就是讲座中提到的日志示例程序它演示了标准库logging的完整用法包括自定义 Formatter 实现彩色输出。核心实现如下定义CustomFormatter(logging.Formatter)子类通过 ANSI 转义序列为不同日志等级着色DEBUG/INFO用灰色\x1b[38;21m、WARNING用黄色\x1b[33;21m、ERROR用红色\x1b[31;21m、CRITICAL用加粗红色\x1b[31;1m统一以\x1b[0m复位默认格式串为%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)即包含时间、logger 名、等级、消息正文以及所在文件与行号这为事后定位提供了精确位置程序根据命令行参数切换输出模式log参数改用纯文本格式%(asctime)s : %(levelname)s : %(name)s : %(message)scolor参数则启用CustomFormatter第二个参数如ERROR通过logging.__getattribute__动态设置 logger 的过滤等级主体循环随机生成 010 的整数分别触发info、warning、error、critical日志并time.sleep(0.3)模拟一个持续产生日志的长期运行程序。在仓库目录下可以直接这样运行它$ python static/files/logger.py # Raw output as with just prints $ python static/files/logger.py log # Log formatted output $ python static/files/logger.py log ERROR # Print only ERROR levels and above $ python static/files/logger.py color # Color formatted outputANSI 转义序列与终端彩色输出有很多技巧可以提升日志的可读性最常用的是着色。ls、grep这类程序使用 ANSI escape codesANSI 转义序列它们是一系列特殊字符可以让 shell 改变输出颜色。例如执行下面的命令会打印红色的字符串This is redecho -e \e[38;2;255;0;0mThis is red\e[0m其中\e[38;2;R;G;Bm是 24 位真彩色前景色设置\e[0m复位所有属性。前提是终端支持真彩色true color如果不支持例如 macOS 自带的 Terminal.app可以使用兼容性更广的 16 色方案例如echo -e \e[31;1mThis is red\e[0m下面的脚本展示了如何在支持真彩色的终端中遍历打印 0、20、40……255 各通道的颜色块#!/usr/bin/env bash for R in $(seq 0 20 255); do for G in $(seq 0 20 255); do for B in $(seq 0 20 255); do printf \e[38;2;${R};${G};${B}m█\e[0m; done done done第三方日志与系统日志如果你正在构建大型软件系统很可能会用到一些作为独立程序运行的依赖如 Web 服务器、数据库或消息代理。与这些系统交互时阅读它们的日志非常必要因为仅靠客户端侧的错误信息往往不足以定位问题。日志存放位置大多数程序会把日志保存在系统中的某个地方。对于 UNIX 系统程序日志通常存放在/var/log。例如 NGINX Web 服务器将其日志存放于/var/log/nginx。目前系统开始普遍采用system log系统日志所有日志都会汇总到这里大多数但不是全部Linux 系统使用systemd系统守护进程它控制系统中的很多东西例如哪些服务应该启动并运行。systemd将日志以特殊格式存放于/var/log/journal可以使用journalctl命令显示这些消息macOS 上除了/var/log/system.log越来越多的工具使用log show显示系统日志对于大多数 UNIX 系统可以使用dmesg命令读取内核日志。向系统日志写入与检索如果你希望把自己的日志加入系统日志可以使用logger这个 shell 程序。大多数编程语言也提供写系统日志的方法。下面这个例子展示了如何写入一条日志并在两个平台上把它检索出来logger Hello Logs # On macOS log show --last 1m | grep Hello # On Linux journalctl --since 1m ago | grep Hello正如在数据整理那节课上看到的日志内容可以非常多需要处理和过滤才能得到想要的信息。如果经常需要对journalctl和log show的结果做大量过滤可以考虑先使用它们自带的选项如--since、--last在源头过滤。另外lnav这类工具为日志文件提供了更好的展现和浏览方式。调试器当打印已经不能满足调试需求时应该使用调试器。调试器是允许我们与正在执行的程序交互的程序它可以做到当到达某一行时将程序暂停一次一条指令地逐步执行程序程序崩溃后查看变量的值满足特定条件时暂停程序以及其他高级功能。Python 的 pdb很多编程语言都有自己的调试器Python 的调试器是pdb。下面是pdb支持的核心命令简介l(ist)显示当前行周围的 11 行或接着上次显示继续往下 11 行s(tep)执行当前行并在第一个可能的时机停止通常指步入函数n(ext)继续执行直到到达当前函数的下一行或函数返回通常指步过函数b(reak)设置断点基于传入的参数p(rint)在当前上下文对表达式求值并打印结果还有一个pp命令它使用pprint美化打印r(eturn)执行到当前函数返回q(uit)退出调试器。让我们用pdb修复下面这段有 bug 的冒泡排序代码参考讲座视频def bubble_sort(arr): n len(arr) for i in range(n): for j in range(n): if arr[j] arr[j1]: arr[j] arr[j1] arr[j1] arr[j] return arr print(bubble_sort([4, 2, 1, 8, 7, 6]))这段代码的明显问题包括内层循环上界应为n - i - 1否则j1会越界以及交换赋值写反了arr[j] arr[j1]把大值覆盖掉了。你可以启动python -m pdb 文件名.py用l查看源码、b在指定行设断点、n/s单步跟踪、p打印数组中间状态逐步观察排序过程找到根因。因为 Python 是解释型语言所以可以直接在pdbshell 中执行命令和指令。ipdb是pdb的增强版它使用 IPython 作为 REPL开启 tab 补全、语法高亮、更好的回溯和更好的内省同时保留与pdb模块相同的接口。底层语言调试器gdb 与 lldb对于更底层的编程语言你可能需要了解gdb及其改进版pwndbg和lldb。它们针对类 C 语言调试进行了优化但也允许你探索几乎任何进程并获取其当前的机器状态例如寄存器、栈、程序计数器等。常见用法包括在崩溃后查看回溯栈backtrace、检查内存内容、附加到运行中的进程等。专门工具系统调用追踪与网络分析即使需要调试的程序是一个二进制的黑盒仍然有工具可以帮忙。当程序需要执行只有操作系统内核才能完成的操作时它必须使用系统调用system call。在 Linux 中可以用strace追踪系统调用在 macOS 和 BSD 中则使用dtrace。dtrace使用起来可能有些别扭因为它使用自有的D语言但可以用dtruss这个封装使其具有与strace类似的接口。下面例子展示了如何用strace或dtruss显示执行ls时对stat系统调用的追踪结果# On Linux sudo strace -e lstat ls -l /dev/null # On macOS sudo dtruss -t lstat64_extended ls -l /dev/null有些情况下需要查看网络数据包才能定位问题。tcpdump和 Wireshark 这样的网络数据包分析工具可以帮助获取网络数据包的内容并基于不同条件过滤。对于 Web 开发Chrome/Firefox 的开发者工具非常方便且功能强大源码查看任意站点的 HTML/CSS/JS 源码实时修改 HTML、CSS、JS 代码修改网站的内容、样式和行为用于测试由此可见网页截图是不可信的Javascript shell在 JS REPL 中执行命令网络分析请求的时间线存储查看 Cookies 和本地应用存储。静态分析有些问题不需要执行代码就能发现。例如仔细观察一段代码就能发现某个循环变量覆盖了某个已存在的变量或函数名或某个变量在被读取之前并没有被定义。静态分析工具正是针对这类问题的它把程序源码作为输入基于规则进行分析并对代码正确性进行推理。下面这段 Python 代码存在几个问题首先循环变量foo覆盖了之前定义的函数foo最后一行把bar错写成了baz因此程序完成sleep一分钟后执行到这一行时便会崩溃import time def foo(): return 42 for foo in range(5): print(foo) bar 1 bar * 0.2 time.sleep(60) print(baz)静态分析工具可以发现此类问题。用pyflakes分析时会得到与这两处 bug 相关的错误信息mypy则进行类型检查会指出bar起初是int、后来变成了float——这些问题都无需运行代码即可发现$ pyflakes foobar.py foobar.py:6: redefinition of unused foo from line 3 foobar.py:11: undefined name baz $ mypy foobar.py foobar.py:6: error: Incompatible types in assignment (expression has type int, variable has type Callable[[], Any]) foobar.py:9: error: Incompatible types in assignment (expression has type float, variable has type int) foobar.py:11: error: Name baz is not defined Found 3 errors in 1 file (checked 1 source file)在Shell 工具和脚本那节课介绍了shellcheck它是应用于 shell 脚本的同类工具。多数编辑器和 IDE 都支持在编辑器界面内直接显示这些工具的输出并高亮警告和错误的位置这通常被称为代码检查code linting它也可以用来展示代码风格违规或不安全的代码结构在 vim 中插件ale或syntastic可以帮助做同样的事情在 Python 中pylint和pep8是风格检查工具的典型例子bandit则专门用来发现常见安全漏洞其他语言也有非常详尽的静态分析工具列表如 Awesome Static Analysis 的Writing一节代码检查工具linters可参考 Awesome Linters。与风格检查相辅相成的是代码格式化工具code formatters例如 Python 的black、Go 语言的gofmt、Rust 的rustfmt以及 JavaScript、HTML、CSS 的prettier。这些工具会自动把代码格式化为该语言通用的风格规范。标准化的代码格式不仅有助于他人阅读你的代码也能让你更轻松地阅读他人同样经过格式化的代码。性能分析Profiling即使代码在功能上完全符合预期如果它在运行过程中耗尽了所有 CPU 或内存资源那也未必合格。算法课程通常教授大 O 表示法却很少教你如何找到程序中的热点hot spots。鉴于过早优化是万恶之源你更应该了解性能分析器profilers和监控工具——它们会帮助你找到程序中最耗时、最耗资源的部分从而让你集中精力优化这些特定部分。计时与调试类似多数情况下只需打印代码从一处运行到另一处的时间即可发现问题。下面是一个使用 Pythontime模块的例子import time, random n random.randint(1, 10) * 100 # 获取当前时间 start time.time() # 做些工作 print(Sleeping for {} ms.format(n)) time.sleep(n/1000) # 比较当前时间和起始时间 print(time.time() - start) # 输出 # Sleeping for 500 ms # 0.5713930130004883不过墙上时间wall clock time也可能有误导性因为计算机可能同时在运行其他进程或者在等待某些事件发生。工具通常会区分三种时间真实时间Real程序从开始到结束流逝的墙上时间包括其他进程使用的时间以及阻塞例如等待 I/O 或网络的时间用户时间UserCPU 执行用户态代码所花费的时间系统时间SysCPU 执行内核态代码所花费的时间。通常用户时间 系统时间代表你的进程在 CPU 上实际消耗的时间。例如试着在一个网络不佳的环境下执行 HTTP 请求并在命令前加上time$ time curl https://missing.csail.mit.edu /dev/null real 0m2.561s user 0m0.015s sys 0m0.012s请求花费了 2 秒多才完成但进程仅花费了 15 毫秒 CPU 用户时间和 12 毫秒 CPU 内核时间——其余时间都消耗在等待网络上。CPU 性能分析工具大多数情况下当人们提及性能分析工具时指的是 CPU 性能分析工具。CPU 性能分析工具有两种追踪分析器tracing记录程序的每一次函数调用采样分析器sampling周期性地通常每毫秒监测程序并记录程序堆栈。它们使用这些记录生成统计信息显示程序在哪些事情上花费了最多的时间。大多数编程语言都有基于命令行的分析器通常可以集成在 IDE 中。Python 的 cProfile在 Python 中使用cProfile模块分析每次函数调用所消耗的时间。下面实现了一个基础的 grep 命令#!/usr/bin/env python import sys, re def grep(pattern, file): with open(file, r) as f: print(file) for i, line in enumerate(f.readlines()): pattern re.compile(pattern) match pattern.search(line) if match is not None: print({}: {}.format(i, line), end) if __name__ __main__: times int(sys.argv[1]) pattern sys.argv[2] for i in range(times): for file in sys.argv[3:]: grep(pattern, file)用下面的命令对这段代码进行分析。从输出可知I/O 消耗了大量时间编译正则表达式也比较耗费时间。因为正则表达式只需编译一次可以将其移到 for 循环外面来改进性能$ python -m cProfile -s tottime grep.py 1000 ^(import|\s*def)[^,]*$ *.py [omitted program output] ncalls tottime percall cumtime percall filename:lineno(function) 8000 0.266 0.000 0.292 0.000 {built-in method io.open} 8000 0.153 0.000 0.894 0.000 grep.py:5(grep) 17000 0.101 0.000 0.101 0.000 {built-in method builtins.print} 8000 0.100 0.000 0.129 0.000 {method readlines of _io._IOBase objects} 93000 0.097 0.000 0.111 0.000 re.py:286(_compile) 93000 0.069 0.000 0.069 0.000 {method search of _sre.SRE_Pattern objects} 93000 0.030 0.000 0.141 0.000 re.py:231(compile) 17000 0.019 0.000 0.029 0.000 codecs.py:318(decode) 1 0.017 0.017 0.911 0.911 grep.py:3(module) [omitted lines]关于cProfile以及其他类似的分析器需要注意它显示的是每次函数调用的时间看起来可能反直觉——尤其是使用了第三方函数库时因为内部函数调用也会被看作函数调用。行分析器 line_profiler更符合直觉的显示方式是包含每行代码的执行时间这正是行分析器的工作。例如下面这段 Python 代码会向课程网站发起请求然后解析响应页面中的全部 URL#!/usr/bin/env python import requests from bs4 import BeautifulSoup # 这个装饰器会告诉行分析器 # 我们想要分析这个函数 profile def get_urls(): response requests.get(https://missing.csail.mit.edu) s BeautifulSoup(response.content, lxml) urls [] for url in s.find_all(a): urls.append(url[href]) if __name__ __main__: get_urls()如果用cProfile分析它会得到超过 2500 行的输出即使排序也很难搞懂时间花在哪里。改用line_profiler通过kernprof命令它会基于行显示时间一目了然$ kernprof -l -v a.py Wrote profile results to urls.py.lprof Timer unit: 1e-06 s Total time: 0.636188 s File: a.py Function: get_urls at line 5 Line # Hits Time Per Hit % Time Line Contents 5 profile 6 def get_urls(): 7 1 613909.0 613909.0 96.5 response requests.get(https://missing.csail.mit.edu) 8 1 21559.0 21559.0 3.4 s BeautifulSoup(response.content, lxml) 9 1 2.0 2.0 0.0 urls [] 10 25 685.0 27.4 0.1 for url in s.find_all(a): 11 24 33.0 1.4 0.0 urls.append(url[href])96.5% 的时间都花在requests.get这一次网络请求上后续解析和遍历几乎可以忽略。内存分析像 C 或 C 这样的语言内存泄漏会导致程序在使用完内存后不去释放它。可以借助 Valgrind 这样的工具检查内存泄漏问题。对于 Python 这类具有垃圾回收机制的语言内存分析器同样有用——因为对于某个对象来说只要有指针还指向它它就不会被回收。下面这个例子及其输出展示了memory-profiler的工作方式注意其装饰器与line-profiler类似profile def my_func(): a [1] * (10 ** 6) b [2] * (2 * 10 ** 7) del b return a if __name__ __main__: my_func()$ python -m memory_profiler example.py Line # Mem usage Increment Line Contents 3 profile 4 5.97 MB 0.00 MB def my_func(): 5 13.61 MB 7.64 MB a [1] * (10 ** 6) 6 166.20 MB 152.59 MB b [2] * (2 * 10 ** 7) 7 13.61 MB -152.59 MB del b 8 13.61 MB 0.00 MB return a可以看到b [2] * (2 * 10 ** 7)一次性分配了约 152 MB 内存del b后立即释放——内存分析器能精确呈现每个分配点的内存增量。事件分析perf在使用strace调试时你可能希望把某些代码当作黑盒处理。perf命令对 CPU 的差异做了抽象它不报告时间和内存消耗而是报告与程序相关的系统事件。例如perf可以报告不佳的缓存局部性poor cache locality、大量的页错误page faults或活锁livelocks。常用命令简介perf list列出可以被 perf 追踪的事件perf stat COMMAND ARG1 ARG2收集与某个进程或指令相关的事件perf record COMMAND ARG1 ARG2记录命令执行的采样信息并将统计数据储存在perf.data中perf report格式化并打印perf.data中的数据。可视化使用分析器分析真实程序时由于软件复杂性输出结果包含大量信息。人类是视觉动物非常不善于阅读大量文字因此很多工具都提供了可视化分析器输出结果的功能。对于采样分析器常见的 CPU 分析数据可视化形式是火焰图flame graph它在 Y 轴显示函数调用关系在 X 轴显示耗时比例。火焰图同时还是可交互的你可以深入程序的某一具体部分并查看其栈追踪例如 Brendan Gregg 的 CPU-bash-flamegraph 就是经典示例。调用图call graph和控制流图可以显示子程序之间的关系把函数作为节点、函数调用作为边。将它与分析器信息例如调用次数、耗时等结合时调用图会非常有用可以帮助分析程序流程。在 Python 中可以使用pycallgraph生成这些图片典型输出如维基百科上由 pycallgraph 生成的调用图示例。资源监控有时候分析程序性能的第一步是搞清楚它所消耗的资源。程序变慢通常是因为所需资源不够例如没有足够的内存或网络连接变慢。有很多工具可以显示 CPU 占用、内存使用、网络、磁盘使用等系统资源通用监控最流行的是htop它是top的改进版可以显示当前运行进程的多种统计信息。常用快捷键F6进程排序、t显示树状结构、h打开或折叠线程。还可以留意glances实现类似但用户界面更好如果需要合并测量全部进程dstat也很好用它可以实时计算不同子系统的资源度量数据例如 I/O、网络、CPU 利用率、上下文切换等I/O 操作iotop显示实时 I/O 占用信息可以方便地检查某个进程是否正在执行大量磁盘读写磁盘使用df显示每个分区的信息du显示当前目录下每个文件的磁盘使用情况disk usage-h选项使输出以对人类更友好的格式显示ncdu是交互性更好的du可以在不同目录间导航、删除文件和文件夹内存使用free显示系统当前空闲内存内存也可以使用htop等工具查看打开文件lsof列出被进程打开的文件信息当需要查看某个文件被哪个进程打开时非常有用网络连接和配置ss监控网络包的收发情况并显示网络接口信息常见使用场景是找到占用某端口的进程查看路由、网络设备和接口信息使用ip命令。注意netstat和ifconfig已被上述工具取代网络使用nethogs和iftop是很好的交互式命令行网络占用监控工具。如果想测试这些监控工具可以使用stress命令为系统人为增加负载。专用工具基准测试 hyperfine有时候你只需要对黑盒程序进行基准测试并据此对软件选择进行评估。hyperfine这类命令行工具可以帮你快速完成基准测试。例如在 Shell 工具和脚本那节课中推荐用fd代替find这里可以用hyperfine比较两者$ hyperfine --warmup 3 fd -e jpg find . -iname *.jpg Benchmark #1: fd -e jpg Time (mean ± σ): 51.4 ms ± 2.9 ms [User: 121.0 ms, System: 160.5 ms] Range (min … max): 44.2 ms … 60.1 ms 56 runs Benchmark #2: find . -iname *.jpg Time (mean ± σ): 1.126 s ± 0.101 s [User: 141.1 ms, System: 956.1 ms] Range (min … max): 0.975 s … 1.287 s 10 runs Summary fd -e jpg ran 21.89 ± 2.33 times faster than find . -iname *.jpg在这个示例环境中fd比find快了约 20 倍--warmup 3表示先执行 3 次预热再正式测量避免冷启动和缓存影响。和调试一样浏览器也内置了不错的性能分析工具可以分析页面加载让你搞清楚时间消耗在什么地方加载、渲染、脚本等例如 Firefox Profiler 与 Chrome DevTools 的性能面板。课后练习与仓库配套资源本节练习与配套示例来自原讲座习题解答的路径由仓库根目录的 _config.yml 配置solution_url: missing-notes-and-solutions/2020/solutions/结合本页 front matter 中的solution.url: debugging-profiling-solution即可定位到对应的习题解答页面。调试练习使用 Linux 上的journalctl或 macOS 上的log show命令获取最近一天中超级用户的登录信息及其所执行的指令。如果找不到相关信息可以执行一些无害的命令例如sudo ls然后再次查看。学习pdb实践教程并熟悉相关命令例如通过python -m pdb逐步跟踪冒泡排序等小程序的执行熟悉l/s/n/b/p/r/q命令的配合使用。安装shellcheck并检查下面的脚本。这段代码有什么问题修复相关问题并在你的编辑器中安装 linter 插件让它自动显示警告信息#!/bin/sh ## Example: a typical script with several problems for f in $(ls *.m3u) do grep -qi hq.*mp3 $f \ echo -e Playlist $f contains a HQ file in mp3 format done常见问题包括for f in $(ls *.m3u)未加引号导致文件名含空格时出错、echo -e中的$f在单引号内不会展开变量、缺少对$f的引用加引号等shellcheck会逐条指出。进阶题阅读可逆调试reverse debugging相关资料并尝试创建一个可以工作的例子使用rr或RevPDB。rr录制程序执行后可确定性重放配合 GDB 实现时光倒流式的反向调试。性能分析练习仓库的 static/files/sorts.py 提供了一些排序算法的实现包括insertionsort插入排序、quicksort非原地快排、quicksort_inplace原地快排以及一个test_sorted随机测试函数。请使用cProfile和line_profiler比较插入排序和快速排序的性能两种算法的瓶颈分别在哪里可以从源码推断quicksort每次递归都会用列表推导式创建left/right新列表产生大量内存分配insertionsort是原地元素搬移但最坏情况下比较/移动次数多quicksort_inplace通过原地交换减少分配。然后使用memory_profiler检查内存消耗为什么插入排序的内存表现更好因为非原地快排在每一层递归都新建列表。再看看原地排序版本的快排表现。附加题使用perf查看不同算法的循环次数及缓存命中/丢失情况。下面这段用于计算斐波那契数列的 Python 代码为每个数字都定义了一个函数#!/usr/bin/env python def fib0(): return 0 def fib1(): return 1 s def fib{}(): return fib{}() fib{}() if __name__ __main__: for n in range(2, 10): exec(s.format(n, n-1, n-2)) # from functools import lru_cache # for n in range(10): # exec(fib{} lru_cache(1)(fib{}).format(n, n)) print(eval(fib9()))将代码拷贝到文件中使其变为可执行程序。首先安装pycallgraph和 graphviz如果能够执行dot则说明已安装 GraphViz然后使用pycallgraph graphviz -- ./fib.py执行代码并查看生成的pycallgraph.png。fib0被调用了多少次可以通过**记忆化memoization**优化将注释掉的lru_cache部分放开重新生成图片这回每个fibN函数被调用了多少次不缓存时fib0会被指数级地重复调用启用lru_cache后每个fibN最多只计算一次。我们经常会遇到想监听的端口已被其他进程占用的情况请通过进程 PID 查找相应进程。首先执行python -m http.server 4444启动一个最简单的 Web 服务器监听4444端口在另一个终端执行lsof | grep LISTEN打印所有监听端口的进程及对应端口找到对应 PID 后使用kill PID停止该进程。限制进程资源也是非常实用的技术。执行stress -c 3并用htop对 CPU 消耗进行可视化接着执行taskset --cpu-list 0,2 stress -c 3再可视化。stress占用了 3 个 CPU 吗为什么没有taskset把进程绑定到 CPU 0 和 2 两个核心上stress -c 3只会在可用 CPU 上运行因此最多使用 2 个 CPU查阅man taskset寻找答案。附加题使用cgroups实现相同的操作并限制stress -m的内存使用。进阶题执行curl ipinfo.io获取关于你 IP 的信息。打开 Wireshark 抓取curl发起的请求和收到的回复报文提示可以使用http过滤条件只显示 HTTP 报文。延伸阅读本文对应仓库中的原始文档为 _2020/debugging-profiling.md配套示例程序见 static/files/logger.py 与 static/files/sorts.py与本讲相关的课程还包括Shell 工具和脚本shellcheck、fd/find对比与数据整理日志的过滤与sed流编辑仓库还收录了 2026 年新版讲座的英文原文 _2026/debugging-profiling.md其中补充了可逆调试rr、AddressSanitizer 等内存消毒器、eBPF/bpftrace、AI 辅助调试等更新内容可作为进阶参考。调试与性能分析的完整工作流可以概括为先用日志与系统日志快速定位方向journalctl/log show/logger再上调试器精确跟踪pdb/gdb黑盒场景用strace/tcpdump观察系统调用与网络包编译期问题交给静态分析与 linter性能侧则先time计时、htop/free/iotop看资源再用cProfile/line_profiler/memory-profiler/perf定位热点最后以火焰图、调用图可视化并以hyperfine做基准对比。掌握这套工具链你就能系统性地把写出来的代码打磨成想要的代码。赞分享文档教程教育【免费下载链接】missing-semester-cn.github.iothe CS missing semester Chinese version项目地址https://gitcode.com/gh_mirrors/mi/missing-semester-cn.github.io点击查看免费下载相关推荐Monad调试工具链日志系统与性能分析实战Monad调试工具链日志系统与性能分析实战 你是否在区块链节点运行中遇到过这些问题交易执行异常却找不到关键日志系统突然卡顿但缺乏性能数据定位瓶颈Mona深入Fulguris会话持久化机制Parcelable与Bundle背后的设计权衡深入Fulguris会话持久化机制Parcelable与Bundle背后的设计权衡 Fulguris 是一款开源的 Android 网页浏览器⚡Web BrSnow高级配置自定义网络拓扑与性能优化的终极指南Snow高级配置自定义网络拓扑与性能优化的终极指南 Snow作为一款功能强大的网络工具提供了丰富的高级配置选项帮助用户打造个性化的网络拓扑结构并实现性能最网络与通信后端上一篇WSL 容器 SDK 的 ImageProgress 数据类镜像 pull/import/load/push 进度上报机制详解下一篇ngx-admin 日期格式化自定义管道与国际化处理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表