Baq*_*dri 4 ruby list-comprehension
我最近在Tomcat的JRuby上遇到了运行Sinatra的permgen内存泄漏.问题与Sinatra用来支持各种模板选项的Tilt库有关.旧代码(此处未包含)正在生成内存泄漏.新代码(下面)没有,事实上我发现permgen GC现在正在运行.
Ruby应该是自我描述的,但我无法通过阅读它来弄清楚这些代码.有嵌套的类逃逸.为什么?为什么定义一个方法然后解除绑定?
为什么代码编译了一堆模板并保留它们以便重复使用这么复杂的外观?
另外:如果有任何GitHub员工正在查看此问题,您能否向GitHub添加一些功能,允许用户在代码片段中插入问题?
(此代码取自https://github.com/rtomayko/tilt/blob/master/lib/tilt.rb)
def compile_template_method(locals)
source, offset = precompiled(locals)
offset += 5
method_name = "__tilt_#{Thread.current.object_id.abs}"
Object.class_eval <<-RUBY, eval_file, line - offset
#{extract_magic_comment source}
TOPOBJECT.class_eval do
def #{method_name}(locals)
Thread.current[:tilt_vars] = [self, locals]
class << self
this, locals = Thread.current[:tilt_vars]
this.instance_eval do
#{source}
end
end
end
end
RUBY
unbind_compiled_method(method_name)
end
Run Code Online (Sandbox Code Playgroud)
有嵌套的类逃逸.为什么?
这种方法看起来像是来自战争伤痕累累,修复和修补的生产代码,而不是优雅的自描述代码(所以我们可以原谅他们一点).
那为什么两个人逃避?在第二个嵌套的"真实"模板方法代码可以被评估之前,要评估的代码必须以正确的源编码作为前缀,该编码可能已被定义为模板文件中的"魔术注释".
一旦正确设置了字符串编码,就可以尝试真正的class_eval.另一种说法可能是"这是编写源代码的源代码的源代码"!
据推测,这是为了修复Ruby 1.9中可能出现的兼容性问题,其中正在编译的模板可能包含与Tilt库源代码本身(US-ASCII编码)的编码不同的字符编码(UTF-8),会导致模板字符串的错误评估(因为字符串编码已经在调用模板文件的主机代码中设置).
为什么定义一个方法然后解除绑定?
澄清一下:在Ruby中,unbound与undefined不同.
未绑定方法作为可以调用的UnboundMethod类型的自由方法对象存在,尽管它们不再与特定对象关联.未绑定的方法不再具有接收器.
为了创建一个未绑定的方法,它首先被绑定(定义为)一个对象.这就是为什么编译模板方法可以从顶级对象中快速删除的原因,因为它只是生成未绑定方法所必需的临时安排.
此技术用于使用可以使用特定于给定类的不同实例的编译模板,而无需以任何可见或永久的方式更改根对象或第三方开发人员的客户端类.
通过将编译的模板方法与特定的客户端代码对象解除关联,在以后调用使用该类型的对象的模板期间,编译的模板方法可以稍后反弹到该对象的类的新实例.
例如,给定以下ERB模板:
<p>Hello <%= @name %></p>
Run Code Online (Sandbox Code Playgroud)
......以及以下调用代码:
scott = Person.new
scott.name = "Scott"
output = template.render(scott)
=> "<p>Hello Scott</p>"
Run Code Online (Sandbox Code Playgroud)
在第一次渲染期间,模板被评估并针对TOPOBJECT对象的实例进行编译.编译的模板方法将命名为" __tilt_2151955260".然后,此方法将被解除绑定,以便再次对TOPOBJECT类型的所有实例(根据Ruby版本只是Object或BasicObject)使用,因此可以用于任何客户端对象类型.
下次渲染模板时,编译的模板方法与TOPOBJECT的'baq'实例绑定:
baq = Person.new
baq.name = "Baq"
output = template.render(baq)
Run Code Online (Sandbox Code Playgroud)
在引擎盖下,当template.render(baq)调用时,未绑定的编译模板方法被绑定到Person的'baq'实例:
__tilt_2151955260.bind(baq).call
Run Code Online (Sandbox Code Playgroud)
不必每次调用class_eval都会带来可观的性能提升.
为什么代码编译了一堆模板并保留它们以便重复使用这么复杂的外观?
我的评估是,尽管代码实现确实看起来确实看起来不那么复杂,但这些间接层通常在框架代码中是必要的,这些代码旨在使公共API非常简单和甜蜜地为成千上万的其他开发人员消费,即使它是以少数必须维护它的开发人员为代价的.
由于在许多不同的语言环境中消耗的API引起的实际问题以及来自世界各地的许多编码,代码复杂性(双eval嵌套)也增加了.
脚注: 问题中提到的Template类已被重构为单独的文件github.com/rtomayko/tilt/blob/master/lib/tilt/template.rb
| 归档时间: |
|
| 查看次数: |
290 次 |
| 最近记录: |