1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Experimental: Improve performance of ActionView by preventing method cache flushing due to runtime Kernel#extend:

* The helper module adds a new _helper_serial property onto AbstractController subclasses
  * When #helper is used to add helpers to a class, the serial number is updated
  * An ActionView subclass is created and cached based on this serial number.
    * That subclass includes the helper module from the controller
    * Subsequent requests using the same controller with the same serial will result in
      reusing that subclass, rather than being forced to take an action (like include
      or extend) that will result in a global method cache flush on MRI and a flush 
      of the entire AV class' cache on JRuby.
  * For now, this optimization is not applied to the RJS helpers, which results in
    a global method cache flush in MRI and a flush of the JavaScriptGenerator class in
    JRuby only when using RJS.
    * Since the effects are limited to using RJS, and would only affect JavaScriptGenerator
      in JRuby (as opposed to the entire view object), it seems worthwhile to apply this
      now.
  * This resulted in a significant performance improvement. I will have benchmarks
    in the next day or two that show the performance impact of the last several
    commits.
  * There is a small chance this could break existing code (although I'm not sure how).
    If that happens, please report it immediately.
This commit is contained in:
Yehuda Katz 2009-08-09 03:28:46 -03:00
parent 10eaba8f41
commit e58b2769cf
2 changed files with 32 additions and 14 deletions

View file

@ -4,8 +4,16 @@ module AbstractController
include RenderingController
def self.next_serial
@helper_serial ||= 0
@helper_serial += 1
end
included do
extlib_inheritable_accessor(:_helpers) { Module.new }
extlib_inheritable_accessor(:_helper_serial) do
AbstractController::Helpers.next_serial
end
end
module ClassMethods
@ -58,6 +66,8 @@ module AbstractController
# of the helper module. Any methods defined in the block
# will be helpers.
def helper(*args, &block)
self._helper_serial = AbstractController::Helpers.next_serial + 1
args.flatten.each do |arg|
case arg
when Module

View file

@ -164,6 +164,9 @@ module ActionView #:nodoc:
#
# See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
class Base
module Subclasses
end
include Helpers, Rendering, Partials, ::ERB::Util
extend ActiveSupport::Memoizable
@ -212,30 +215,35 @@ module ActionView #:nodoc:
ActionView::PathSet.new(Array(value))
end
extlib_inheritable_accessor :helpers
attr_reader :helpers
class ProxyModule < Module
def initialize(receiver)
@receiver = receiver
end
def include(*args)
super(*args)
@receiver.extend(*args)
end
end
def self.for_controller(controller)
new(controller.class.view_paths, {}, controller).tap do |view|
view.helpers.include(controller._helpers) if controller.respond_to?(:_helpers)
@views ||= {}
# TODO: Decouple this so helpers are a separate concern in AV just like
# they are in AC.
if controller.class.respond_to?(:_helper_serial)
klass = @views[controller.class._helper_serial] ||= Class.new(self) do
Subclasses.const_set(controller.class.name.gsub(/::/, '__'), self)
if controller.respond_to?(:_helpers)
include controller._helpers
self.helpers = controller._helpers
end
end
else
klass = self
end
klass.new(controller.class.view_paths, {}, controller)
end
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc:
@formats = formats || [:html]
@assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) }
@controller = controller
@helpers = ProxyModule.new(self)
@helpers = self.class.helpers || Module.new
@_content_for = Hash.new {|h,k| h[k] = "" }
self.view_paths = view_paths
end