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 ref fa0bc95
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:incache_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:
However:
{
"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
}
]
}
{
"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
Flamegraph
- Flamegraph of master with
bin/serve_benchmark start and the flamegraph gem
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!
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.Number vary somewhat over runs but differences are consistent.
Environment
0.10.0.rc4, on reffa0bc95ruby -e "puts RUBY_DESCRIPTION"ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin14]OS Type & Version:
uname -aDarwin 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.15Integrated application and version
bundle show activemodel.bundle/ruby/2.2.0/gems/activemodel-4.0.13Backtrace
N/A
Additional helpful information
Cache developments since then:
However:
{ "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 } ] }{ "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:
Possibly related
Flamegraph
bin/serve_benchmark startand the flamegraph gem