0Pricing
R Academy · 课时

使用 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. system.time() 与 proc.time()
  2. 使用 Rprof 和 profvis 分析代码性能
  3. 用向量化提升速度
  4. 使用 microbenchmark 进行基准测试
← 返回 R Academy