Skip to content

Instantly share code, notes, and snippets.

@flah00
Created August 13, 2012 21:39
Show Gist options
  • Save flah00/3344276 to your computer and use it in GitHub Desktop.
Save flah00/3344276 to your computer and use it in GitHub Desktop.
DataMapper-1.3.0-beta vs ActiveRecord 3.2.8 for postgres
# -*- encoding: utf-8 -*-
require File.expand_path('../lib/dm-core/version', __FILE__)
Gem::Specification.new do |gem|
gem.authors = [ "Dan Kubb" ]
gem.email = [ "dan.kubb@gmail.com" ]
gem.summary = "An Object/Relational Mapper for Ruby"
gem.description = "Faster, Better, Simpler."
gem.homepage = "http://datamapper.org"
gem.date = "2011-10-11"
gem.files = `git ls-files`.split("\n")
gem.test_files = `git ls-files -- {spec}/*`.split("\n")
gem.extra_rdoc_files = %w[LICENSE README.md]
gem.name = "dm-core"
gem.require_paths = [ "lib" ]
gem.version = DataMapper::VERSION
gem.add_runtime_dependency('addressable', '~> 2.2.6')
gem.add_runtime_dependency('virtus', '~> 0.5')
gem.add_development_dependency('rake', '~> 0.9.2')
gem.add_development_dependency('rspec', '~> 1.3.2')
gem.add_development_dependency('activerecord')
gem.add_development_dependency('addressable')
gem.add_development_dependency('faker')
gem.add_development_dependency('rbench', '~>0.2.3')
gem.add_development_dependency('do_postgres')
gem.add_development_dependency('pg')
end
GIT
remote: https://github.com/datamapper/dm-migrations.git
revision: 8bfcec08286a12ceee1bc3e5a01da3b5b7d4a74d
branch: master
specs:
dm-migrations (1.3.0.beta)
dm-core (~> 1.3.0.beta)
GIT
remote: https://github.com/solnic/virtus
revision: f21b1493cb64ba244402d32b1f80e68ae9ef15c1
specs:
virtus (0.5.1)
backports (~> 2.6.1)
PATH
remote: .
specs:
dm-core (1.3.0.beta)
addressable (~> 2.2.6)
virtus (~> 0.5)
GEM
remote: http://rubygems.org/
specs:
activemodel (3.2.8)
activesupport (= 3.2.8)
builder (~> 3.0.0)
activerecord (3.2.8)
activemodel (= 3.2.8)
activesupport (= 3.2.8)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activesupport (3.2.8)
i18n (~> 0.6)
multi_json (~> 1.0)
addressable (2.2.8)
arel (3.0.2)
backports (2.6.3)
builder (3.0.0)
data_objects (0.10.8)
addressable (~> 2.1)
do_postgres (0.10.8)
data_objects (= 0.10.8)
faker (1.0.1)
i18n (~> 0.4)
i18n (0.6.0)
multi_json (1.3.6)
pg (0.14.0)
rake (0.9.2.2)
rbench (0.2.3)
rcov (0.9.11)
rspec (1.3.2)
tzinfo (0.3.33)
yard (0.7.5)
yardstick (0.4.0)
yard (~> 0.7.0)
PLATFORMS
ruby
DEPENDENCIES
activerecord
addressable
dm-core!
dm-migrations (~> 1.3.0.beta)!
do_postgres
faker
pg
rake (~> 0.9.2)
rbench (~> 0.2.3)
rcov (~> 0.9.10)
rspec (~> 1.3.2)
virtus (~> 0.5)!
yard (~> 0.7.2)
yardstick (~> 0.4)
#!/usr/bin/env ruby -Ku
#Dir["/Users/pchampon/.rvm/gems/ruby-1.9.2-p290@dm-core-dev/bundler/gems/*"].each do |dir|
Dir["#{ENV["GEM_HOME"]}/bundler/gems/*"].each do |dir|
$: << dir + "/lib"
end
#require 'ftools'
require 'rubygems'
gem 'activerecord'
gem 'addressable', '~> 2.1'
gem 'faker'
gem 'rbench', '~> 0.2.3'
require 'dm-migrations'
require 'dm-transactions'
require 'active_record'
require 'addressable/uri'
require 'faker'
require 'rbench'
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'dm-core'))
socket_file = Pathname.glob(%w[
/opt/local/var/run/mysql5/mysqld.sock
tmp/mysqld.sock
/tmp/mysqld.sock
tmp/mysql.sock
/tmp/mysql.sock
/var/mysql/mysql.sock
/var/run/mysqld/mysqld.sock
]).find { |path| path.socket? }
configuration_options = {
:adapter => 'postgresql',
:username => 'adaptly',
:password => '',
:database => 'dm_core_test',
}
configuration_options[:socket] = socket_file unless socket_file.nil?
log_dir = DataMapper.root.to_s + '/log'
Dir.mkdir(log_dir) unless File.directory?(log_dir)
#DataMapper::Logger.new(log_dir + '/dm.log', :off)
adapter = DataMapper.setup(:default, "postgres://adaptly@localhost/dm_core_test")
if configuration_options[:adapter]
sqlfile = File.join(File.dirname(__FILE__), '..', 'tmp', 'performance.sql')
mysql_bin = %w[ psql ].select { |bin| `which #{bin}`.length > 0 }
mysqldump_bin = %w[ mysqldump mysqldump5 ].select { |bin| `which #{bin}`.length > 0 }
end
#ActiveRecord::Base.logger = Logger.new(log_dir / 'ar.log')
#ActiveRecord::Base.logger.level = 0
class User
include DataMapper::Resource
property :id, Serial
property :name, String
property :email, String
property :about, Text, :lazy => false
property :created_on, Date
end
class Exhibit
include DataMapper::Resource
property :id, Serial
property :name, String
property :zoo_id, Integer
property :user_id, Integer
property :notes, Text, :lazy => false
property :created_on, Date
belongs_to :user
end
DataMapper.auto_migrate!
ActiveRecord::Base.establish_connection(configuration_options)
class ARExhibit < ActiveRecord::Base #:nodoc:
set_table_name 'exhibits'
belongs_to :user, :class_name => 'ARUser', :foreign_key => 'user_id'
end
class ARUser < ActiveRecord::Base #:nodoc:
set_table_name 'users'
has_many :exhibits, :foreign_key => 'user_id'
end
ARExhibit.find_by_sql('SELECT 1')
def touch_attributes(*exhibits)
exhibits.flatten.each do |exhibit|
exhibit.id
exhibit.name
exhibit.created_on
end
end
def touch_relationships(*exhibits)
exhibits.flatten.each do |exhibit|
exhibit.id
exhibit.name
exhibit.created_on
exhibit.user
end
end
c = configuration_options
if sqlfile && File.exists?(sqlfile)
puts "Found data-file. Importing from #{sqlfile}"
#adapter.execute("LOAD DATA LOCAL INFILE '#{sqlfile}' INTO TABLE exhibits")
`#{mysql_bin} -u #{c[:username]} #{"-p#{c[:password]}" unless c[:password].blank?} #{c[:database]} < #{sqlfile}`
else
puts 'Generating data for benchmarking...'
# pre-compute the insert statements and fake data compilation,
# so the benchmarks below show the actual runtime for the execute
# method, minus the setup steps
# Using the same paragraph for all exhibits because it is very slow
# to generate unique paragraphs for all exhibits.
notes = Faker::Lorem.paragraphs.join($/)
today = Date.today
puts 'Inserting 10,000 users and exhibits...'
10_000.times do
user = User.create(
:created_on => today,
:name => Faker::Name.name,
:email => Faker::Internet.email
)
Exhibit.create(
:created_on => today,
:name => Faker::Company.name,
:user => user,
:notes => notes,
:zoo_id => rand(10).ceil
)
end
if sqlfile
answer = nil
until answer && answer[/\A(?:y(?:es)?|no?)\b/i]
print('Would you like to dump data into tmp/performance.sql (for faster setup)? [Yn]');
STDOUT.flush
answer = gets
end
if %w[ y yes ].include?(answer.downcase)
File.makedirs(File.dirname(sqlfile))
#adapter.execute("SELECT * INTO OUTFILE '#{sqlfile}' FROM exhibits;")
`#{mysqldump_bin} -u #{c[:username]} #{"-p#{c[:password]}" unless c[:password].blank?} #{c[:database]} exhibits users > #{sqlfile}`
puts "File saved\n"
end
end
end
TIMES = ENV.key?('x') ? ENV['x'].to_i : 10_000
puts 'You can specify how many times you want to run the benchmarks with rake:perf x=(number)'
puts 'Some tasks will be run 10 and 1000 times less than (number)'
puts "Benchmarks will now run #{TIMES} times"
# Inform about slow benchmark
# answer = nil
# until answer && answer[/^$|y|yes|n|no/]
# print("A slow benchmark exposing problems with SEL is newly added. It takes approx. 20s\n");
# print("you have scheduled it to run #{TIMES / 100} times.\nWould you still include the particular benchmark? [Yn]")
# STDOUT.flush
# answer = gets
# end
# run_rel_bench = answer[/^$|y|yes/] ? true : false
RBench.run(TIMES) do
column :times
column :ar, :title => 'AR 3.2.8'
column :dm, :title => "DM #{DataMapper::VERSION}"
column :diff, :compare => [:ar, :dm]
report 'Model#id', (TIMES * 100).ceil do
ar_obj = ARExhibit.find(1)
dm_obj = Exhibit.get(1)
ar { ar_obj.id }
dm { dm_obj.id }
end
report 'Model.new (instantiation)' do
ar { ARExhibit.new }
dm { Exhibit.new }
end
report 'Model.new (setting attributes)' do
attrs = { :name => 'sam', :zoo_id => 1 }
ar { ARExhibit.new(attrs) }
dm { Exhibit.new(attrs) }
end
report 'Model.get specific (not cached)' do
ActiveRecord::Base.uncached { ar { touch_attributes(ARExhibit.find(1)) } }
dm { touch_attributes(Exhibit.get(1)) }
end
report 'Model.get specific (cached)' do
ActiveRecord::Base.cache { ar { touch_attributes(ARExhibit.find(1)) } }
Exhibit.repository(:default) { dm { touch_attributes(Exhibit.get(1)) } }
end
report 'Model.first' do
ar { touch_attributes(ARExhibit.first) }
dm { touch_attributes(Exhibit.first) }
end
report 'Model.all limit(100)', (TIMES / 10).ceil do
ar { touch_attributes(ARExhibit.find(:all, :limit => 100)) }
dm { touch_attributes(Exhibit.all(:limit => 100)) }
end
report 'Model.all limit(100) with relationship', (TIMES / 10).ceil do
ar { touch_relationships(ARExhibit.all(:limit => 100, :include => [ :user ])) }
dm { touch_relationships(Exhibit.all(:limit => 100)) }
end
report 'Model.all limit(10,000)', (TIMES / 1000).ceil do
ar { touch_attributes(ARExhibit.find(:all, :limit => 10_000)) }
dm { touch_attributes(Exhibit.all(:limit => 10_000)) }
end
exhibit = {
:name => Faker::Company.name,
:zoo_id => rand(10).ceil,
:notes => Faker::Lorem.paragraphs.join($/),
:created_on => Date.today
}
report 'Model.create' do
ar { ARExhibit.create(exhibit) }
dm { Exhibit.create(exhibit) }
end
report 'Resource#attributes=' do
attrs_first = { :name => 'sam', :zoo_id => 1 }
attrs_second = { :name => 'tom', :zoo_id => 1 }
ar { exhibit = ARExhibit.new(attrs_first); exhibit.attributes = attrs_second }
dm { exhibit = Exhibit.new(attrs_first); exhibit.attributes = attrs_second }
end
report 'Resource#update' do
ar { ARExhibit.find(1).update_attributes(:name => 'bob') }
dm { Exhibit.get(1).update(:name => 'bob') }
end
report 'Resource#destroy' do
ar { ARExhibit.first.destroy }
dm { Exhibit.first.destroy }
end
report 'Model.transaction' do
ar { ARExhibit.transaction { ARExhibit.new } }
dm { Exhibit.transaction { Exhibit.new } }
end
summary 'Total'
end
connection = adapter.send(:open_connection)
command = connection.create_command('DROP TABLE exhibits')
command = connection.create_command('DROP TABLE users')
command.execute_non_query rescue nil
connection.close
| AR 3.2.8 | DM 1.3.0.beta | DIFF |
---------------------------------------------------------------------------------------------
Model#id x100000 | 0.512 | 0.384 | 1.33x |
Model.new (instantiation) x10000 | 0.222 | 0.002 | 107.09x |
Model.new (setting attributes) x10000 | 0.788 | 0.381 | 2.07x |
Model.get specific (not cached) x10000 | 3.578 | 7.079 | 0.51x |
Model.get specific (cached) x10000 | 3.570 | 0.150 | 23.84x |
Model.first x10000 | 3.276 | 5.768 | 0.57x |
Model.all limit(100) x1000 | 6.674 | 11.442 | 0.58x |
Model.all limit(100) with relationship x1000 | 15.777 | 138.098 | 0.11x |
Model.all limit(10,000) x10 | 4.707 | 10.403 | 0.45x |
Model.create x10000 | 10.711 | 10.590 | 1.01x |
Resource#attributes= x10000 | 1.028 | 0.611 | 1.68x |
Resource#update x10000 | 6.718 | 9.430 | 0.71x |
Resource#destroy x10000 | 8.567 | 11.715 | 0.73x |
Model.transaction x10000 | 1.282 | 3.653 | 0.35x |
=============================================================================================
Total | 67.411 | 209.705 | 10.07x |
  • git clone https://github.com/datamapper/dm-core.git
  • cd dm-core
  • copy performance.rb to script/
  • copy dm-core.gemspec to .
  • modify script/performance.rb, add your db creds
  • ADAPTER=postgres PLUGINS=dm-transactions bundle install
  • createdb dm_core_test
  • bundle open rbench
    • open lib/rbench/report.rb
    • line 53, change #ntimes to #count
    • save and quit
  • ./script/performance.rb
    • At the prompt (Would you like to dump data into tmp/performance.sql (for faster setup)? [Yn]) type n!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment