使用 Rprof 和 profvis 分析代码性能
找出脚本中耗时最多的函数
使用 Rprof 和 profvis 分析代码性能 是 CoddyKit 上的免费 R Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 R Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 R Academy 课程共包含 4 节课。
什么是性能分析
计时可以告诉您代码运行了多长时间。性能分析则可以告诉您代码内部的时间花在哪里。R 的性能分析器会定期对调用栈进行采样,从而以统计方式呈现哪些函数开销较大。
主要有两个工具:内置的 Rprof() 和交互式 profvis 包。
启动和停止 Rprof()
Rprof('output.prof', interval = 0.01) 会启动性能分析器。它每隔 10 毫秒将调用栈样本写入文件。运行较慢的代码后,调用 Rprof(NULL) 停止记录。
interval 参数控制以秒为单位的采样频率——值越小,分辨率越高,但输出文件也越大。
# Pattern — do not run Rprof inside knitr/Quarto
# Rprof('my_profile.prof', interval = 0.01)
#
# slow_function <- function(n) {
# x <- numeric(n)
# for (i in seq_len(n)) x[i] <- sqrt(i)
# sum(x)
# }
# slow_function(500000)
#
# Rprof(NULL) # stop profiling使用 summaryRprof() 读取结果
summaryRprof('output.prof') 会解析性能分析文件,并返回一个包含两个数据框的列表:
- by.self — 每个函数自身耗费的时间(不包括被调用的函数)
- by.total — 包括该函数调用的所有函数在内的总时间
按 self.pct 排序即可找到热点。
# After Rprof() run:
# prof <- summaryRprof('my_profile.prof')
# head(prof$by.self)
#
# Example output columns:
# self.time self.pct total.time total.pct
# slow_fn 1.22 61.0 1.98 99.0
# sqrt 0.76 38.0 0.76 38.0
# sum 0.02 1.0 0.02 1.0自身时间与总时间
理解自身时间和总时间对于性能分析至关重要:
- 自身时间 — 函数运行自身代码行所耗费的时间(不包括等待被调用函数的时间)
- 总时间 — 自身时间加上被调用函数所耗费的全部时间
自身时间较低但总时间较高的函数,变慢的原因在于它调用的函数,而不是自身逻辑。应优化被调用函数,而不是调用者。
# Conceptual example:
# wrapper() -> process_data() -> slow_sort()
#
# total.pct: wrapper=100, process_data=90, slow_sort=85
# self.pct: wrapper=5, process_data=5, slow_sort=85
#
# => slow_sort is the real bottleneck to fix.
cat('High total + low self => the culprit is a callee function
')认识 profvis
profvis 对 Rprof() 进行了封装,并在 RStudio 或浏览器中提供交互式 HTML 火焰图。与原始的 summaryRprof() 输出相比,它的可读性要高得多。
使用 install.packages('profvis') 安装一次,然后用 profvis({...}) 包裹您的代码。
# library(profvis)
#
# profvis({
# n <- 200000
# x <- numeric(n)
# for (i in seq_len(n)) x[i] <- log(i)
# total <- sum(x)
# sorted <- sort(x)
# })读取 profvis 火焰图
profvis 输出包含两个面板:
- 火焰图 — 水平条的宽度表示时间;嵌套条表示调用栈深度
- 数据表 — 可排序地查看每个函数和源代码行的自身时间与总时间
火焰图底部较宽的条表示开销最大的调用者。较高的栈表示调用链较深。
# Reading the flame graph:
# - Each horizontal bar = one function on the call stack
# - Width proportional to time spent
# - Bottom = outermost caller, top = deepest callee
# - Click a bar to zoom in
# - 'Memory' tab shows allocation by line
cat('profvis shows self time per source line — invaluable for tight loops
')识别热点
查看 profvis 输出后,可以通过寻找火焰图中较宽且自身时间较高的函数来识别热点。R 中常见的原因包括:
- 逐元素处理、未进行向量化的循环
- 在循环中反复使用
rbind()或c(),导致对象不断增大 - 在紧密循环中反复进行正则表达式或字符串解析
# Before fix — growing vector in loop (common hot spot)
# profvis reveals repeated reallocations:
# result <- c()
# for (i in 1:50000) result <- c(result, i^2)
#
# After fix — pre-allocated:
# result <- numeric(50000)
# for (i in 1:50000) result[i] <- i^2
cat('Pre-allocation eliminates the most common loop hot spot
')分析内存分配
Rprof 还可以使用 memory.profiling = TRUE 跟踪内存分配。profvis 会在计时面板旁显示内存面板,帮助您发现分配大量临时对象的函数——这是造成垃圾回收暂停的主要原因之一。
# Memory profiling with Rprof:
# Rprof('mem.prof', interval = 0.01, memory.profiling = TRUE)
# ... slow code ...
# Rprof(NULL)
# prof <- summaryRprof('mem.prof', memory = 'both')
# head(prof$by.self)
#
# profvis also shows mem delta per line automatically
cat('Memory profiling pinpoints allocation hot spots causing GC pauses
')分析实际数据流水线
对数据流水线进行系统化的性能分析:将整个流水线放入 profvis({}) 中,找出最慢的阶段,优化它,然后重新进行性能分析以确认改进效果。切勿盲目优化。
# Workflow:
# 1. profvis({ full_pipeline() }) => identify Stage 3 is 80% of time
# 2. Rewrite Stage 3 (vectorize / use data.table)
# 3. profvis({ full_pipeline() }) => confirm Stage 3 now < 10%
# 4. system.time({ full_pipeline() }) => confirm overall speedup
cat('Profile -> identify -> fix -> re-profile is the correct cycle
')Rprof 采样的局限性
Rprof 使用统计采样,因此非常快的函数(耗时短于采样间隔)可能不会出现。对于微小表达式的微基准测试,请改用 microbenchmark 包。
此外,Rprof 不会分析 R 接口以下的 C/C++ 代码——只能看到 R 层的调用栈。
# Rprof interval = 0.01s => functions faster than 10ms may not appear
# For sub-millisecond work use microbenchmark:
# microbenchmark(expr1, expr2, times = 1000L)
#
# For C-level profiling use external tools:
# - Instruments (macOS)
# - perf (Linux)
cat('Rprof is for R-level profiling; use microbenchmark for micro-timing
')R 性能分析最佳实践
请遵循以下做法,以获得可靠的性能分析结果:
- 使用真实的数据规模进行分析——较小的输入会掩盖实际瓶颈
- 在分析前运行预热迭代,以排除一次性初始化开销
- 在干净的 R 会话中进行分析,避免已加载包的干扰
- 使用
profvis进行探索;使用summaryRprof生成持续集成或自动化报告
# Clean session profiling checklist:
# 1. Restart R (Ctrl+Shift+F10 in RStudio)
# 2. Load only required packages
# 3. Run once to warm up
# 4. profvis({ ... }) on second run
# 5. Compare before/after with system.time()
cat('Always profile with realistic data in a clean R session
')快速检查:Rprof 中的自身时间与总时间
某个函数在 summaryRprof() 输出中显示 total.pct = 95%,但 self.pct = 3%。这说明了什么?
性能分析工具回顾
R 为您提供了两层性能分析工具:
Rprof('file.prof', interval=0.01)+Rprof(NULL)+summaryRprof()— 内置、可编写脚本,并适合持续集成profvis({...})— 交互式火焰图,支持源代码行注释和内存跟踪
正确的工作流程始终是:先测量,找出最热的热点,只优化该热点,然后重新测量以确认收益。
常见问题解答
「使用 Rprof 和 profvis 分析代码性能」课时是免费的吗?
是的 — 「使用 Rprof 和 profvis 分析代码性能」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。
「使用 Rprof 和 profvis 分析代码性能」这节课中我会学到什么?
找出脚本中耗时最多的函数 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 R Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「使用 Rprof 和 profvis 分析代码性能」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 R Academy 课中编写并运行代码吗?
能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- system.time() 与 proc.time()
- 使用 Rprof 和 profvis 分析代码性能
- 用向量化提升速度
- 使用 microbenchmark 进行基准测试