Skip to content

Instantly share code, notes, and snippets.

@miloops
Created October 8, 2010 18:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save miloops/617327 to your computer and use it in GitHub Desktop.
Save miloops/617327 to your computer and use it in GitHub Desktop.
-- create_table(:accounts)
-> 0.0264s
-- create_table(:posts)
-> 0.0008s
-- create_table(:comments)
-> 0.0010s
==================================================
ActiveRecord::IdentityMap.enabled: false
Objects:
Warmup: 1110266 allocations | 40078000 bytes
Actual: 1108696 allocations | 40061024 bytes
==================================================
ActiveRecord::IdentityMap.enabled: true
Objects:
Warmup: 272520 allocations | 8646368 bytes
Actual: 224883 allocations | 6988789 bytes
==================================================
ActiveRecord::IdentityMap.enabled: true
Objects:
Warmup: 226782 allocations | 7061606 bytes
Actual: 39599 allocations | 498807 bytes
==================================================
ActiveRecord::IdentityMap.enabled: false
Objects:
Warmup: 196359 allocations | 6179002 bytes
Actual: 199547 allocations | 6086341 bytes
-- create_table(:accounts)
-> 0.0262s
-- create_table(:posts)
-> 0.0008s
-- create_table(:comments)
-> 0.0010s
==================================================
ActiveRecord::IdentityMap.enabled: false
Objects:
Warmup: 555077 allocations | 20047967 bytes
Actual: 554746 allocations | 20045261 bytes
==================================================
ActiveRecord::IdentityMap.enabled: true
Objects:
Warmup: 118825 allocations | 3707707 bytes
Actual: 17924 allocations | 238183 bytes
==================================================
ActiveRecord::IdentityMap.enabled: true
Objects:
Warmup: 17919 allocations | 238100 bytes
Actual: 17924 allocations | 238181 bytes
==================================================
ActiveRecord::IdentityMap.enabled: false
Objects:
Warmup: 110312 allocations | 3473307 bytes
Actual: 97896 allocations | 3039749 bytes
$:.unshift("/Users/miloops/Workspace/github/miloops/rails/activerecord/lib")
$:.unshift("/Users/miloops/Workspace/github/swistak/weakling/lib")
$:.unshift("/Users/miloops/Workspace/github/railsarel/lib")
require 'active_record'
ActiveRecord::Base.establish_connection(
:adapter => "sqlite3",
:database => ":memory:"
)
ActiveRecord::Schema.define do
create_table :accounts do |t|
t.string :email
t.timestamps
end
create_table :posts do |t|
t.string :title
t.text :body
t.integer :account_id
t.timestamps
end
create_table :comments do |t|
t.string :body
t.integer :post_id
t.timestamps
end
end
class Post < ActiveRecord::Base
belongs_to :account
has_many :comments
end
class Account < ActiveRecord::Base
has_many :posts
end
class Comment < ActiveRecord::Base
belongs_to :post
end
def instances(&block)
GC.enable_stats
GC.clear_stats
block.call
warmup_objs = GC.num_allocations
warmup_bytes = GC.allocated_size
GC.clear_stats
block.call
puts "=" * 50
puts "ActiveRecord::IdentityMap.enabled: #{ActiveRecord::IdentityMap.enabled}"
puts "Objects:"
puts "Warmup: #{warmup_objs} allocations | #{warmup_bytes} bytes"
puts "Actual: #{GC.num_allocations} allocations | #{GC.allocated_size} bytes"
end
Account.destroy_all
a = Account.create(:email => 'miloops@example.com')
50.times do |i|
c = a.posts.create!(:title => "test", :body => "Loldel")
10.times do
c.comments.create!(:body => ("lol! " * 10))
end
end
Account.first.posts.each do |post|
post.account.email
post.comments.each do |comment|
comment.post.account.email
end
end
instances do
ActiveRecord::IdentityMap.enabled = false
Account.first.posts.each do |post|
post.account.email
post.comments.each do |comment|
comment.post.account.email
end
end
end
instances do
ActiveRecord::IdentityMap.enabled = true
Account.first.posts.each do |post|
post.account.email
post.comments.each do |comment|
comment.post.account.email
end
end
end
module ActiveRecord::IdentityMap
class << self
def current
repositories[current_repository_name] ||= WeakHash.new
end
end
end
instances do
ActiveRecord::IdentityMap.enabled = true
Account.first.posts.each do |post|
post.account.email
post.comments.each do |comment|
comment.post.account.email
end
end
end
Post.belongs_to :account, :inverse_of => :posts
Post.has_many :comments, :inverse_of => :post
Comment.belongs_to :post, :inverse_of => :comments
Account.has_many :posts, :inverse_of => :account
instances do
ActiveRecord::IdentityMap.enabled = false
Account.first.posts.each do |post|
post.account.email
post.comments.each do |comment|
comment.post.account.email
end
end
end
class WeakHash
def initialize( cache = Hash.new )
@cache = cache
@key_map = {}
@reclaim_key = lambda do |key_id|
delete(@key_map[key_id]) unless @cache.empty?
end
end
def []( key )
value_id = @cache[key]
value_id ? ObjectSpace._id2ref(value_id) : nil
rescue RangeError
nil
end
def []=( key, value )
case key
when Fixnum, Symbol, true, false
key2 = key
else
key2 = key.dup
end
@cache[key2] = value.object_id
@key_map[key.object_id] = key2
ObjectSpace.define_finalizer(key, @reclaim_key)
end
def clear
@key_map.clear
@cache.clear
end
def delete(key)
@cache.delete(key)
end
end
module ActiveRecord
# = Active Record Identity Map
#
# Ensures that each object gets loaded only once by keeping every loaded
# object in a map. Looks up objects using the map when referring to them.
#
# More information on Identity Map pattern:
# http://www.martinfowler.com/eaaCatalog/identityMap.html
#
# == Configuration
#
# In order to disable IdentityMap, set <tt>config.active_record.identity_map = false</tt>
# in your <tt>config/application.rb</tt> file.
#
# IdentityMap is enabled by default.
#
module IdentityMap
extend ActiveSupport::Concern
class << self
attr_accessor :repositories
attr_accessor :current_repository_name
attr_accessor :enabled
def current
repositories[current_repository_name] ||= Weakling::WeakHash.new
end
def with_repository(name = :default)
old_repository = self.current_repository_name
self.current_repository_name = name
yield if block_given?
ensure
self.current_repository_name = old_repository
end
def without
old, self.enabled = self.enabled, false
yield if block_given?
ensure
self.enabled = old
end
def get(class_name, primary_key)
current[[class_name, primary_key.to_s]]
end
def add(record)
current[[record.class.name, record.id.to_s]] = record
end
def remove(record)
current.delete([record.class.name, record.id.to_s])
end
def clear
current.clear
end
alias enabled? enabled
alias identity_map= enabled=
end
self.repositories ||= Hash.new
self.current_repository_name ||= :default
self.enabled = true
module InstanceMethods
# Reinitialize an Identity Map model object from +coder+.
# +coder+ must contain the attributes necessary for initializing an empty
# model object.
def reinit_with(coder)
@attributes_cache = {}
dirty = @changed_attributes.keys
@attributes.update(coder['attributes'].except(*dirty))
@changed_attributes.update(coder['attributes'].slice(*dirty))
@changed_attributes.delete_if{|k,v| v.eql? @attributes[k]}
_run_find_callbacks
self
end
end
module ClassMethods
def identity_map
ActiveRecord::IdentityMap
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment