Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 0 additions & 24 deletions .github/workflows/rspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,3 @@ jobs:

- name: Run tests
run: bundle exec rspec

- name: 'Upload Coverage Report'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: ./coverage

coverage:
needs: [ test ]
name: coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download Coverage Report
uses: actions/download-artifact@v4
with:
name: coverage-report
path: ./coverage
- uses: paambaati/codeclimate-action@v9.0.0
env:
# Set CC_TEST_REPORTER_ID as secret of your repo
CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}}
with:
debug: true
1 change: 1 addition & 0 deletions benchmark/Gemfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
source 'http://rubygems.org'
gem 'class_kit'
gem 'benchmark'
20 changes: 16 additions & 4 deletions benchmark/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
GEM
remote: http://rubygems.org/
specs:
class_kit (0.6.0)
benchmark (0.5.0)
bigdecimal (4.0.1)
class_kit (0.9.1)
bigdecimal
hash_kit
json
hash_kit (0.6.0)
json (2.1.0)
hash_kit (0.7.0)
json (2.18.1)

PLATFORMS
ruby
x86_64-darwin-25

DEPENDENCIES
benchmark
class_kit

CHECKSUMS
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
class_kit (0.9.1) sha256=0b31f65130a2b99807883cbf211e8d22d5f10f0f5a7beb3ff8336a51a62db0b2
hash_kit (0.7.0) sha256=9ff39a55fb4df2ebf524751862f2178d1778cbaab3812ef7bddfb26f55d0f90c
json (2.18.1) sha256=fe112755501b8d0466b5ada6cf50c8c3f41e897fa128ac5d263ec09eedc9f986

BUNDLED WITH
1.16.1
4.0.4
232 changes: 223 additions & 9 deletions benchmark/benchmark.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,40 @@
require_relative '../lib/class_kit'
require 'date'
require 'benchmark'
require 'json'

class Item
# -------------------------------------------------------------------
# Test entities
# -------------------------------------------------------------------

class BenchContact
extend ClassKit
attr_accessor_type :landline, type: String
attr_accessor_type :mobile, type: String
attr_accessor_type :email, type: String
end

class BenchAddress
extend ClassKit
attr_accessor_type :line1, type: String
attr_accessor_type :line2, type: String
attr_accessor_type :postcode, type: String
attr_accessor_type :country, type: String
end

class BenchEmployee
extend ClassKit
attr_accessor_type :name, type: String
attr_accessor_type :age, type: Integer
attr_accessor_type :salary, type: Float
attr_accessor_type :dob, type: Date
attr_accessor_type :active, type: :bool
attr_accessor_type :address, type: BenchAddress
attr_accessor_type :contacts, type: Array, collection_type: BenchContact
end

class BenchFlatItem
extend ClassKit
attr_accessor_type :text, type: String
attr_accessor_type :integer, type: Integer
attr_accessor_type :float, type: Float
Expand All @@ -13,9 +43,19 @@ class Item
attr_accessor_type :bool, type: :bool
end

items = []
10_000.times do
items << Item.new.tap do |e|
class BenchDeeplyNested
extend ClassKit
attr_accessor_type :name, type: String
attr_accessor_type :child, type: BenchEmployee
attr_accessor_type :employees, type: Array, collection_type: BenchEmployee
end

# -------------------------------------------------------------------
# Data builders
# -------------------------------------------------------------------

def build_flat_item
BenchFlatItem.new.tap do |e|
e.text = 'foo bar'
e.integer = 50
e.float = 25.2
Expand All @@ -25,12 +65,186 @@ class Item
end
end

def build_address
BenchAddress.new.tap do |a|
a.line1 = '25 The Street'
a.line2 = 'Home Town'
a.postcode = 'NE3 5RT'
a.country = 'United Kingdom'
end
end

def build_contact
BenchContact.new.tap do |c|
c.landline = '01234567890'
c.mobile = '07891234567'
c.email = 'test@example.com'
end
end

def build_employee
BenchEmployee.new.tap do |e|
e.name = 'Joe Bloggs'
e.age = 42
e.salary = 55_000.50
e.dob = Date.parse('1980-06-03')
e.active = true
e.address = build_address
e.contacts = [build_contact, build_contact]
end
end

def build_deeply_nested
BenchDeeplyNested.new.tap do |d|
d.name = 'Root'
d.child = build_employee
d.employees = 5.times.map { build_employee }
end
end

# -------------------------------------------------------------------
# Benchmark runner
# -------------------------------------------------------------------

ITERATIONS = 5
SIZES = { small: 100, medium: 1_000, large: 10_000 }

helper = ClassKit::Helper.new

json = ''
def separator
puts '-' * 70
end

def run_benchmark(label, iterations: ITERATIONS)
times = iterations.times.map do
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield
Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
end
median = times.sort[times.size / 2]
best = times.min
printf " %-50s median: %8.4fs best: %8.4fs\n", label, median, best
median
end

puts '=' * 70
puts 'ClassKit Benchmark Suite'
puts "Ruby #{RUBY_VERSION} | #{RUBY_PLATFORM}"
puts '=' * 70

results = {}

# -------------------------------------------------------------------
# 1. Flat serialization (to_json / from_json)
# -------------------------------------------------------------------
puts "\n### 1. Flat items (6 typed attributes, no nesting)"
separator

puts '***serialize items***'
puts Benchmark.measure { json = helper.to_json(items) }
SIZES.each do |size_name, count|
items = count.times.map { build_flat_item }
json = helper.to_json(items)

puts '***deserialize items***'
puts Benchmark.measure { helper.from_json(json: json, klass: Item) }
key_ser = "flat_#{size_name}_serialize"
key_de = "flat_#{size_name}_deserialize"

results[key_ser] = run_benchmark("to_json #{count} flat items") { helper.to_json(items) }
results[key_de] = run_benchmark("from_json #{count} flat items") do
helper.from_json(json: json, klass: BenchFlatItem)
end
end

# -------------------------------------------------------------------
# 2. Nested serialization (Employee with Address + Contacts)
# -------------------------------------------------------------------
puts "\n### 2. Nested items (Employee -> Address + 2x Contact)"
separator

SIZES.each do |size_name, count|
items = count.times.map { build_employee }
json = helper.to_json(items)

key_ser = "nested_#{size_name}_serialize"
key_de = "nested_#{size_name}_deserialize"

results[key_ser] = run_benchmark("to_json #{count} nested items") { helper.to_json(items) }
results[key_de] = run_benchmark("from_json #{count} nested items") do
helper.from_json(json: json, klass: BenchEmployee)
end
end

# -------------------------------------------------------------------
# 3. Deeply nested (3 levels deep with arrays)
# -------------------------------------------------------------------
puts "\n### 3. Deeply nested items (3 levels, arrays of nested)"
separator

[10, 100, 500].each do |count|
items = count.times.map { build_deeply_nested }
json = helper.to_json(items)

key_ser = "deep_#{count}_serialize"
key_de = "deep_#{count}_deserialize"

results[key_ser] = run_benchmark("to_json #{count} deep items") { helper.to_json(items) }
results[key_de] = run_benchmark("from_json #{count} deep items") do
helper.from_json(json: json, klass: BenchDeeplyNested)
end
end

# -------------------------------------------------------------------
# 4. Hash round-trip (no JSON overhead)
# -------------------------------------------------------------------
puts "\n### 4. Hash round-trip (to_hash / from_hash, 1000 nested)"
separator

items = 1_000.times.map { build_employee }
hashes = items.map { |i| helper.to_hash(i) }

results['hash_serialize'] = run_benchmark('to_hash 1000 nested items') { items.each { |i| helper.to_hash(i) } }
results['hash_deserialize'] = run_benchmark('from_hash 1000 nested items') do
hashes.each do |h|
helper.from_hash(hash: h, klass: BenchEmployee)
end
end

# -------------------------------------------------------------------
# 5. Micro: single object round-trip
# -------------------------------------------------------------------
puts "\n### 5. Micro: single object (10_000 iterations)"
separator

employee = build_employee
employee_json = helper.to_json(employee)
employee_hash = helper.to_hash(employee)

results['micro_to_json'] = run_benchmark('to_json single employee x10k') do
10_000.times do
helper.to_json(employee)
end
end
results['micro_from_json'] = run_benchmark('from_json single employee x10k') do
10_000.times do
helper.from_json(json: employee_json, klass: BenchEmployee)
end
end
results['micro_to_hash'] = run_benchmark('to_hash single employee x10k') do
10_000.times do
helper.to_hash(employee)
end
end
results['micro_from_hash'] = run_benchmark('from_hash single employee x10k') do
10_000.times do
helper.from_hash(hash: employee_hash, klass: BenchEmployee)
end
end

# -------------------------------------------------------------------
# Summary
# -------------------------------------------------------------------
puts "\n"
puts '=' * 70
puts 'Summary (median times in seconds)'
puts '=' * 70
results.sort_by { |_, v| -v }.each do |label, time|
printf " %-45s %8.4fs\n", label, time
end