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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
|
# frozen_string_literal: true
require "bundler/setup"
require "rqrcode"
require "benchmark/ips"
require "memory_profiler"
require "stackprof"
require "json"
require "fileutils"
require "time"
module BenchmarkHelper
# Test data of varying sizes
QR_DATA = {
tiny: "Hi",
small: "https://github.com/whomwah/rqrcode",
medium: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.",
large: "A" * 500,
xlarge: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " * 20
}.freeze
# Create QR codes for benchmarking (rendering-only benchmarks)
def self.qrcodes
@qrcodes ||= QR_DATA.transform_values { |data| RQRCode::QRCode.new(data) }
end
# Get raw QR data for end-to-end benchmarks (generation + rendering)
def self.qr_data
QR_DATA
end
# Get results directory
def self.results_dir
@results_dir ||= File.join(__dir__, "results")
end
# Ensure results directory exists
def self.ensure_results_dir
FileUtils.mkdir_p(results_dir) unless Dir.exist?(results_dir)
end
# Get timestamp for filename
def self.timestamp
@timestamp ||= Time.now.strftime("%Y%m%d_%H%M%S")
end
# Save results to JSON
def self.save_results(name, data)
ensure_results_dir
filename = File.join(results_dir, "#{name}_#{timestamp}.json")
File.write(filename, JSON.pretty_generate(data))
puts "\n💾 Results saved to: #{filename}"
end
# Run IPS benchmark (rendering-only, uses pre-generated QR codes)
def self.run_ips(label, warmup: 2, time: 5, &block)
puts "\n" + "=" * 80
puts "IPS Benchmark: #{label} (rendering-only)"
puts "=" * 80
results = {}
report = Benchmark.ips do |x|
x.config(warmup: warmup, time: time)
block.call(x, qrcodes)
x.compare!
end
# Extract actual metrics from the report
report.entries.each do |entry|
results[entry.label] = {
iterations_per_second: entry.stats.central_tendency.round(2),
standard_deviation: entry.stats.error_percentage.round(2),
samples: entry.measurement_cycle
}
end
# Calculate comparison ratios (fastest = 1.0x)
if results.any?
fastest_ips = results.values.map { |r| r[:iterations_per_second] }.max
results.each do |_label, data|
data[:comparison] = (fastest_ips / data[:iterations_per_second]).round(2)
end
end
# Save results with actual metrics
save_results(
"ips_#{label.downcase.gsub(/\s+/, "_")}",
{
label: label,
timestamp: Time.now.iso8601,
ruby_version: RUBY_VERSION,
results: results
}
)
report
end
# Run IPS benchmark for end-to-end workflow (generation + rendering)
def self.run_ips_e2e(label, warmup: 2, time: 5, &block)
puts "\n" + "=" * 80
puts "IPS Benchmark: #{label} (end-to-end: generation + rendering)"
puts "=" * 80
results = {}
report = Benchmark.ips do |x|
x.config(warmup: warmup, time: time)
block.call(x, qr_data)
x.compare!
end
# Extract actual metrics from the report
report.entries.each do |entry|
results[entry.label] = {
iterations_per_second: entry.stats.central_tendency.round(2),
standard_deviation: entry.stats.error_percentage.round(2),
samples: entry.measurement_cycle
}
end
# Calculate comparison ratios (fastest = 1.0x)
if results.any?
fastest_ips = results.values.map { |r| r[:iterations_per_second] }.max
results.each do |_label, data|
data[:comparison] = (fastest_ips / data[:iterations_per_second]).round(2)
end
end
# Save results with actual metrics
save_results(
"ips_e2e_#{label.downcase.gsub(/\s+/, "_")}",
{
label: "#{label} (end-to-end)",
timestamp: Time.now.iso8601,
ruby_version: RUBY_VERSION,
results: results
}
)
report
end
# Run memory profiler
def self.run_memory(label, &block)
puts "\n" + "=" * 80
puts "Memory Profile: #{label}"
puts "=" * 80
report = MemoryProfiler.report do
block.call(qrcodes)
end
report.pretty_print(scale_bytes: true, normalize_paths: true)
# Save memory results
memory_data = {
label: label,
timestamp: Time.now.iso8601,
ruby_version: RUBY_VERSION,
total_allocated_memsize: report.total_allocated_memsize,
total_retained_memsize: report.total_retained_memsize,
total_allocated: report.total_allocated,
total_retained: report.total_retained
}
save_results("memory_#{label.downcase.gsub(/\s+/, "_")}", memory_data)
report
end
# Run stack profiler
def self.run_stackprof(label, mode: :cpu, &block)
puts "\n" + "=" * 80
puts "Stack Profile: #{label} (#{mode} mode)"
puts "=" * 80
profile = StackProf.run(mode: mode, interval: 1000) do
block.call(qrcodes)
end
StackProf::Report.new(profile).print_text(limit: 20)
# Save stackprof results
stackprof_data = {
label: label,
timestamp: Time.now.iso8601,
ruby_version: RUBY_VERSION,
mode: mode,
samples: profile[:samples],
frames: profile[:frames].map do |_frame_id, frame_data|
{
name: frame_data[:name],
total_samples: frame_data[:samples],
file: frame_data[:file],
line: frame_data[:line]
}
end.sort_by { |f| -f[:total_samples] }.first(20)
}
save_results("stackprof_#{label.downcase.gsub(/\s+/, "_")}", stackprof_data)
profile
end
# Convenience method to run all profiling types
def self.profile_all(label, &block)
run_ips(label, &block)
run_memory(label, &block)
run_stackprof(label, &block)
end
# Helper to print section header
def self.section(title)
puts "\n\n"
puts "#" * 80
puts "# #{title}"
puts "#" * 80
puts "Timestamp: #{timestamp}"
puts "Ruby Version: #{RUBY_VERSION}"
end
# Helper to format bytes
def self.format_bytes(bytes)
if bytes < 1024
"#{bytes} B"
elsif bytes < 1024 * 1024
"#{(bytes / 1024.0).round(2)} KB"
else
"#{(bytes / (1024.0 * 1024)).round(2)} MB"
end
end
end
|