Attributes Unwrapped: Lessons under the surface of active record

Post on 15-May-2015

1,001 views 1 download

Tags:

description

Ведущий разработчик Ruby on Rails (Rails Core member) Джон Лейтон не так давно работал над совершенствованием реализации работы с атрибутами в Active Record. Он расскажет о своем опыте работы над важной для производительности областью Rails, даст советы и расскажет о техниках, которые могут быть применены к собственным приложениям слушателей. Говоря о будущем, Джон также расскажет о своих идеях по изменению API работы с атрибутами в лучшую сторону; эти изменения могут появиться в Rails 4.0.

transcript

Attributes Unwrapped

@jonleighton

I lied to you

Measure

Measure

Refactor

Measure

Refactor

Optimise

LET’S DO SCIENCE

gem 'rails', '3.2.0'

require 'active_record'

ActiveRecord::Base

.establish_connection(

:adapter => 'sqlite3',

:database => ':memory:')

ActiveRecord::Schema.define do

create_table :posts do |t|

t.string :title

end

end

class Post < ActiveRecord::Base

end

p = Post.create(:title => "lol")

require 'benchmark'

n = 1_000_000

Benchmark.report(20) do |r|

r.report('attribute') do

n.times { p.title }

end

r.report('read_attribute') do

n.times { p[:title] }

end

end

attribute

read_attribute

1.09 s

2.87 s

gem install

benchmark_suite

require 'benchmark/ips'

Benchmark.ips do |r|

r.report('attribute') do

p.title

end

r.report('read_attribute') do

p[:title]

end

end

attribute

read_attribute

828,648

299,856

Ruby 1.9

Ruby 1.8

Ruby 1.8

oops!

define_method

method compilation

create_table :roflcopters do |t|

t.string " ROFL:ROFL:ROFL:ROFL"

t.string " _^____ "

t.string " L __/ []\ "

t.string "LOL===_ \ "

t.string " L \_________] "

t.string " I I "

t.string " --------/ "

end

if compilable?

class_eval <<-STR

def #{attr_name}

...

end

STR

else

define_method attr_name do

...

end

end

attr_name

=~

/\A[a-zA-Z_]\w*[!?=]?\z/

:title

=~

/\A[a-zA-Z_]\w*[!?=]?\z/

:title =~ /.../

# => true

Ruby 1.9

Ruby 1.8

:title =~ /.../

# => false

def __temp__

...

end

alias "@#>" :__temp__

undef_method :__temp__

def __temp__

...

end

alias "@#>" :__temp__

undef_method :__temp__

DON'T USE THIS

API Changes

def title

self[:title].upcase

end

def title

super.upcase

end

module A

def foo

"bar"

end

end

class B

include A

def foo

super.upcase

end

end

Don’t fight Ruby

<3 <3 <3

#read_attribute

#[]

def read_attribute(name)

name = "_#{name}"

if respond_to?(name)

send(name)

else

# other stuff

end

end

Module.new

def read_attribute(name)

mod = self.class.methods_module

if mod.respond_to?(name)

mod.send(name, @attributes)

else

# other stuff

end

end

Module.new.respond_to?(:name)

# => true

Module.new { extend self }

Module.new { extend self }

mod.method_defined?(:name)

Module.new { extend self }

INSANE HACKmod.method_defined?(:name)

Still too slow☹

gem install

perftools.rb

if attr_name == 'id'

attr_name =

self.class.primary_key

end

No code is fasterthan no code

def title

cast @attributes['title']

end

def title

cast @attributes[:title]

end

class A

def initialize

@attributes = { :foo => 1 }

end

def foo

@attributes[:foo]

end

end

class B

def initialize

@attributes = { 'foo' => 1 }

end

def foo

@attributes['foo']

end

end

Benchmark.ips do |r|

r.report('symbol') { a.foo }

r.report('string') { b.foo }

end

code = "@attributes['foo']"

iseq =

RubyVM::InstructionSequence

.compile(code)

puts iseq.disassemble

trace 1

getinstancevariable :@attributes

putstring "foo"

opt_aref <ic:2>

leave

trace 1

getinstancevariable :@attributes

putobject :foo

opt_aref <ic:2>

leave

DEFINE_INSN

putstring

(VALUE str)

()

(VALUE val)

{

val = rb_str_resurrect(str);

}

DEFINE_INSN

putobject

(VALUE val)

()

(VALUE val)

{

/* */

}

code = "@attributes['foo']"

compiled =

Rubinius::Compiler

.compile_string(code)

puts compiled.decode

push_ivar 0

push_literal "foo"

string_dup

send_stack :[], 1

pop

push_true

ret

push_ivar 0

push_literal :foo

send_stack :[], 1

pop

push_true

ret

jruby --bytecode

-e "@attributes['foo']"

RubyString

RubyString

RubySymbol

Performance problemsare a code smell

Be a scientist

But...

Avoid roflscaling!

Thanks!IT'S OVER!

Thanks!IT'S OVER!

(♥ @tenderlove ♥)