Skip to content

Instantly share code, notes, and snippets.

@fnordfish
Created August 24, 2024 07:55
Show Gist options
  • Save fnordfish/ffed81bccd0516d8b19aee2d4a45ec28 to your computer and use it in GitHub Desktop.
Save fnordfish/ffed81bccd0516d8b19aee2d4a45ec28 to your computer and use it in GitHub Desktop.
Ruby benchmark conditonally adding a traling slash
# Add a trailing slash to a string unless it already ends with one
require "benchmark/ips"
PATH = "foo/bar".freeze
NOOP_PATH = "foo/bar/".freeze
def file_join
File.join(PATH, "")
end
def file_join_noop
File.join(NOOP_PATH, "")
end
def str_cond
PATH.end_with?("/") ? PATH : "#{PATH}/"
end
def str_cond_new
"#{PATH}#{PATH.end_with?("/") ? "" : "/"}"
end
def str_cond_noop
NOOP_PATH.end_with?("/") ? NOOP_PATH : "#{NOOP_PATH}/"
end
def str_cond_new_noop
"#{NOOP_PATH}#{PATH.end_with?("/") ? "" : "/"}"
end
puts "When adding a trailing slash to a path"
Benchmark.ips do |x|
x.report('File.join') { file_join }
x.report('conditional add slash') { str_cond }
x.report('always new string conditional add slash') { str_cond_new }
x.compare!
end
# When adding a trailing slash to a path
# ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin23]
# Warming up --------------------------------------
# File.join 578.636k i/100ms
# conditional add slash
# 933.332k i/100ms
# always new string conditional add slash
# 757.871k i/100ms
# Calculating -------------------------------------
# File.join 5.812M (± 1.5%) i/s - 29.510M in 5.078518s
# conditional add slash
# 9.401M (± 1.4%) i/s - 47.600M in 5.064141s
# always new string conditional add slash
# 7.562M (± 1.2%) i/s - 37.894M in 5.011693s
# Comparison:
# conditional add slash: 9401189.9 i/s
# always new string conditional add slash: 7562105.0 i/s - 1.24x slower
# File.join: 5812222.1 i/s - 1.62x slower
puts "When path ends with slash"
Benchmark.ips do |x|
x.report('File.join') { file_join_noop }
x.report('conditional add slash') { str_cond_noop }
x.report('always new string conditional add slash') { str_cond_new_noop }
x.compare!
end
# When path ends with slash
# ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin23]
# Warming up --------------------------------------
# File.join 598.416k i/100ms
# conditional add slash
# 1.627M i/100ms
# always new string conditional add slash
# 768.114k i/100ms
# Calculating -------------------------------------
# File.join 5.980M (± 1.8%) i/s - 29.921M in 5.005489s
# conditional add slash
# 16.404M (± 1.2%) i/s - 82.972M in 5.058643s
# always new string conditional add slash
# 7.622M (± 1.4%) i/s - 38.406M in 5.039760s
# Comparison:
# conditional add slash: 16404499.6 i/s
# always new string conditional add slash: 7622075.8 i/s - 2.15x slower
# File.join: 5979610.0 i/s - 2.74x slower
@bkuhlmann
Copy link

Here's the benchmark I used, in case it's of interest. ...but, yeah, #end_with? is more performant.

#! /usr/bin/env ruby
# frozen_string_literal: true

# Save as `benchmark`, then `chmod 755 benchmark`, and run as `./benchmark`.

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"
  gem "benchmark-ips"
  gem "debug"
end

module Demo
  BARE = "one/two"
  SLASH = "one/two/"

  def self.bare_standard = File.join BARE, File::SEPARATOR

  def self.bare_check = BARE.end_with? File::SEPARATOR ? BARE : "#{BARE}#{File::SEPARATOR}"

  def self.slash_standard = File.join SLASH, File::SEPARATOR

  def self.slash_check = SLASH.end_with? File::SEPARATOR ? SLASH : "#{SLASH}#{File::SEPARATOR}"
end

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("Bare Standard") { Demo.bare_standard }
  benchmark.report("Bare Check") { Demo.bare_check }
  benchmark.report("Slash Standard") { Demo.slash_standard }
  benchmark.report("Slash Check") { Demo.slash_check }

  benchmark.compare!
end

__END__

ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [arm64-darwin23.6.0]
Warming up --------------------------------------
       Bare Standard   673.476k i/100ms
          Bare Check     3.106M i/100ms
      Slash Standard   649.068k i/100ms
         Slash Check     3.206M i/100ms
Calculating -------------------------------------
       Bare Standard      6.861M (± 0.6%) i/s -     34.347M in   5.006475s
          Bare Check     38.672M (± 0.6%) i/s -    195.669M in   5.059890s
      Slash Standard      6.959M (± 0.6%) i/s -     35.050M in   5.036570s
         Slash Check     40.574M (± 0.2%) i/s -    205.209M in   5.057669s

Comparison:
         Slash Check: 40573866.6 i/s
          Bare Check: 38671997.0 i/s - 1.05x  slower
      Slash Standard:  6959292.3 i/s - 5.83x  slower
       Bare Standard:  6860808.0 i/s - 5.91x  slower

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment