-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Performance degradation (~6x) when using given to model a dependent relation #2100
Comments
I bet it's fixed by #2096. Try HEAD? |
Unfortunately, the performance degradation still exists. Actually, I work together with @braktar and we tested on his computer with the fix. |
Put this into a benchmark and let's fix it. |
@senhalil are you going to work on the benchmark? |
@dnesteryuk this issue fell in the cracks apparently, thanks for the ping. I don't think I will be able to work on this issue since we gave up using |
I wrote this simple benchmark to compare params without dependencies and with them: require 'grape'
require 'benchmark/ips'
class GivenAPI < Grape::API
prefix :api
version 'v1', using: :path
params do
optional :matrix
given matrix: ->(val) { val.nil? || val.empty? } do
requires :vehicle, type: Hash do
requires :router_mode, type: String
end
end
given matrix: ->(val) { val.any? } do
requires :vehicle, type: Hash do
optional :matrix_id, type: String
optional :router_mode, type: String
exactly_one_of :matrix_id, :router_mode
end
end
end
post '/' do
'hello'
end
end
class SimpleAPI < Grape::API
prefix :api
version 'v1', using: :path
params do
optional :matrix
requires :vehicle, type: Hash do
optional :matrix_id, type: String
optional :router_mode, type: String
exactly_one_of :matrix_id, :router_mode
end
end
post '/' do
'hello'
end
end
options = {
method: 'POST',
params: {
matrix: {
something: 'test'
},
vehicle: {
router_mode: 'hello'
}
}
}
env = Rack::MockRequest.env_for('/api/v1', options)
# warm up
GivenAPI.call
Benchmark.ips do |ips|
ips.report('Given') do
GivenAPI.call env
end
ips.report('Simple') do
SimpleAPI.call env
end
ips.compare!
end The result:
I would say it isn't that bad. |
I happened to have sometime this weekend, I thought I can chip-in. I suspect this issue is related to data size and model parameter type, that's why it doesn't show up on a simple model with a small instance. I am still surprised that it takes 1.50x slower even with this model and instance. I should have shared the definition of the model parameters properly in my original post. The matrix parameter actually has parameters of type I have found an error in my original code that produced 20x difference -- one of the parameters were not defined in the SimpleAPI 🤦 -- sorry for the hype! I created the following smallish model from scratch by cropping the irrelevant parts of I suspect the bigger the data gets, the worse the performance difference gets -- even if the part of the data have nothing to do with the conditional part of the model.
require 'grape'
require 'benchmark/ips'
class SimpleAPI < Grape::API
prefix :api
version 'v1', using: :path
content_type :json, 'application/json; charset=UTF-8'
default_format :json
def self.vrp_request_point(this)
this.requires(:id, type: String, allow_blank: false)
this.optional(:matrix_index, type: Integer, allow_blank: false, desc: 'Index within the matrices')
this.optional(:location, type: Hash, allow_blank: false) do
requires(:lat, type: Float, allow_blank: false)
requires(:lon, type: Float, allow_blank: false)
end
this.exactly_one_of :matrix_index, :location
end
params do
optional(:vrp, type: Hash, documentation: { param_type: 'body' }) do
optional(:name, type: String)
optional(:matrices, type: Array, documentation: { desc: 'Define the distances' }) do
requires(:id, type: String, allow_blank: false)
optional(:time, type: Array[Array[Float]], allow_blank: false, desc: 'Matrix of time')
optional(:distance, type: Array[Array[Float]], allow_blank: false, desc: 'Matrix of distance')
end
optional(:points, type: Array) do
SimpleAPI.vrp_request_point(self)
end
end
end
post '/' do
'hello'
end
end
class GivenAPI < Grape::API
prefix :api
version 'v1', using: :path
content_type :json, 'application/json; charset=UTF-8'
default_format :json
def self.vrp_request_point(this)
this.requires(:id, type: String, allow_blank: false)
this.optional(:location, type: Hash, allow_blank: false) do
requires(:lat, type: Float, allow_blank: false)
requires(:lon, type: Float, allow_blank: false)
end
end
params do
optional(:vrp, type: Hash, documentation: { param_type: 'body' }) do
optional(:name, type: String)
optional(:matrices, type: Array, documentation: { desc: 'Define the distances' }) do
requires(:id, type: String, allow_blank: false)
optional(:time, type: Array[Array[Float]], allow_blank: false, desc: 'Matrix of time')
optional(:distance, type: Array[Array[Float]], allow_blank: false, desc: 'Matrix of distance')
end
given matrices: ->(val) { val.nil? || val.empty? } do
optional(:points, type: Array, documentation: { desc: 'Particular place on the map' }) do
GivenAPI.vrp_request_point(self)
exactly_one_of :location
end
end
given matrices: ->(val) { val && !val.empty? } do
optional(:points, type: Array, documentation: { desc: 'Particular place in the map' }) do
GivenAPI.vrp_request_point(self)
optional(:matrix_index, type: Integer, allow_blank: false, desc: 'Index within the matrices')
exactly_one_of :matrix_index, :location
end
end
end
end
post '/' do
'hello'
end
end
options = {
method: 'POST',
params: JSON.parse(File.read('vrp_w_matrix_example.json')) # https://gist.github.com/senhalil/263188ebdbe04ef545fc4278838cd8d8#file-vrp_w_matrix_example-json
}
env = Rack::MockRequest.env_for('/api/v1', options)
Benchmark.ips do |ips|
ips.config(time: 60, warmup: 1)
ips.report('Given') do
GivenAPI.call env
end
ips.report('Simple') do
SimpleAPI.call env
end
ips.compare!
end |
Fixes #2100 The reason was in `ActiveSupport::HashWithIndifferentAccess`, it is super expensive. When users use `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder` or `Grape::Extensions::Hashie::Mash::ParamBuilder` as a parameter builder there is no change. However, users who use `Grape::Extensions::Hash::ParamBuilder` must make sure an attribute to be dependent on must be a symbol. given :matrix do # block here end
Fixes #2100 The reason was in `ActiveSupport::HashWithIndifferentAccess`, it is super expensive. When users use a `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder` or `Grape::Extensions::Hashie::Mash::ParamBuilder` parameter builder there is no change. However, users who use `Grape::Extensions::Hash::ParamBuilder` must make sure a parameter to be dependent on must be a symbol. given :matrix do # block here end Benchmark after this fix: Warming up -------------------------------------- Given 1.000 i/100ms Simple 1.000 i/100ms Calculating ------------------------------------- Given 0.804 (± 0.0%) i/s - 49.000 in 61.186831s Simple 0.855 (± 0.0%) i/s - 52.000 in 60.926097s Comparison: Simple: 0.9 i/s Given: 0.8 i/s - 1.06x slower
Fixes #2100 The reason was in `ActiveSupport::HashWithIndifferentAccess`, it is super expensive. When users use a `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder` or `Grape::Extensions::Hashie::Mash::ParamBuilder` parameter builder there is no change. However, users who use `Grape::Extensions::Hash::ParamBuilder` must make sure a parameter to be dependent on must be a symbol. given :matrix do # block here end Benchmark after this fix: Warming up -------------------------------------- Given 1.000 i/100ms Simple 1.000 i/100ms Calculating ------------------------------------- Given 0.804 (± 0.0%) i/s - 49.000 in 61.186831s Simple 0.855 (± 0.0%) i/s - 52.000 in 60.926097s Comparison: Simple: 0.9 i/s Given: 0.8 i/s - 1.06x slower
Fixes #2100 The reason was in `ActiveSupport::HashWithIndifferentAccess`, it is super expensive. When users use a `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder` or `Grape::Extensions::Hashie::Mash::ParamBuilder` parameter builder there is no change. However, users who use `Grape::Extensions::Hash::ParamBuilder` must make sure a parameter to be dependent on must be a symbol. given :matrix do # block here end Benchmark after this fix: Warming up -------------------------------------- Given 1.000 i/100ms Simple 1.000 i/100ms Calculating ------------------------------------- Given 0.804 (± 0.0%) i/s - 49.000 in 61.186831s Simple 0.855 (± 0.0%) i/s - 52.000 in 60.926097s Comparison: Simple: 0.9 i/s Given: 0.8 i/s - 1.06x slower
Fixes #2100 The reason was in `ActiveSupport::HashWithIndifferentAccess`, it is super expensive. When users use a `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder` or `Grape::Extensions::Hashie::Mash::ParamBuilder` parameter builder there is no change. However, users who use `Grape::Extensions::Hash::ParamBuilder` must make sure a parameter to be dependent on must be a symbol. given :matrix do # block here end Benchmark after this fix: Warming up -------------------------------------- Given 1.000 i/100ms Simple 1.000 i/100ms Calculating ------------------------------------- Given 0.804 (± 0.0%) i/s - 49.000 in 61.186831s Simple 0.855 (± 0.0%) i/s - 52.000 in 60.926097s Comparison: Simple: 0.9 i/s Given: 0.8 i/s - 1.06x slower
Fixes #2100 The reason was in `ActiveSupport::HashWithIndifferentAccess`, it is super expensive. When users use a `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder` or `Grape::Extensions::Hashie::Mash::ParamBuilder` parameter builder there is no change. However, users who use `Grape::Extensions::Hash::ParamBuilder` must make sure a parameter to be dependent on must be a symbol. given :matrix do # block here end Benchmark after this fix: Warming up -------------------------------------- Given 1.000 i/100ms Simple 1.000 i/100ms Calculating ------------------------------------- Given 0.804 (± 0.0%) i/s - 49.000 in 61.186831s Simple 0.855 (± 0.0%) i/s - 52.000 in 60.926097s Comparison: Simple: 0.9 i/s Given: 0.8 i/s - 1.06x slower
Amazing! Thanks for following this through @dnesteryuk |
@senhalil did you try the master branch on your project? |
@dnesteryuk tried with the same benchmark and it looks great 👇 Cheers!
|
@dnesteryuk @dblock I don't know how it can be done in a proper way which can be used in an automated way but if you would like to turn this benchmark into a proper benchmark or a test, let me know! The lowest I could drop the duration of the benchmark is around a minute in total which is kind of too much but I am afraid anything shorter than this would not give reliable results (at least that's the case on my machine).
|
We check-in these benchmarks into https://github.com/ruby-grape/grape/tree/master/benchmark, I wonder if there's a way to run them as part of CI and have them fail if the threshold is breached? |
When change one of the parameters of out API as dependent to another one, the post duration increased from ~30 seconds to ~600 seconds. Is this normal?
Our model is deeply nested but both the dependee and depender are on the first level.
The text was updated successfully, but these errors were encountered: