<repo_name>shuheiktgw/benchmark_fast_jsonapi<file_sep>/spec/serializers/performance_with_json_api_adapter_spec.rb
require 'rails_helper'

describe 'Serializers Performance with json_api adapter' do
  before(:all) do
    ActiveModel::Serializer.config.adapter = :json_api

    class AmsCompanySerializer < ActiveModel::Serializer
      attributes :name, :updated_at, :created_at

      has_many :ams_employees
    end

    class AmsEmployeeSerializer < ActiveModel::Serializer
      attributes :name, :position, :age, :updated_at, :created_at

      belongs_to :ams_company
    end

    class FjaCompanySerializer
      include FastJsonapi::ObjectSerializer

      attributes :name, :updated_at, :created_at

      has_many :fja_employees
    end

    class FjaEmployeeSerializer
      include FastJsonapi::ObjectSerializer

      attributes :name, :position, :age, :updated_at, :created_at

      belongs_to :fja_company
    end
  end

  before(:all) { GC.disable }
  after(:all) { GC.enable }

  before do
    ams_companies = create_list(:ams_company, company_count)
    ams_companies.each do |ams_company|
      create_list(:ams_employee, employee_count, ams_company: ams_company)
    end

    fja_companies = create_list(:fja_company, company_count)
    fja_companies.each do |fja_company|
      create_list(:fja_employee, employee_count, fja_company: fja_company)
    end
  end

  shared_examples 'serialize record' do
    it 'should serialize record' do
      puts "#{ company_count } companies"
      puts "#{ employee_count } employees"

      fja_total_time = 0
      ams_total_time = 0

      100.times do
        fja_total_time += Benchmark.measure { FjaCompanySerializer.new(FjaCompany.preload(:fja_employees).all, include: [:fja_employees]).serialized_json }.real * 1000
        ams_total_time += Benchmark.measure { ActiveModelSerializers::SerializableResource.new(AmsCompany.preload(:ams_employees).all, include: [:ams_employees]).to_json }.real * 1000
      end

      print 'FastJsonapi:              '
      puts fja_total_time / 100.0

      print 'ActiveModel::Serializer:  '
      puts ams_total_time / 100.0
    end
  end

  context '1 companies' do
    let(:company_count) { 1 }

    context '1 employees' do
      let(:employee_count) { 1 }

      it_behaves_like 'serialize record'
    end

    context '25 employees' do
      let(:employee_count) { 25 }

      it_behaves_like 'serialize record'
    end

    context '250 employees' do
      let(:employee_count) { 250 }

      it_behaves_like 'serialize record'
    end
  end

  context '25 companies' do
    let(:company_count) { 25 }

    context '1 employees' do
      let(:employee_count) { 1 }

      it_behaves_like 'serialize record'
    end

    context '25 employees' do
      let(:employee_count) { 25 }

      it_behaves_like 'serialize record'
    end

    context '250 employees' do
      let(:employee_count) { 250 }

      it_behaves_like 'serialize record'
    end
  end
end
<file_sep>/app/models/ams_company.rb
class AmsCompany < ApplicationRecord
  has_many :ams_employees
end
<file_sep>/spec/factories/ams_company.rb
FactoryGirl.define do
  factory :ams_company, class: AmsCompany do
    name 'Gooogle'
  end
end
<file_sep>/app/models/ams_employee.rb
class AmsEmployee < ApplicationRecord
  belongs_to :ams_company
end
<file_sep>/app/models/fja_company.rb
class FjaCompany < ApplicationRecord
  has_many :fja_employees
end
<file_sep>/README.md
# Benchmark for Netflix/fast_jsonapi

This is a sample Rails application to benchmark [Netflix/fast_jsonapi](https://github.com/Netflix/fast_jsonapi) in a real Rails environment. 

# How it works?
I created `Company has many employees` relationship with `AmsCompany.rb` and `AmsEmployee.rb` to test `ActiveModel::Serializer`,  and `FjaCompany.rb` and `FjaEmployee.rb` to test `FastJsonapi`.

Also, for `ActiveModel::Serializer` I used two adapters, `default adapter` and `json_api adapter`. `json_api adapter` formats the result in the similar form to Json:API.


All the benchmarks are taken in `spec/serializers/performance_with_json_api_adapter_spec.rb` and `performance_with_default_adapter_spec.rb`.  

# Results

### `FastJsonapi` (FJA) against`ActiveModel::Serializer` (AMS) with `json_api adapter`

|  | Serializer | 1 company | 25 companies |
|:-----------:|:------------:|:------------|:------------|
| 1 employee | AMS | 32.995999999911874 | 60.81799999992654 |
| 1 employee | FJA | 19.072999999934837 | 8.621999999832042 |
| 25 employees | AMS | 35.11500000013257 | 577.1009999998569 |
| 25 employees | FJA | 6.498000000192405 | 85.53800000026968 |
| 250 employees | AMS | 246.90600000030827 | 5089.174999999614 |
| 250 employees | FJA | 32.62899999981528 | 754.1630000000623 |

### `FastJsonapi` against`ActiveModel::Serializer` with `default adapter`

|  | Serializer | 1 company | 25 companies |
|:-----------:|:------------:|:------------|:------------|
| 1 employee | AMS | 15.873000000283355 | 24.73100000042905 |
| 1 employee | FJA | 16.28900000014255 | 8.800000000519503 |
| 25 employees | AMS | 7.133000000067113 | 134.71600000048056 |
| 25 employees | FJA | 6.07899999977235 | 84.16800000031799 |
| 250 employees | AMS | 52.87399999997433 | 855.4990000002363 |
| 250 employees | FJA | 31.502000000728003 | 1261.395999999877 |

The results above show two things.

1. `FastJsonapi` is about 4 - 7 times faster than`ActiveModel::Serializer` with `json_api adapter`.
2. `FastJsonapi` is about 1.5 times faster than`ActiveModel::Serializer` with `default adapter`. 

# How to take a benchmark?

Install dependencies first.

```
$ bundle install 
```

Then create tables.

```
$ bundle ex rake db:create
$ bundle ex rake db:migrate
$ bundle ex rake db:migrate RIALS_ENV=test 
```

Finally run spec.

```
$ bundle ex rspec
```
<file_sep>/app/models/fja_employee.rb
class FjaEmployee < ApplicationRecord
  belongs_to :fja_company
end
<file_sep>/spec/factories/ams_employee.rb
FactoryGirl.define do
  sequence :name do |n|
    "Employee #{n}"
  end

  factory :ams_employee, class: AmsEmployee do
    name
    age 25
    position 'engineer'
  end
end
<file_sep>/db/migrate/20180313035401_create_fja_employees.rb
class CreateFjaEmployees < ActiveRecord::Migration[5.1]
  def change
    create_table :fja_employees do |t|
      t.references :fja_company
      t.string :name
      t.string :position
      t.integer :age

      t.timestamps
    end
  end
end
<file_sep>/spec/serializers/profiler_spec.rb
require 'rails_helper'
require 'profiler'
require 'rblineprof'
require 'rblineprof-report'

describe 'Serializers Performance with default adapter' do
  before(:all) do
    class AmsCompanySerializer < ActiveModel::Serializer
      attributes :name, :updated_at, :created_at

      has_many :ams_employees
    end

    class AmsEmployeeSerializer < ActiveModel::Serializer
      attributes :name, :position, :age, :updated_at, :created_at

      belongs_to :ams_company
    end

    class FjaCompanySerializer
      include FastJsonapi::ObjectSerializer

      attributes :name, :updated_at, :created_at

      has_many :fja_employees
    end

    class FjaEmployeeSerializer
      include FastJsonapi::ObjectSerializer

      attributes :name, :position, :age, :updated_at, :created_at

      belongs_to :fja_company
    end
  end

  before(:all) { GC.disable }
  after(:all) { GC.enable }

  before do
    ams_companies = create_list(:ams_company, company_count)
    ams_companies.each do |ams_company|
      create_list(:ams_employee, employee_count, ams_company: ams_company)
    end

    fja_companies = create_list(:fja_company, company_count)
    fja_companies.each do |fja_company|
      create_list(:fja_employee, employee_count, fja_company: fja_company)
    end
  end

  shared_examples 'serialize record' do
    it 'should serialize record' do
      puts '--------------------------------------------------------------------------------------------------------'
      puts
      puts
      puts 'FastJsonapi'

      puts (Benchmark.measure { FjaCompanySerializer.new(FjaCompany.preload(:fja_employees).all, include: [:fja_employees]).serialized_json }.real * 1000).to_s + ' ms'

      puts
      puts
      puts '--------------------------------------------------------------------------------------------------------'
      puts
      puts
      puts 'ActiveModelSerializers with default adaptor'

      ActiveModel::Serializer.config.adapter = :attributes
      puts (Benchmark.measure { ActiveModelSerializers::SerializableResource.new(AmsCompany.preload(:ams_employees).all, include: [:ams_employees]).to_json }.real * 1000).to_s + ' ms'

      puts
      puts
      puts '--------------------------------------------------------------------------------------------------------'
      puts
      puts
      puts 'ActiveModelSerializers with json_api adaptor'

      ActiveModel::Serializer.config.adapter = :json_api
      puts (Benchmark.measure { ActiveModelSerializers::SerializableResource.new(AmsCompany.preload(:ams_employees).all, include: [:ams_employees]).to_json }.real * 1000).to_s + ' ms'
    end
  end

  shared_examples 'serialize record with profiler' do
    it 'should serialize record' do
      puts
      puts
      puts '--------------------------------------------------------------------------------------------------------'
      puts
      puts
      puts 'FastJsonapi'

      Profiler__::start_profile
      FjaCompanySerializer.new(FjaCompany.preload(:fja_employees).all, include: [:fja_employees]).serialized_json
      Profiler__::print_profile(STDERR)
      Profiler__::stop_profile


      puts
      puts
      puts '--------------------------------------------------------------------------------------------------------'
      puts
      puts
      puts 'ActiveModelSerializers with default adaptor'

      ActiveModel::Serializer.config.adapter = :attributes
      Profiler__::start_profile
      ActiveModelSerializers::SerializableResource.new(AmsCompany.preload(:ams_employees).all).to_json
      Profiler__::print_profile(STDERR)
      Profiler__::stop_profile

      puts
      puts
      puts '--------------------------------------------------------------------------------------------------------'
      puts
      puts
      puts 'ActiveModelSerializers with json_api adaptor'

      ActiveModel::Serializer.config.adapter = :json_api
      Profiler__::start_profile
      ActiveModelSerializers::SerializableResource.new(AmsCompany.preload(:ams_employees).all).to_json
      Profiler__::print_profile(STDERR)
      Profiler__::stop_profile
    end
  end

  shared_examples 'serialize record with line profiler' do
    it 'should serialize record' do
      puts
      puts
      puts '--------------------------------------------------------------------------------------------------------'
      puts
      puts
      puts 'FastJsonapi'

      fja_profile = lineprof(/./) do
        FjaCompanySerializer.new(FjaCompany.preload(:fja_employees).all, include: [:fja_employees]).serialized_json
      end

      LineProf.report(fja_profile)

      puts
      puts
      puts '--------------------------------------------------------------------------------------------------------'
      puts
      puts
      puts 'ActiveModelSerializers with default adaptor'


      ActiveModel::Serializer.config.adapter = :attributes

      asm_profile_default = lineprof(/./) do
        ActiveModelSerializers::SerializableResource.new(AmsCompany.preload(:ams_employees).all).to_json
      end

      LineProf.report(asm_profile_default)

      puts
      puts
      puts '--------------------------------------------------------------------------------------------------------'
      puts
      puts
      puts 'ActiveModelSerializers with json_api adaptor'

      ActiveModel::Serializer.config.adapter = :json_api
      asm_profile_json = lineprof(/./) do
        ActiveModelSerializers::SerializableResource.new(AmsCompany.preload(:ams_employees).all).to_json
      end

      LineProf.report(asm_profile_json)
    end
  end

  context '1 companies' do
    let(:company_count) { 1 }

    context '250 employees' do
      let(:employee_count) { 250 }

      it_behaves_like 'serialize record'
      it_behaves_like 'serialize record with profiler'
      it_behaves_like 'serialize record with line profiler'
    end
  end
end
<file_sep>/spec/factories/fja_employee.rb
FactoryGirl.define do

  factory :fja_employee, class: FjaEmployee do
    name
    age 25
    position 'engineer'
  end
end
<file_sep>/spec/factories/fja_company.rb
FactoryGirl.define do
  factory :fja_company, class: FjaCompany do
    name 'Gooogle'
  end
end
