zenhacks & friends · builder.include ‘’ ... thingy() while condition do thingy() end end...

58
Polishing Ruby ZenHacks & Friends by: Ryan Davis, Seattle.rb

Upload: others

Post on 18-Jul-2020

10 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Polishing RubyZenHacks & Friends

by: Ryan Davis, Seattle.rb

Page 2: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Setting ExpectationsThis Presentation is...

• ...58 slides in 45 minutes - very dense

• ...about the Shinies

• ...“Hard Core”, not for everyone

Page 3: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Driving Philosophy• Simplicity & Clarity

• Otherwise, you don’t fully understand.

• Make it Right

• Make it Work

• Make it Fast

• Speed comes last, and only objectively.

• Balance size and speed.

• Accelerate Maturity by splitting from parents.

• Have fun! Otherwise, what’s the point?

Page 4: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Standard Example

• I’ll be using factorial throughout most of the code in order to make things easier to track.

• Most of the time, it’ll be implemented as:

def factorial(n) f = 1 n.downto(2) { |x| f *= x } return fend

Page 5: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Overview

ZenHacks

ruby2ruby

sexp as method

auto-refactoring

obfuscator

profiler

zenoptimize

Advanced

Dependencies

ABC Metrics

Colored Sexp

Graphs

Overview

Current Status

Step by Step

Factorial

Future Plans

Intermediate

Architecture

& extensibility

+ A Sekrit

Overview

factorial

node printer

code2test

test2code

unit_diff

RubyInline ruby2c

Beginner

Overview

RI::C - hello world

factorial_c

ZenTestParseTree/

SexpProcessor

Page 6: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Newb

Overview

factorial

node printer

code2test

test2code

unit_diff

Beginner

Overview

RI::C - hello world

factorial_c

RubyInlineZenTestParseTree/

SexpProcessor

Page 7: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

• Generates missing tests.

• Generates missing methods under test (MUT).

• Helps illuminate failing tests.

ZenTest

Page 8: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ZenTestGenerating Tests

class TestFactorial < Test::Unit::TestCase def test_factorial raise NotImplementedError, 'Need to write test_factorial' endend

class Factorial def factorial(n) f = 1 n.downto(2) { |x| f *= x } return f end end

Page 9: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ZenTestGenerating Tests

Page 10: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ZenTestGenerating MUTs

class Factorial def factorial raise NotImplementedError, ‘Need to write factorial’ endend

class TestFactorial < Test::Unit::TestCase def test_factorial @f = Factorial.new assert_equal(1, @f.factorial(1)) assert_equal(2, @f.factorial(2)) assert_equal(6, @f.factorial(3)) endend

Page 11: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ZenTestGenerating MUTs

Page 12: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

& ./test.rb... 1) Failure:test_conditional4(TestTypeChecker) [./r2ctestcase.rb:1068:in `test_conditional4' ./r2ctestcase.rb:1055:in `test_conditional4']:<t(:if, t(:call, t(:lit, 42, Type.long), :==, t(:arglist, t(:lit, 0, Type.long)), Type.bool), t(:return, t(:lit, 2, Type.long), Type.void), t(:if, t(:call, t(:lit, 42, Type.long), :>, t(:arglist, t(:lit, 0, Type.long)), Type.bool), t(:return, t(:lit, 3, Type.long), Type.void), t(:return, t(:lit, 4, Type.long), Type.void), Type.void), Type.void)> expected but was<t(:if, t(:call,

ZenTestIlluminating Issues

Which would you rather read/debug???

This:

& ./test.rb | unit_diff... 1) Failure:test_conditional4(TestTypeChecker)12c10< :>,---> :<,

Or this:

Page 13: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ZenTestIlluminating Issues

Page 14: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

• Allows you to write C functions within your ruby classes.

• No compiling/linking/packaging phase required.

• Only recompiles when needed.

• Incredibly accelerated development time.

• Write & Run. There is no step 3.

• You aren’t limited to C.

RubyInline

Page 15: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

RubyInlineObligatory Hello World

class Hello inline do |builder| builder.include ‘<stdio.h>’ builder.c ‘ void hello() { puts(“hello world”); } ‘ endend

Hello.new.hello

% ./hello.rbhello world

Page 16: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

RubyInline

Page 17: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

RubyInlineFactorial

class FastMath inline do |builder| builder.include ‘<math.h>’ builder.c ‘ long factorial_c(int max) { int i=max, result=1; while (i >= 2) { result *= i--; } return result; }’ endend

math = FastMath.new10000.times do math.factorial(20); end

Page 18: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

RubyInlineFactorial

Page 19: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ParseTree & SexpProcessor

“ParseTree is a little brown stinky ferret that digs down a hole and violently rips the AST away from the warm bosom of ruby”

Page 20: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ParseTree & SexpProcessor

• ParseTree:

• Extracts ruby’s AST internals and makes them digestible by mortals.

• SexpProcessor:

• The framework for digesting them.

Page 21: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Basic Vocabulary

• Sexp = s-expression = An S-expression (S stands for symbolic) is a convention for representing data structures in a text form. (wikipedia)

• AST = Abstract Syntax Tree = What the parser makes when it parses and what the interpreter runs.

Page 22: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ParseTree

• ParseTree is the library that converts ruby into sexps.

[:defn, :factorial, [:scope, [:block, [:args, :n], [:lasgn, :f, [:lit, 1]], [:iter, [:call, [:lvar, :n], :downto, [:array, [:lit, 2]]], [:dasgn_curr, :x], [:lasgn, :f, [:call, [:lvar, :f], :*, [:array, [:dvar, :x]]]]], [:lvar, :f]]]]

mmmmm, factorial…

Page 23: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ParseTreeparse_tree_show

Page 24: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

SexpProcessorClass Catalog

class QuickPrinter < SexpProcessor def initialize super self.strict = false self.auto_shift_type = true end def process_class(exp) puts “class #{exp.shift}” exp.shift # superclass process exp.shift # body return s() end def process_defn(exp) puts “ def #{exp.shift}” exp.shift # body return s() endend

class QuickPrinter def initialize def process_class def process_defn

=>

Page 25: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

SexpProcessor

Page 26: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Dependencies

ABC Metrics

Colored Sexp

Graphs

Overview

Current Status

Step by Step

Factorial

Future Plans

Intermediate

Architecture

& extensibility

+ A Sekrit

RubyInline ruby2cParseTree/

SexpProcessor

Intermediate

Eric Hodel’sCar!

It’s famous!Have you had Matz in your

car?

Page 27: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

RubyInlineGeneral Architecture

• RubyInline has a totally open architecture making it easy to extend with other languages.

• Uses ducktyping, an RI extension only needs to implement:

• load_cache

• build

• load

builder = builder_class.new self

yield builder

unless options[:testing] then unless builder.load_cache then builder.build builder.load endend

Page 28: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

• You thought jruby was wrong? HA!

• Ugly, yeah... but there is a LOT out there.

• Originally by Yoshida Masato for ruby 1.4!

• 20 minute conversion into RubyInline.

• Sooo easy. Goodbye setup.rb!

• Seems to run complex PERL just fine.

RubyInlineInline::PERL

I’m really sorry for this.

Honest.

Page 29: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

RubyInlineInline::PERL

Page 30: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ParseTree & SexpProcessor

• Tools you easily make with SexpProcessor:

• Dependency Reporting

• Complexity Analysis

• Parser Visualization

• All of the Above

(and many more)

Page 31: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

SexpProcessorDependency Analysis

def process_defn(exp) name = exp.shift @current_method = name return s(:defn, name, process(exp.shift), process(exp.shift)) end

def process_const(exp) name = exp.shift const = (defined?($c) ? @current_class.name : “#{@current_class}.#{@current_method}”) is_class = ! (Object.const_get(name) rescue nil).nil? @dependencies[name] << const if is_class return s(:const, name) end

Page 32: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

SexpProcessorDependency Analysis

Page 33: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

SexpProcessorComplexity Metrics

• ABC Metrics• # of Assignments• # of Branches• # of Calls

% parse_tree_abc printer.rb|ABC| = Math.sqrt(assignments^2 + branches^2 + calls^2)

1) QuickPrinter.process_class = 0 + 1 + 7 = 7.07 2) QuickPrinter.process_defn = 0 + 0 + 4 = 4.00 3) QuickPrinter.initialize = 2 + 0 + 0 = 2.00 4) Total = 2 + 1 + 11 = 13.07

Page 34: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

SexpProcessorComplexity Metrics

Page 35: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

:defn

:factorial :scope

:block

:args :lasgn :iter :return

:n :f :lit

1

:call :dasgn_curr :lasgn

:lvar :downto :array

:n :lit

2

:x :f :call

:lvar :* :array

:f :dvar

:x

:lvar

:f

SexpProcessorVisualization

Page 36: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

SexpProcessorVisualization

Page 37: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Ruby2cThe Problem

Takahashi Method:

C Sucks

Page 38: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

• Automatic translation of static ruby subset to C.

• Only translate if it has native support in C.

• Open architecture will allow it to translate to both Ruby internals C and generic ANSI C.

• Uses type inference to help with translation to generic C types.

Ruby2cThe Basics

Page 39: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Ruby2cBasic Design

SexpProcessor

ParseTree Rewriter TypeChecker Ruby2CCompositeSexp!

Processor

Sexpprocessesprocessors

ParseTree

Rewriter

TypeChecker

R2CRewriter

RubyToC

!P

ip

el

in

e!

Page 40: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Ruby2cStep-by-Step

• Ruby parses code

• ParseTree extracts AST

• Rewriter doesn’t touch it

• (SexpProcessor.process converts it to a Sexp)

• TypeChecker unifies it

• Ruby2C translates to C

Ruby

false and true

[:and, [:false], [:true]]

case NODE_AND: add_to_parse_tree(current, node->nd_1st); add_to_parse_tree(current, node->nd_2nd); break;

ParseTree

s(:and, s(:false), s(:true))

(no code for :and)

Rewriter

[:and, [:false, Type.bool], [:true,

Type.bool], Type.bool]

def process_and(exp) rhs = process exp.shift lhs = process exp.shift rhs.sexp_type.unify lhs.sexp_type rhs.sexp_type.unify Type.bool return t(:and, rhs, lhs, Type.bool)end

TypeChecker

Qfalse && Qtrue;

def process_and(exp) lhs = process exp.shift rhs = process exp.shift return "#{lhs} && #{rhs}"end

Ruby2C

Page 41: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Ruby2cFactorial

• For plain algorithmic code, ruby2c works quite well.

• Type inference determines the types of the args and automatically translates from Ruby types to C types.

• Ruby iterators are converted to equivalent while loops in C.

def factorial(n) f = 1 n.downto(2) { |x| f *= x } return fend

static VALUE factorial (VALUE self, VALUE _n) { long n = NUM2INT(_n); long f, x; f = 1; x = n; while (x >= 2) { f = f * x; x = x - 1; }; return INT2NUM(f);}

Page 42: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

• For 1.0 final:

• Clean

• Document

• Differentiate between generic C and Ruby C

• More Ping-Pong against Metaruby

Ruby2cFuture Plans

Page 43: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Advanced

ruby2ruby -

factorial

sexp as method

auto-refactoring

obfuscator

profiler

zenoptimize

Advanced

ZenHacks

Page 44: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ZenHacksComing Full Circle

• A cornucopia of hackery: Toys, Tricks and Tools that have spawned out of our other projects but don't exactly fit there.

• Integrative: They almost always depend on multiple projects.

• Stuff that others might want to play with, but that I didn’t want to officially “support”.

• Anything I wrote that made me wince.

Page 45: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ZenHacksRubyToRuby - Building Blocks

• First SexpProcessor instance translates Sexps back to ruby.

• Provides a powerful foundation for a number of tools that follow a similar pattern:

• Extract Sexp

• Analyze and Modify Sexp

• Translate to Ruby

• Feed back to Ruby via eval

Page 46: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

sexp = [:defn, :factorial, [:scope, [:block, [:args, :n], [:lasgn, :f, [:lit, 1]], [:iter, [:call, [:lvar, :n], :downto, [:array, [:lit, 2]]], [:dasgn_curr, :x], [:lasgn, :f, [:call, [:lvar, :f], :*, [:array, [:dvar, :x]]]]], [:lvar, :f]]]]

RubyToRuby.new.process sexp=> “def factorial(n) f = 1 n.downto(2) {|x| f = (f * x) } fend”

ZenHacksRubyToRuby - Building Blocks

Page 47: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ZenHacks• Back in June _why posted a blog entry about

having a ruby-lisp hybrid.

• It was just ruby w/ extra parens...

• ZenHacks lets you do nasty stuff like this:

require ‘sexp2ruby’

class Foo _ [:defn, :example, [:args], [:call, [:lit, 1], :+, [:array, [:lit, 1]]]]end

Foo.new.example #=> 2

...not that you’d want to.

Page 48: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

RubyToRubyauto-refactoring examples

• User contribution by Rudi Cilibrasi.

• RubyToRuby allows sexps to be user friendly.

• We can analyze ruby mechanically, but then report what we find using ruby again.

• It allows us to write “lint-like” tools with nice reports like the following:

Suggest refactoring HastilyWritten#weirdfunc from:

def weirdfunc() thingy() while condition do thingy() endend

to:

def weirdfunc() begin thingy() end while conditionend

Page 49: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

RubyToRubyauto-refactoring examples

Page 50: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Ruby Obfuscator

• ParseTree

• + SexpProcessor

• + ruby2c

• + a new tail-end

• = RubyObfuscator

def factorial(n) f = 1 n.downto(2) { |x| f *= x } return fend! ! ! ! ! becomes:

static VALUErrc_cF_factorial(VALUE __self, VALUE n) { VALUE f; VALUE x; f = LONG2FIX(1); x = n; while (RTEST(rb_funcall(x, rb_intern(“>=”), 1, LONG2FIX(2)))) { f = rb_funcall(f, rb_intern(“*”), 1, x); x = rb_funcall(x, rb_intern(“-”), 1, LONG2FIX(1)); }; return f;}

Page 51: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Ruby Obfuscator

Page 52: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

zenprofile

• Standard profiler is 65 lines long, but dead slow.

• Shugo modified the event system to bypass set_trace_func.

• Massively fast, but 701 lines of C.

• zenprofile is 190 lines long, 114 are inlined C.

• 24x faster than ruby, ⅓rd slower than shugo’s.

• zenprofile’s goal is to balance size and speed.

Page 53: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

0

187.5

375.0

562.5

750.0

original zenprofile shugo

16

24

576 701

190

65

zenprofilesize vs speedLoC Time (*8)

Page 54: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

zenprofile

Page 55: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ZenOptimizeFull Circle... on steroids

• Take profiler’s architecture• Make a simple fast

method call counter• Use the profiler to trigger

a threshold event.• Event pulls method’s sexp.• Pass to ruby2c• Pass C to inline• Now you have

zenoptimize!• in 153 lines of code...

% time ruby factorial.rb 5000000Iter = 5000000, T = 67.23166600 sec, 0.00001345 sec / iterreal 1m7.310suser 0m55.980ssys 0m0.280s

% time ruby -reallyfast factorial.rb 5000000*** Optimizing Factorial.factorialIter = 5000000, T = 13.30087900 sec, 0.00000266 sec / iterreal 0m14.382suser 0m12.550ssys 0m0.290s

Page 56: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

ZenOptimizeFull Circle... on steroids

Page 57: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Other Possibilities

• Automated Refactorings

• Idiomatic Optimizations (e.g. always use the most efficient iterator)

• Duplicate code checker

• Many kinds of lint-like tools

• Deprecation rewriter, etc.

Page 58: ZenHacks & Friends · builder.include ‘’ ... thingy() while condition do thingy() end end to: def weirdfunc() begin thingy() end while condition end. RubyToRuby

Thank You

http://www.zenspider.com/seattle.rb

Presenters: Send me your

slides!