Hello Richard,
Thanks for the response. Unfortunately I'm not sure how it's possible to do
this without the Hash look-up. For example one might imagine adding
something like this to ActiveSupport::Callbacks (which would prevent more
than one string from being permanently allocated):
@@_current_kind = nil
def get_frozen_callback(kind)
if @@_current_kind == kind
@@_current_callback
else
@@_current_kind = kind
@@_current_callback = "_#{kind}_callbacks".freeze
end
end
The problem is that this is what a typical run actually looks like:
[1] pry(main)> Symbolie.all.to_a
in #run_callbacks -- self:
#<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter:0x007faa970f41e8>,
kind: checkout
Symbolie Load (2.6ms) SELECT "symbolies".* FROM "symbolies"
in #run_callbacks -- self: #<Symbolie:0x007faa95775cd0>, kind: find
in #run_callbacks -- self: #<Symbolie:0x007faa95775cd0>, kind: initialize
in #run_callbacks -- self: #<Symbolie:0x007faa95775960>, kind: find
in #run_callbacks -- self: #<Symbolie:0x007faa95775960>, kind: initialize
in #run_callbacks -- self: #<Symbolie:0x007faa95775690>, kind: find
in #run_callbacks -- self: #<Symbolie:0x007faa95775690>, kind: initialize
in #run_callbacks -- self: #<Symbolie:0x007faa957753c0>, kind: find
in #run_callbacks -- self: #<Symbolie:0x007faa957753c0>, kind: initialize
in #run_callbacks -- self: #<Symbolie:0x007faa957750f0>, kind: find
in #run_callbacks -- self: #<Symbolie:0x007faa957750f0>, kind: initialize
in #run_callbacks -- self: #<Symbolie:0x007faa95774e20>, kind: find
in #run_callbacks -- self: #<Symbolie:0x007faa95774e20>, kind: initialize
in #run_callbacks -- self: #<Symbolie:0x007faa95774b50>, kind: find
# etc.....
The objects are initialized sequentially so without a hash the strings will
still have to be allocated and deallocated on a per-object basis - since as
far as I understand (I may be mistaken here?) it is not useful to freeze a
dynamically-generated string in Ruby without storing the reference e.g.
[2] pry(main)> "hello world".freeze.object_id
=> 70185328616580
[3] pry(main)> "hello world".freeze.object_id
=> 70185328616580
[4] pry(main)> var = "world"
=> "world"
[5] pry(main)> "hello #{var}".freeze.object_id
=> 70185328477560
[6] pry(main)> "hello #{var}".freeze.object_id
=> 70185328139100
I think for all normal applications the memory use from the hash is
entirely marginal (about the same as a previous_changes on a single AR
object) and since all objects will re-use these calls it doesn't make a lot
of sense to clear.Actually I think even if the plan was to clear, it may be
quite difficult to do so - since callbacks are an ActiveSupport concern
they should be generally usable without AR (in theory?) -- meaning that any
class which extends the concern and calls the define_callbacks and
run_callbacks methods should be capable of using them correctly. Perhaps an
alternative would be to make the string freezing behaviour specific to the
ActiveRecord extension of ActiveSupport callbacks, and guaranteeing that
the callbacks hash is cleared after all callbacks are run (it would need to
have transactional not object based context) and may be little messy and
I'm really not sure clearing 10-30 strings from memory (potentially only
until the next callback cycle re-allocates them) would justify that.
I will make a pull request on github as per your suggestion.
- Alex
On Sunday, August 23, 2015 at 11:47:35 PM UTC+8, richard schneeman wrote:
>
> I appreciate people pointing out slow spots. I'm not the most quallified
> to speak about AR but I'll try to help with the proposed patch. You can get
> a bit more speed by taking out the hash lookup:
>
> ```ruby
> require 'benchmark/ips'
>
> var = "world".freeze
> hash = { var => "hello #{var}".freeze}
>
> Benchmark.ips do |x|
> x.report("hash ") { hash[var] }
> x.report("compare ") { var == "world".freeze ? "hello world".freeze :
> "hello #{var}" }
> x.report("static ") { "hello world".freeze }
> x.report("none ") { "hello #{var}" }
> end
> ```
>
> Gives you:
>
>
> ```
> Calculating -------------------------------------
> hash 90.986k i/100ms
> compare 98.282k i/100ms
> static 103.704k i/100ms
> none 90.593k i/100ms
> -------------------------------------------------
> hash 4.567M (±19.0%) i/s - 22.201M
> compare 5.441M (±13.7%) i/s - 26.831M
> static 7.160M (±22.9%) i/s - 34.222M
> none 3.215M (±14.8%) i/s - 15.763M
> ```
>
> The hash also introduces the possibility of a memory leak, if there's only
> two values that ever hit that method, we're fine, but at that rate we could
> optimize those two string paths since it would be faster. If somehow
> arbitrary data from a user supplied `params` gets in there, then the hash
> could quickly take up memory until your app crashes. It might not happen
> today, but maybe in the future new functionality gets added that exposes
> this method to a public API and then we're toast.
>
> Thanks for the benchmarking and for the patch. If you make a PR to
> github.com/rails/rails we can talk about it more, ping me @schneems. We
> can also have someone with more AR experience take a look.
>
>
>
> ---
> Richard Schneeman
> http://www.schneems.com
>
>
>
> On Sun, Aug 23, 2015 at 9:39 AM, [email protected] <javascript:> <
> [email protected] <javascript:>> wrote:
>
> Hello,
>
> I've been trying to track-down a pretty significant memory bloat issue
> with ActiveRecord objects. Running some generic experiments e.g. an empty
> model with 6 enum_fields, created_at, updated_at - and fetching 72000 of
> these with ActiveRecord from a Postgres Database allocates approximately
> 120mb of memory or (1747 bytes per object) - measured using Instruments.On
> the other hand fetching the same 72000 objects into a list of hashes
> directly with Ruby-PG is only about 10mb of memory.
>
> One particular thing I found which was surprising is that enum is not any
> more efficient than string. This appears to be because the Postgresql
> Adapter for ActiveRecord uses TypeMapAllString on the RubyPG level and than
> performs all of the TypeMapping in Ruby.
> <https://github.com/rails/rails/blob/master/activerecord%2Flib%2Factive_record%2Fconnection_adapters%2Fpostgresql%2Fdatabase_statements.rb#L168>
>
> <https://github.com/rails/rails/blob/master/activerecord%2Flib%2Factive_record%2Fconnection_adapters%2Fpostgresql%2Fdatabase_statements.rb#L168>This
>
> seems to be grossly inefficient since Ruby-PG has support for both basic
> and custom type-mapping on the C-level.Here is some notable output from
> the memory_profiler gem - this is for *only* 729 objects.
>
>
> 1617 "0"
> 1458 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.3/lib/
> active_record/connection_adapters/postgresql/database_statements.rb:169
> 159 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.3/lib/
> active_record/connection_adapters/postgresql/oid/type_map_initializer.rb:
> 161459 "2"
> 1459 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.3/lib/
> active_record/connection_adapters/postgresql/database_statements.rb:
> 1691459 "1"
> 1459 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.3/lib/
> active_record/connection_adapters/postgresql/database_statements.rb:169729
> "_initialize_callbacks"
> 729 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/
> active_support/callbacks.rb:81729 "_find_callbacks"
> 729 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/
> active_support/callbacks.rb:81729 "initialize"
> 729 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/
> active_support/callbacks.rb:81729 "find"
> 729 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/
> active_support/callbacks.rb:81505 "\n"
> 262 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.3/lib/
> active_record/relation.rb:17
> 87 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.3/lib/
> active_record/relation/delegation.rb:15
> 42 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.3/lib/
> active_record/relation/delegation.rb:16
> 34 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.3/lib/
> active_record/connection_adapters/abstract/database_statements.rb:228
> 31 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/
> active_support/dependencies.rb:274
> 20 /Users/user_name/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.3/lib/
> active_record/relation.rb:654
>
> ...
--
You received this message because you are subscribed to the Google Groups "Ruby
on Rails: Core" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.