Skip to content

Instantly share code, notes, and snippets.

@casperisfine
Created April 22, 2021 15:32
Show Gist options
  • Save casperisfine/f6d2728ba91721929e775bc14af7438f to your computer and use it in GitHub Desktop.
Save casperisfine/f6d2728ba91721929e775bc14af7438f to your computer and use it in GitHub Desktop.
# frozen_string_literal: true
require 'benchmark/ips'
require 'active_support/all'
require 'zlib'
sizes = if ENV['SIZES']
ENV['SIZES'].split(',').map(&:to_i)
else
[0, 10, 2_000]
end
module ActiveSupport
module Cache
module Coders
module OptimizedCoder
extend self
COMPRESSED = 0x01
SERIALIZED = 0x02
def dump(entry)
dump_compressed(entry, false)
end
def dump_compressed(entry, threshold)
flags = 0
header = [entry.expires_at, entry.version]
header.pop until header.empty? || !header.last.nil?
header_payload = Marshal.dump(header)
value = entry.value
if value.is_a?(String)
value_payload = entry.value
else
flags |= SERIALIZED
value_payload = Marshal.dump(value)
end
if threshold && value_payload.bytesize >= threshold
compressed_payload = Zlib::Deflate.deflate(value_payload)
if compressed_payload.bytesize < value_payload.bytesize
value_payload = compressed_payload
flags |= COMPRESSED
end
end
flags.chr << header_payload.bytesize.chr << header_payload << value_payload
end
def load(payload)
flags, header_size = payload.unpack('CC')
header = Marshal.load(payload.byteslice(2..-1))
value_payload = payload.byteslice((2 + header_size)..-1)
if flags & COMPRESSED > 0
if flags & SERIALIZED > 0
Entry.new(value_payload, compressed: true, expires_at: header[0], version: header[1])
else
Entry.new(Zlib::Inflate.inflate(value_payload), expires_at: header[0], version: header[1])
end
elsif flags & SERIALIZED > 0
Entry.new(Marshal.load(value_payload), expires_at: header[0], version: header[1])
else
Entry.new(value_payload, expires_at: header[0], version: header[1])
end
end
end
end
end
end
sizes.each do |bytesize|
puts "================ #{bytesize} bytes ================"
if bytesize == 0
entry_marshal = Marshal.dump(ActiveSupport::Cache::Entry.new(''))
entry_pack = ActiveSupport::Cache::Coders::Rails70Coder.dump(ActiveSupport::Cache::Entry.new('', compress: false))
opt_entry_pack = ActiveSupport::Cache::Coders::OptimizedCoder.dump(ActiveSupport::Cache::Entry.new('', compress: false))
else
string = ('A'..'z').cycle.take(bytesize).join
entry_marshal = Marshal.dump(ActiveSupport::Cache::Entry.new(string, expires_in: 1.hour, version: "v42").compressed(1_024))
entry_pack = ActiveSupport::Cache::Coders::Rails70Coder.dump_compressed(ActiveSupport::Cache::Entry.new(string, expires_in: 1.hour, version: "v42", compress: false), 1_024)
opt_entry_pack = ActiveSupport::Cache::Coders::OptimizedCoder.dump_compressed(ActiveSupport::Cache::Entry.new(string, expires_in: 1.hour, version: "v42", compress: false), 1_024)
end
puts "Marshal.dump.bytesize: #{entry_marshal.bytesize}"
puts "EntryCoder.dump.bytesize: #{entry_pack.bytesize}"
puts "OptimizedCoder.dump.bytesize: #{entry_pack.bytesize}"
puts
Benchmark.ips do |x|
x.report('Marshal.dump') { Marshal.dump(ActiveSupport::Cache::Entry.new(string, expires_in: 1.hour, version: "v42").compressed(1_024)) }
x.report('Rails70.dump') { ActiveSupport::Cache::Coders::Rails70Coder.dump_compressed(ActiveSupport::Cache::Entry.new(string, expires_in: 1.hour, version: "v42", compress: false), 1_024) }
x.report('Optimized.dump') { ActiveSupport::Cache::Coders::OptimizedCoder.dump_compressed(ActiveSupport::Cache::Entry.new(string, expires_in: 1.hour, version: "v42", compress: false), 1_024) }
x.compare!
end
puts
Benchmark.ips do |x|
x.report('Marshal.load') { Marshal.load(entry_marshal).value }
x.report('Rails70.load') { ActiveSupport::Cache::Coders::Rails70Coder.load(entry_pack).value }
x.report('OptimizedCoder.load') { ActiveSupport::Cache::Coders::OptimizedCoder.load(opt_entry_pack).value }
x.compare!
end
puts
puts
end
================ 0 bytes ================
Marshal.dump.bytesize: 90
EntryCoder.dump.bytesize: 13
OptimizedCoder.dump.bytesize: 13
Warming up --------------------------------------
Marshal.dump 17.129k i/100ms
Rails70.dump 21.300k i/100ms
Optimized.dump 16.390k i/100ms
Calculating -------------------------------------
Marshal.dump 170.537k (± 1.4%) i/s - 856.450k in 5.023018s
Rails70.dump 215.330k (± 1.3%) i/s - 1.086M in 5.045690s
Optimized.dump 163.612k (± 1.4%) i/s - 819.500k in 5.009718s
Comparison:
Rails70.dump: 215329.6 i/s
Marshal.dump: 170537.1 i/s - 1.26x (± 0.00) slower
Optimized.dump: 163612.0 i/s - 1.32x (± 0.00) slower
Warming up --------------------------------------
Marshal.load 36.668k i/100ms
Rails70.load 55.709k i/100ms
OptimizedCoder.load 51.832k i/100ms
Calculating -------------------------------------
Marshal.load 366.149k (± 1.4%) i/s - 1.833M in 5.008249s
Rails70.load 550.303k (± 1.5%) i/s - 2.785M in 5.062743s
OptimizedCoder.load 517.866k (± 2.0%) i/s - 2.592M in 5.006465s
Comparison:
Rails70.load: 550303.1 i/s
OptimizedCoder.load: 517866.1 i/s - 1.06x (± 0.00) slower
Marshal.load: 366148.8 i/s - 1.50x (± 0.00) slower
================ 10 bytes ================
Marshal.dump.bytesize: 127
EntryCoder.dump.bytesize: 53
OptimizedCoder.dump.bytesize: 53
Warming up --------------------------------------
Marshal.dump 14.675k i/100ms
Rails70.dump 20.229k i/100ms
Optimized.dump 19.653k i/100ms
Calculating -------------------------------------
Marshal.dump 146.104k (± 0.9%) i/s - 733.750k in 5.022520s
Rails70.dump 203.623k (± 1.1%) i/s - 1.032M in 5.067272s
Optimized.dump 198.870k (± 1.2%) i/s - 1.002M in 5.040741s
Comparison:
Rails70.dump: 203623.3 i/s
Optimized.dump: 198870.5 i/s - same-ish: difference falls within error
Marshal.dump: 146104.4 i/s - 1.39x (± 0.00) slower
Warming up --------------------------------------
Marshal.load 29.581k i/100ms
Rails70.load 43.199k i/100ms
OptimizedCoder.load 40.047k i/100ms
Calculating -------------------------------------
Marshal.load 296.891k (± 1.1%) i/s - 1.509M in 5.082104s
Rails70.load 431.656k (± 1.2%) i/s - 2.160M in 5.004583s
OptimizedCoder.load 404.179k (± 5.1%) i/s - 2.042M in 5.072898s
Comparison:
Rails70.load: 431656.3 i/s
OptimizedCoder.load: 404178.5 i/s - 1.07x (± 0.00) slower
Marshal.load: 296890.8 i/s - 1.45x (± 0.00) slower
================ 2000 bytes ================
Marshal.dump.bytesize: 221
EntryCoder.dump.bytesize: 127
OptimizedCoder.dump.bytesize: 127
Warming up --------------------------------------
Marshal.dump 3.953k i/100ms
Rails70.dump 4.023k i/100ms
Optimized.dump 4.778k i/100ms
Calculating -------------------------------------
Marshal.dump 39.823k (± 1.1%) i/s - 201.603k in 5.063150s
Rails70.dump 40.129k (± 1.0%) i/s - 201.150k in 5.013145s
Optimized.dump 47.953k (± 1.2%) i/s - 243.678k in 5.082282s
Comparison:
Optimized.dump: 47953.3 i/s
Rails70.dump: 40128.8 i/s - 1.19x (± 0.00) slower
Marshal.dump: 39822.7 i/s - 1.20x (± 0.00) slower
Warming up --------------------------------------
Marshal.load 10.688k i/100ms
Rails70.load 13.666k i/100ms
OptimizedCoder.load 12.594k i/100ms
Calculating -------------------------------------
Marshal.load 107.654k (± 1.3%) i/s - 545.088k in 5.064150s
Rails70.load 133.274k (± 2.6%) i/s - 669.634k in 5.028035s
OptimizedCoder.load 124.683k (± 1.7%) i/s - 629.700k in 5.051781s
Comparison:
Rails70.load: 133273.6 i/s
OptimizedCoder.load: 124683.4 i/s - 1.07x (± 0.00) slower
Marshal.load: 107654.1 i/s - 1.24x (± 0.00) slower
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment