Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify and optimize Util.falsey? #24

Merged
merged 1 commit into from
Jul 7, 2016

Conversation

iconara
Copy link
Contributor

@iconara iconara commented Apr 15, 2016

I noticed that there was a new utility function that encapsulated the JMESPath rules for false. It's great that an important definition like that is not repeated over and over again.

However, I think the implementation for an often used function like this needs to take performance into account. Using literals in Ruby is problematic since they're allocated on each call, leading to unnecessary garbage being created.

I rewrote the code to avoid allocations, and also simplified it slightly.

These are benchmarks for a few different inputs before these changes (the full benchmark code is below).

                                     user     system      total        real
true                             0.450000   0.010000   0.460000 (  0.458666)
false                            0.120000   0.000000   0.120000 (  0.118656)
{}                               0.440000   0.000000   0.440000 (  0.437886)
[]                               0.730000   0.000000   0.730000 (  0.739866)
["a"]                            0.960000   0.000000   0.960000 (  0.968578)
{"a"=>"b"}                       1.080000   0.000000   1.080000 (  1.085161)
#<Object:0x007fe6c4088c90>       0.460000   0.010000   0.470000 (  0.462774)
nil                              0.090000   0.000000   0.090000 (  0.095598)
#<struct Foo one=nil, two=nil>   0.850000   0.000000   0.850000 (  0.848883)
#<struct Foo one=1, two=2>       0.860000   0.000000   0.860000 (  0.866559)

And these are the numbers with the changes applied:

                                     user     system      total        real
true                             0.340000   0.000000   0.340000 (  0.341450)
false                            0.090000   0.000000   0.090000 (  0.088636)
{}                               0.160000   0.000000   0.160000 (  0.159498)
[]                               0.170000   0.000000   0.170000 (  0.166334)
["a"]                            0.470000   0.000000   0.470000 (  0.479173)
{"a"=>"b"}                       0.630000   0.000000   0.630000 (  0.637846)
#<Object:0x007fddd204bdb8>       0.360000   0.010000   0.370000 (  0.367299)
nil                              0.090000   0.000000   0.090000 (  0.093467)
#<struct Foo one=nil, two=nil>   0.540000   0.000000   0.540000 (  0.543378)
#<struct Foo one=1, two=2>       0.530000   0.000000   0.530000 (  0.536465)

Notice especially the difference for the non-empty array hash cases. Checking that a hash is empty is much better than checking whether or not it is equal to another hash.

The benchmarks of course don't capture the most important aspect of this, GC pressure.

This is the full benchmark code:

Foo = Struct.new(:one, :two)

Benchmark.bmbm do |x|
  [
    true,
    false,
    {},
    [],
    ['a'],
    {'a' => 'b'},
    Object.new,
    nil,
    Foo.new,
    Foo.new(1, 2),
  ].each do |thing|
    x.report(thing.inspect) { 1_000_000.times { JMESPath::Util.falsey?(thing) } }
  end
end

We're have systems that are doing thousands, of not tens of thousands of JMESPath searches per second, so every little bit of performance counts.

The two first cases (nil and false) are equivalent to !value, the three following (String, Hash, Array) are covered by #empty? and the last case (Struct) can use #any?

These optimizations aren't huge in terms of operations per second, but they avoid allocating strings, arrays and hashes, which means that the GC will have to work less, leading to better overall performance.
@trevorrowe
Copy link
Contributor

Thanks for the tuning work. This should be part of the 1.3 release shortly.

@trevorrowe trevorrowe merged commit dc01514 into jmespath:master Jul 7, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants