benchmark/local.rb in alba-1.0.1 vs benchmark/local.rb in alba-1.1.0
- old
+ new
@@ -1,34 +1,39 @@
# Benchmark script to run varieties of JSON serializers
# Fetch Alba from local, otherwise fetch latest from RubyGems
+# --- Bundle dependencies ---
+
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
-
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
- gem "activerecord", "6.1.3"
- gem "sqlite3"
- gem "jbuilder"
gem "active_model_serializers"
- gem "blueprinter"
- gem "representable"
+ gem "activerecord", "6.1.3"
gem "alba", path: '../'
- gem "oj"
+ gem "benchmark-ips"
+ gem "blueprinter"
+ gem "jbuilder"
+ gem "jsonapi-serializer" # successor of fast_jsonapi
gem "multi_json"
+ gem "oj"
+ gem "representable"
+ gem "sqlite3"
end
+# --- Test data model setup ---
+
require "active_record"
-require "sqlite3"
require "logger"
require "oj"
+require "sqlite3"
Oj.optimize_rails
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
-# ActiveRecord::Base.logger = Logger.new(STDOUT)
+# ActiveRecord::Base.logger = Logger.new($stdout)
ActiveRecord::Schema.define do
create_table :posts, force: true do |t|
t.string :body
end
@@ -68,32 +73,71 @@
class User < ActiveRecord::Base
has_many :comments
end
+# --- Alba serializers ---
+
require "alba"
-Alba.backend = :oj
class AlbaCommentResource
include ::Alba::Resource
attributes :id, :body
end
class AlbaPostResource
include ::Alba::Resource
attributes :id, :body
- many :comments, resource: AlbaCommentResource
attribute :commenter_names do |post|
post.commenters.pluck(:name)
end
+ many :comments, resource: AlbaCommentResource
end
+# --- ActiveModelSerializer serializers ---
+
+require "active_model_serializers"
+
+class AMSCommentSerializer < ActiveModel::Serializer
+ attributes :id, :body
+end
+
+class AMSPostSerializer < ActiveModel::Serializer
+ attributes :id, :body
+ attribute :commenter_names
+ has_many :comments, serializer: AMSCommentSerializer
+
+ def commenter_names
+ object.commenters.pluck(:name)
+ end
+end
+
+# --- Blueprint serializers ---
+
+require "blueprinter"
+
+class CommentBlueprint < Blueprinter::Base
+ fields :id, :body
+end
+
+class PostBlueprint < Blueprinter::Base
+ fields :id, :body, :commenter_names
+ association :comments, blueprint: CommentBlueprint
+
+ def commenter_names
+ commenters.pluck(:name)
+ end
+end
+
+# --- JBuilder serializers ---
+
require "jbuilder"
+
class Post
def to_builder
Jbuilder.new do |post|
- post.call(self, :id, :body, :comments, :commenter_names)
+ post.call(self, :id, :body, :commenter_names, :comments)
end
end
def commenter_names
commenters.pluck(:name)
@@ -106,39 +150,73 @@
comment.call(self, :id, :body)
end
end
end
-require "active_model_serializers"
+# --- JSONAPI:Serializer serializers / (successor of fast_jsonapi) ---
-class AMSCommentSerializer < ActiveModel::Serializer
- attributes :id, :body
+class JsonApiStandardCommentSerializer
+ include JSONAPI::Serializer
+
+ attribute :id
+ attribute :body
end
-class AMSPostSerializer < ActiveModel::Serializer
- attributes :id, :body
- has_many :comments, serializer: AMSCommentSerializer
+class JsonApiStandardPostSerializer
+ include JSONAPI::Serializer
+
+ # set_type :post # optional
+ attribute :id
+ attribute :body
attribute :commenter_names
- def commenter_names
- object.commenters.pluck(:name)
+
+ attribute :comments do |post|
+ post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
end
end
-require "blueprinter"
+# --- JSONAPI:Serializer serializers that format the code the same flat way as the other gems here ---
-class CommentBlueprint < Blueprinter::Base
- fields :id, :body
+# code to convert from JSON:API output to "flat" JSON, like the other serializers build
+class JsonApiSameFormatSerializer
+ include JSONAPI::Serializer
+
+ def as_json(*_options)
+ hash = serializable_hash
+
+ if hash[:data].is_a? Hash
+ hash[:data][:attributes]
+
+ elsif hash[:data].is_a? Array
+ hash[:data].pluck(:attributes)
+
+ elsif hash[:data].nil?
+ { }
+
+ else
+ raise "unexpected data type #{hash[:data].class}"
+ end
+ end
end
-class PostBlueprint < Blueprinter::Base
- fields :id, :body, :commenter_names
- association :comments, blueprint: CommentBlueprint
- def commenter_names
- commenters.pluck(:name)
+class JsonApiSameFormatCommentSerializer < JsonApiSameFormatSerializer
+ attribute :id
+ attribute :body
+end
+
+class JsonApiSameFormatPostSerializer < JsonApiSameFormatSerializer
+ attribute :id
+ attribute :body
+ attribute :commenter_names
+
+ attribute :comments do |post|
+ post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
end
end
+# --- Representable serializers ---
+
require "representable"
class CommentRepresenter < Representable::Decorator
include Representable::JSON
@@ -157,23 +235,22 @@
def commenter_names
commenters.pluck(:name)
end
end
+# --- Test data creation ---
+
post = Post.create!(body: 'post')
user1 = User.create!(name: 'John')
user2 = User.create!(name: 'Jane')
post.comments.create!(commenter: user1, body: 'Comment1')
post.comments.create!(commenter: user2, body: 'Comment2')
post.reload
+# --- Store the serializers in procs ---
+
alba = Proc.new { AlbaPostResource.new(post).serialize }
-jbuilder = Proc.new { post.to_builder.target! }
-ams = Proc.new { AMSPostSerializer.new(post, {}).to_json }
-rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
-blueprinter = Proc.new { PostBlueprint.render(post) }
-representable = Proc.new { PostRepresenter.new(post).to_json }
alba_inline = Proc.new do
Alba.serialize(post) do
attributes :id, :body
attribute :commenter_names do |post|
post.commenters.pluck(:name)
@@ -181,18 +258,58 @@
many :comments do
attributes :id, :body
end
end
end
-[alba, jbuilder, ams, rails, blueprinter, representable, alba_inline].each {|x| puts x.call }
+ams = Proc.new { AMSPostSerializer.new(post, {}).to_json }
+blueprinter = Proc.new { PostBlueprint.render(post) }
+jbuilder = Proc.new { post.to_builder.target! }
+jsonapi = proc { JsonApiStandardPostSerializer.new(post).to_json }
+jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(post).to_json }
+rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
+representable = Proc.new { PostRepresenter.new(post).to_json }
+# --- Execute the serializers to check their output ---
+
+puts "Serializer outputs ----------------------------------"
+{
+ alba: alba,
+ alba_inline: alba_inline,
+ ams: ams,
+ blueprinter: blueprinter,
+ jbuilder: jbuilder, # different order
+ jsonapi: jsonapi, # nested JSON:API format
+ jsonapi_same_format: jsonapi_same_format,
+ rails: rails,
+ representable: representable
+}.each { |name, serializer| puts "#{name.to_s.ljust(24, ' ')} #{serializer.call}" }
+
+# --- Run the benchmarks ---
+
require 'benchmark'
time = 1000
Benchmark.bmbm do |x|
x.report(:alba) { time.times(&alba) }
- x.report(:jbuilder) { time.times(&jbuilder) }
+ x.report(:alba_inline) { time.times(&alba_inline) }
x.report(:ams) { time.times(&ams) }
- x.report(:rails) { time.times(&rails) }
x.report(:blueprinter) { time.times(&blueprinter) }
+ x.report(:jbuilder) { time.times(&jbuilder) }
+ x.report(:jsonapi) { time.times(&jsonapi) }
+ x.report(:jsonapi_same_format) { time.times(&jsonapi_same_format) }
+ x.report(:rails) { time.times(&rails) }
x.report(:representable) { time.times(&representable) }
- x.report(:alba_inline) { time.times(&alba_inline) }
+end
+
+require 'benchmark/ips'
+Benchmark.ips do |x|
+ x.report(:alba, &alba)
+ x.report(:alba_inline, &alba_inline)
+ x.report(:ams, &ams)
+ x.report(:blueprinter, &blueprinter)
+ x.report(:jbuilder, &jbuilder)
+ x.report(:jsonapi, &jsonapi)
+ x.report(:jsonapi_same_format, &jsonapi_same_format)
+ x.report(:rails, &rails)
+ x.report(:representable, &representable)
+
+ x.compare!
end