Description
Expected behavior vs actual behavior
Expected: Configure a cache and using the AMS serializer cache method should improve rendering performance.
Actual: performance decreases AND more objects are allocated.
Steps to reproduce
current master: git co fa0bc95
.
bin/bench
caching on: caching serializers: gc off 606.0970710386515/ips; 1853 objects
caching off: caching serializers: gc off 526.5338285238549/ips; 1853 objects
caching on: non-caching serializers: gc off 709.8031139840541/ips; 1390 objects
caching off: non-caching serializers: gc off 746.4513428127035/ips; 1390 objects
Benchmark results:
{
"commit_hash": "fa0bc95",
"version": "0.10.0.rc4",
"benchmark_run[environment]": "2.2.3p173",
"runs": [
{
"benchmark_type[category]": "caching on: caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 606.097,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1853
},
{
"benchmark_type[category]": "caching off: caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 526.534,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1853
},
{
"benchmark_type[category]": "caching on: non-caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 709.803,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1390
},
{
"benchmark_type[category]": "caching off: non-caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 746.451,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1390
}
]
}
CACHE_ON=false bin/bench
caching on: caching serializers: gc off 664.8712562099971/ips; 1853 objects
caching off: caching serializers: gc off 613.6203762167032/ips; 1853 objects
caching on: non-caching serializers: gc off 752.267454951568/ips; 1390 objects
caching off: non-caching serializers: gc off 692.4981276214933/ips; 1390 objects
Benchmark results:
{
"commit_hash": "fa0bc95",
"version": "0.10.0.rc4",
"benchmark_run[environment]": "2.2.3p173",
"runs": [
{
"benchmark_type[category]": "caching on: caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 664.871,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1853
},
{
"benchmark_type[category]": "caching off: caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 613.62,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1853
},
{
"benchmark_type[category]": "caching on: non-caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 752.267,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1390
},
{
"benchmark_type[category]": "caching off: non-caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 692.498,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1390
}
]
}
Number vary somewhat over runs but differences are consistent.
Environment
- ActiveModelSerializers Version
0.10.0.rc4
, on reffa0bc95
ruby -e "puts RUBY_DESCRIPTION"
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin14]
OS Type & Version:
uname -a
Darwin mbp14 14.5.0 Darwin Kernel Version 14.5.0: Tue Sep 1 21:23:09 PDT 2015; root:xnu-2782.50.1~1/RELEASE_X86_64 x86_64
: Yosemite 10.10.15
Integrated application and version
bundle show activemodel
.bundle/ruby/2.2.0/gems/activemodel-4.0.13
Backtrace
N/A
Additional helpful information
- https://blog.codeship.com/building-a-json-api-with-rails-5/
-
By making these changes, we’ve changed our response time from 30ms to 50ms… wait, what? Yes, you heard me right. By adding cache, responses in my application have actually slowed down.
- https://twitter.com/leighchalliday/status/642734572703236096 and https://twitter.com/joaomdmoura/status/642801896231727104
-
By looking at the flame graph with caching turned on, I could tell that 48 percent of the time was spent in the cache_check method or farther down in the stack trace. This seems to account for the slowdown from 30ms to 50ms.
active_model_serializers-258f116c3cf5/lib/active_model/serializer/adapter.rb:110:in
cache_check'` (48 samples - 48.00%)
Here’s an image of the flamegraph, which was produced by using rack mini profiler gem with the flamegraph gem. I’ve highlighted in black the portion that’s dealing with the cache.
-
Cache developments since then:
- We now support read_multi
However:
- before:
{
"commit_hash": "43312fa^",
"version": "0.10.0.rc3",
"benchmark_run[environment]": "2.2.2p95",
"runs": [
{
"benchmark_type[category]": "caching on: caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 687.045,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1426
},
{
"benchmark_type[category]": "caching off: caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 688.588,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1426
},
{
"benchmark_type[category]": "caching on: non-caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 849.889,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1084
},
{
"benchmark_type[category]": "caching off: non-caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 769.596,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1084
}
]
}
- after:
{
"commit_hash": "43312fa",
"version": "0.10.0.rc3",
"benchmark_run[environment]": "2.2.2p95",
"runs": [
{
"benchmark_type[category]": "caching on: caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 635.297,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1519
},
{
"benchmark_type[category]": "caching off: caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 601.3,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1519
},
{
"benchmark_type[category]": "caching on: non-caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 782.07,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1113
},
{
"benchmark_type[category]": "caching off: non-caching serializers: gc off",
"benchmark_run[result][iterations_per_second]": 771.094,
"benchmark_run[result][total_allocated_objects_per_iteration]": 1113
}
]
}
So maybe we should take a look at usage in bulk_cache_fetcher
And with more objects it gets worse:
BENCH_STRESS=true bin/bench
Benchmark results:{ "commit_hash": "e03c5f5", "version": "0.10.0.rc4", "benchmark_run[environment]": "2.2.3p173", "runs": [ { "benchmark_type[category]": "caching on: caching serializers: gc off", "benchmark_run[result][iterations_per_second]": 164.688, "benchmark_run[result][total_allocated_objects_per_iteration]": 10755 }, { "benchmark_type[category]": "caching off: caching serializers: gc off", "benchmark_run[result][iterations_per_second]": 143.719, "benchmark_run[result][total_allocated_objects_per_iteration]": 10755 }, { "benchmark_type[category]": "caching on: non-caching serializers: gc off", "benchmark_run[result][iterations_per_second]": 232.669, "benchmark_run[result][total_allocated_objects_per_iteration]": 6690 }, { "benchmark_type[category]": "caching off: non-caching serializers: gc off", "benchmark_run[result][iterations_per_second]": 211.71, "benchmark_run[result][total_allocated_objects_per_iteration]": 6690 } ] }
Possibly related
- Cache not working
- Modify cache key to prevent errors and incorrect data returned. Fixes #1344
- Trigger callback to set serializer#_cache when controller loaded
Flamegraph
iff --git a/Gemfile b/Gemfile
index 3791eef..7be3d53 100644
--- a/Gemfile
+++ b/Gemfile
@@ -39,6 +39,8 @@ gem 'tzinfo-data', platforms: (@windows_platforms + [:jruby])
group :bench do
# https://github.com/rails-api/active_model_serializers/commit/cb4459580a6f4f37f629bf3185a5224c8624ca76
gem 'benchmark-ips', require: false, group: :development
+ gem 'rack-mini-profiler', require: false
+ gem 'flamegraph'
end
group :test do
diff --git a/test/benchmark/app.rb b/test/benchmark/app.rb
index ae110ec..ffbc8cc 100644
--- a/test/benchmark/app.rb
+++ b/test/benchmark/app.rb
@@ -54,6 +54,14 @@ end
require 'active_model_serializers'
+begin
+ require 'rack-mini-profiler'
+rescue LoadError # rubocop:disable Lint/HandleExceptions
+else
+ require 'flamegraph'
+ # just append ?pp=flamegraph
+end
+
# Initialize app before any serializers are defined, for running across revisions.
# ref: https://github.com/rails-api/active_model_serializers/pull/1478
Rails.application.initialize!