Skip to content

Instantly share code, notes, and snippets.

@esparta
Last active August 1, 2019 17:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save esparta/c905af2a0220daca37cd7adf44db6b0d to your computer and use it in GitHub Desktop.
Save esparta/c905af2a0220daca37cd7adf44db6b0d to your computer and use it in GitHub Desktop.
Benchmarks for PR #577 at dry-rb/validations

Proposal

ref: dry-rb/dry-validation#577

A small change on how to create the final values when requesting a complex hash. Instead of a ruby loop, have fetch_values call.

Case: works with a hash pointing to multiple values
Warming up --------------------------------------
             current     9.893k i/100ms
   original proposal    14.670k i/100ms
   proposal with nil    14.260k i/100ms
Calculating -------------------------------------
             current    102.132k (± 6.2%) i/s -    514.436k in   5.060271s
   original proposal    166.367k (± 3.8%) i/s -    836.190k in   5.034047s
   proposal with nil    159.077k (± 3.3%) i/s -    798.560k in   5.025592s

Comparison:
   original proposal:   166366.9 i/s
   proposal with nil:   159077.4 i/s - same-ish: difference falls within error
             current:   102132.3 i/s - 1.63x  slower

Case: works with a path
Warming up --------------------------------------
             current   105.977k i/100ms
   original proposal   108.348k i/100ms
   proposal with nil   106.261k i/100ms
Calculating -------------------------------------
             current      2.144M (± 4.5%) i/s -     10.704M in   5.003159s
   original proposal      2.081M (± 7.0%) i/s -     10.401M in   5.027986s
   proposal with nil      2.147M (± 4.2%) i/s -     10.732M in   5.008840s

Comparison:
   proposal with nil:  2146594.2 i/s
             current:  2144136.8 i/s - same-ish: difference falls within error
   original proposal:  2080937.1 i/s - same-ish: difference falls within error


01_values.rb contains the modified versions (original_proposal & proposal_with_nil) along with the current implementation([])

Specs:

ruby 2.4.6p354 (2019-04-01 revision 67394) [x86_64-linux]

Docker version 19.03.1, build 74b1e89

Darwin hostname 17.7.0 Darwin Kernel Version 17.7.0: Wed Apr 24 21:17:24 PDT 2019; root:xnu-4570.71.45~1/RELEASE_X86_64 x86_6

# frozen_string_literal: true [99/2250]
require 'dry/equalizer'
require 'dry/schema/path'
module Dry
module Validation
# A convenient wrapper for data processed by schemas
#
# Values are available within the rule blocks. They act as hash-like
# objects and expose a convenient API for accessing data.
#
# @api public
class Values
include Enumerable
include Dry::Equalizer(:data)
# Schema's result output
#
# @return [Hash]
#
# @api private
attr_reader :data
# @api private
def initialize(data)
@data = data
end
# Read from the provided key
#
# @example
# rule(:age) do
# key.failure('must be > 18') if values[:age] <= 18
# end
#
# @param [Symbol] key
#
# @return [Object]
#
# @api public
def original_proposal(*args)
return data.dig(*args) if args.size > 1
case (key = args[0])
when Symbol, String, Array, Hash
keys = Schema::Path[key].to_a
return data.dig(*keys) unless keys.last.is_a?(Array)
last = keys.pop
vals = self.class.new(data.dig(*keys))
vals.fetch_values(*last)
else
raise ArgumentError, '+key+ must be a valid path specification'
end
end
def proposal_with_nil(*args)
return data.dig(*args) if args.size > 1
case (key = args[0])
when Symbol, String, Array, Hash
keys = Schema::Path[key].to_a
return data.dig(*keys) unless keys.last.is_a?(Array)
last = keys.pop
vals = self.class.new(data.dig(*keys))
vals.fetch_values(*last) { nil }
else
raise ArgumentError, '+key+ must be a valid path specification'
end
end
def [](*args)
return data.dig(*args) if args.size > 1
case (key = args[0])
when Symbol, String, Array, Hash
path = Schema::Path[key]
keys = path.to_a
return data.dig(*keys) unless keys.last.is_a?(Array)
last = keys.pop
vals = self.class.new(data.dig(*keys))
last.map { |name| vals[name] }
else
raise ArgumentError, '+key+ must be a valid path specification'
end
end
# @api public
def key?(key, hash = data)
return hash.key?(key) if key.is_a?(Symbol)
Schema::Path[key].reduce(hash) do |a, e|
if e.is_a?(Array)
result = e.all? { |k| key?(k, a) }
return result
else
return false unless a.is_a?(Array) ? (e >= 0 && e < a.size) : a.ke
y?(e)
end
a[e]
end
true
end
# @api private
def respond_to_missing?(meth, include_private = false)
super || data.respond_to?(meth, include_private)
end
private
# @api private
def method_missing(meth, *args, &block)
if data.respond_to?(meth)
data.public_send(meth, *args, &block)
else
super
end
end
end
end
end
# frozen_string_literal: true
require 'benchmark/ips'
require_relative 'values'
data =
{ name: 'Jane',
address: {
city: 'Paris',
geo: { lat: 1, lon: 2 } },
phones: [123, 431]
}
values = Dry::Validation::Values.new(data)
puts 'Case: works with a hash pointing to multiple values'
Benchmark.ips do |benchmark|
benchmark.report('current') do
values[address: { geo: [:lat, :lon] }]
end
benchmark.report('original proposal') do
values.original_proposal(address: { geo: [:lat, :lon] })
end
benchmark.report('proposal with nil') do
values.proposal_with_nil(address: { geo: [:lat, :lon] })
end
benchmark.compare!
end
puts 'Case: works with a path'
Benchmark.ips do |with_path|
with_path.report('current') do
values[:address, :cit]
end
with_path.report('original proposal') do
values.original_proposal(:address, :cit)
end
with_path.report('proposal with nil') do
values.proposal_with_nil(:address, :cit)
end
with_path.compare!
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment