Code Profiling in R
1. Introduction to Code Profiling
When we write a program, our first goal should always be to make it correct and readable, not fast.
However, as the program grows or is executed repeatedly (for example, inside loops), it may become
slow and inefficient.
At this stage, we need a systematic way to identify which part of the code is responsible for the
delay. This is where code profiling becomes important.
Definition
Code profiling is the process of measuring how much time and resources different parts of a
program consume.
2. Why Code Profiling is Needed
It is often tempting to assume we know which part of the code is slow, but in reality, this assumption
is frequently wrong. Profiling helps us make decisions based on actual data rather than guesswork.
Key Reasons
To identify performance bottlenecks
To improve execution speed
To reduce unnecessary computations
To make optimization scientific and reliable
Important Principle
First design and debug the code
Then measure performance
Finally optimize only the slow parts
This avoids unnecessary complexity and keeps the code maintainable.
Best Practices for Profiling
Do not optimize too early (premature optimization is harmful)
Always measure before optimizing
Focus only on slow parts of code
Break code into functions for better profiling
Prefer vectorized operations over loops
3. Measuring Execution Time using [Link]()
3.1 Concept
The [Link]() function is the simplest way to measure how long a piece of R code takes to
execute. It is useful when we already have an idea of which part of the code we want to test.
3.2 Syntax
[Link](expression)
For multiple lines:
[Link]({
# multiple statements
})
Ex: [Link](sqrt(1000000))
Step-by-step working: [Link]() starts timer , sqrt(1000000) is executed , Timer stops ,Output is
returned
Output (approx)
user system elapsed
0.00 0.00 0.00
Interpretation: Operation is very fast → almost no time taken
ii. Real Example (Multiple Statements)
[Link]({
n <- 10000
result <- numeric(n)
for(i in 1:n){
result[i] <- sqrt(i) //main time-consuming part
}
})
output:
user system elapsed
0.05 0.00 0.05
The function returns three important values:
user time Time spent by CPU executing the code
system time Time spent in system-level operations
elapsed time Actual wall-clock time (real time experienced)
3.3 Understanding the Difference
Case 1: Elapsed Time > User Time
This happens when the CPU is waiting, for example:
Reading from internet
File operations
[Link](readLines("[Link]
Here, most time is spent waiting for data, not computing.
Case 2: Elapsed Time < User Time
This happens in parallel processing, where multiple CPUs work simultaneously.
[Link](svd(matrix(1:1000000, nrow=1000)))
Here, work is divided across processors, reducing real-world time.
3.4 Limitation of [Link]()
It only tells total execution time, but does not indicate:
Which function is slow
Where the bottleneck exists
For deeper analysis, we need a profiler.
4. The R Profiler using Rprof()
4.1 Concept
When we do not know which part of the code is slow, we use a profiler. The R profiler helps us
understand how time is distributed across functions.
4.2 Working Principle
It uses sampling technique. At fixed intervals (default: 0.02 seconds), it checks Which
function is currently running. Over time, it builds a picture of where time is spent
4.3 Syntax
Rprof("[Link]") # Start profiling
# Run the code
your_function()
Rprof(NULL) # Stop profiling
5. Analyzing Results using summaryRprof()
5.1 Purpose
The summaryRprof() function converts raw profiling data into a readable summary, showing how
much time is spent in each function.
5.2 Syntax
summaryRprof("[Link]")
Example
Rprof("[Link]")
for(i in 1:1000){
x <- rnorm(1000)
mean(x)
Rprof(NULL)
summaryRprof("[Link]")
Output
$[Link]
[Link] [Link] [Link] [Link]
"rnorm" 0.30 60.0 0.30 60.0
"mean" 0.20 40.0 0.20 40.0
$[Link]
[Link] [Link] [Link] [Link]
"rnorm" 0.30 60.0 0.30 60.0
"mean" 0.20 40.0 0.20 40.0
1. $[Link] (Most Important)
Shows time spent inside the function only.(excludes time of functions it calls)
2. $[Link]
Shows time spent in function including all functions it calls
Some functions call other functions [Link] total time helps understand overall impact.
Columns
[Link] → actual time spent in that function
[Link] → percentage of total time
[Link] → includes child functions
[Link] → percentage including child calls
What is Recorded?
The profiler records the function call stack, which means:
Which functions are active
How deeply nested they are
This raw output is not easy to read, so we summarize it.
Interpretation
If a function shows high self time, it means:
→ That function itself is slow
If a function shows high total time but low self time, it means:
→ The functions it calls are slow
This helps in identifying the real bottleneck.
6. Sampling Interval and Its Importance
The profiler checks execution at fixed intervals (default: 0.02 seconds).
Implications
Profiling results are approximate, not exact
Very fast functions may not appear in results
7. Memory Considerations in Profiling
Although Rprof mainly measures time, memory also affects performance.
Common Tools
(A) [Link]()
Measures memory used by an object
Syntax: [Link](x)
Example:
x <- rnorm(1000)
[Link](x)
Output
8008 bytes
(B) gc() (Garbage Collection)
Frees unused memory
Shows memory usage
gc()
Example
x <- rnorm(1000000)
rm(x)
gc()
Output:
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 500000 26.7 1000000 53.4 800000 42.7
Vcells 1200000 9.2 2000000 15.3 1500000 11.5
Ncells: Memory used by non-vector objects (functions, language objects)
Vcells: Memory used by vector data (numeric, character, etc.)
Columns
used (Mb) → memory currently in use
gc trigger (Mb) → limit at which garbage collection will run
max used (Mb) → maximum memory used so far
This output shows current memory usage, limit for cleanup, and peak memory used for both object
types (Ncells and Vcells).
Why Memory Matters
Large objects slow down computation
Memory overflow can crash programs
Efficient memory use improves speed
8. Comparing Code using Microbenchmarking
8.1 Concept
Sometimes we want to compare two methods performing the same task. For this, we use
microbenchmarking, which measures time very precisely.
Example
library(microbenchmark)
microbenchmark(
sqrt(100),
100^0.5
)
Working
Both expressions compute square root of 100
microbenchmark():
o Executes each expression multiple times
o Records execution time for each run
o Summarizes results
Output (Sample)
Unit: nanoseconds
expr min lq mean median uq max neval
sqrt(100) 120 130 150 140 160 200 100
100^0.5 180 190 210 200 220 260 100
Explanation of Columns
expr → expression being tested
min → fastest time
lq → lower quartile
mean → average time
median → middle value (most reliable)
uq → upper quartile
max → slowest time
neval → number of executions
Interpretation
sqrt(100) has lower time values
100^0.5 is slightly slower
So, sqrt() is the faster [Link] is Useful as it Helps choose faster implementation,Useful for small
operations, Provides detailed timing statistics
9. Identifying Bottlenecks
A bottleneck is the part of the code that consumes the most time.
Key Idea
Improving the bottleneck gives the maximum performance gain.
For example:
If a loop runs 1000 times
And inside it a slow function exists
Optimizing that function improves the entire program significantly.
[Link]
Code profiling in R is an essential technique for improving performance. Instead of guessing where
the problem lies, profiling provides data-driven insights into how a program behaves. By using tools
like [Link](), Rprof(), and summaryRprof(), we can systematically identify bottlenecks and
optimize code efficiently.