Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit with actual code ;)

  • Loading branch information...
commit 3d36da3b0fa22f3a4183da7e4403f1850fb76f7a 1 parent 2d92f8a
Yehuda Katz authored
2  LICENSE
View
@@ -1,4 +1,4 @@
-Copyright (c) 2007 YOUR NAME
+Copyright (c) 2007 Yehuda Katz
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
98 README
View
@@ -1,4 +1,96 @@
-templates
-=================
+# benchwarmer
+# ===========
+#
+# A gem that provides a nice DSL for a common type of benchmark.
+#
+# Specifically:
-A plugin for the Merb framework that provides ....
+ require "rubygems"
+ require "benchwarmer"
+
+ TIMES = 100_000
+
+ Benchmark.warmer(TIMES) do # [1]
+ columns :one, :two, :three # [2]
+ titles :one => "Option 1", :three => "Option 3" # [3]
+
+ group("Squeezing") do # [4]
+ report "with #squeeze" do # [5]
+ one { "abc//def//ghi//jkl".squeeze("/") } # [6]
+ two { "abc///def///ghi///jkl".squeeze("/") }
+ three { "abc////def////ghi////jkl".squeeze("/") }
+ end
+ report "with #gsub" do
+ one { "abc//def//ghi//jkl".gsub(/\/+/, "/") }
+ two { "abc///def///ghi///jkl".gsub(/\/+/, "/") }
+ three { "abc////def////ghi////jkl".gsub(/\/+/, "/") }
+ end
+ end
+
+ group("Spliting") do
+ report "with #split" do
+ one { "aaa/aaa/aaa.bbb.ccc.ddd".split(".") }
+ two { "aaa//aaa//aaa.bbb.ccc.ddd.eee".split(".") }
+ three { "aaa///aaa///aaa.bbb.ccc.ddd.eee.fff".split(".") }
+ end
+ report "with #match" do
+ one { "aaa/aaa/aaa.bbb.ccc.ddd".match(/\.([^\.]*)$/) }
+ two { "aaa//aaa//aaa.bbb.ccc.ddd.eee".match(/\.([^\.]*)$/) }
+ three { "aaa///aaa///aaa.bbb.ccc.ddd.eee.fff".match(/\.([^\.]*)$/) }
+ end
+ end
+ end
+
+ # [1] Specify the number of times you want to run each benchmark
+ # [2] Specify the column names. By default, they will be displayed as upcased
+ # versions of themselves.
+ # [3] Specify prettier titles via a Hash. You do *not* need to specify the
+ # same column in both columns and titles.
+ # [4] Specify a group of benchmarks you wish to run
+ # [5] Specify the name of the report you wish to run
+ # [6] Optionally, specify several different variants of the benchmark. This could
+ # be useful if you're testing a new alorithm, and wish to compare a bunch of
+ # benchmarks against each algorithms. In most cases, you will probably want to
+ # use the variant below, which does not require columns.
+
+ # Output:
+ # Running the benchmarks 100000 times each...
+ #
+ # Option 1 | TWO | Option 3 |
+ # -----------------------------------------------------
+ # Squeezing with #squeeze 0.15 | 0.15 | 0.14 |
+ # with #gsub 0.38 | 0.35 | 0.36 |
+ # -----------------------------------------------------
+ # Spliting with #split 0.43 | 0.51 | 0.61 |
+ # with #match 0.29 | 0.35 | 0.38 |
+ # -----------------------------------------------------
+
+ Benchmark.warmer(TIMES) do
+ group("Squeezing") do
+ report "with #squeeze" do
+ "abc//def//ghi//jkl".squeeze("/")
+ end
+ report "with #gsub" do
+ "abc//def//ghi//jkl".gsub(/\/+/, "/")
+ end
+ end
+
+ group("Spliting") do
+ report "with #split" do
+ "aaa/aaa/aaa.bbb.ccc.ddd".split(".")
+ end
+ report "with #match" do
+ "aaa/aaa/aaa.bbb.ccc.ddd".match(/\.([^\.]*)$/)
+ end
+ end
+ end
+
+ # Results |
+ # ---------------------------------
+ # Squeezing with #squeeze 0.15 |
+ # with #gsub 0.37 |
+ # ---------------------------------
+ # Spliting with #split 0.43 |
+ # with #match 0.29 |
+ # ---------------------------------
+
12 Rakefile
View
@@ -1,12 +1,12 @@
require 'rubygems'
require 'rake/gempackagetask'
-GEM = "new_gem"
-VERSION = "0.0.1"
-AUTHOR = "Your Name"
-EMAIL = "Your Email"
-HOMEPAGE = "http://example.com"
-SUMMARY = "A gem that provides..."
+GEM = "benchwarmer"
+VERSION = "0.5"
+AUTHOR = "Yehuda Katz"
+EMAIL = "wycats@gmail.com"
+HOMEPAGE = "http://yehudakatz.com"
+SUMMARY = "A gem that provides a prettier formatting and DSL for Ruby benchmarks"
spec = Gem::Specification.new do |s|
s.name = GEM
130 lib/benchwarmer.rb
View
@@ -0,0 +1,130 @@
+require "benchmark"
+require File.join(File.expand_path(File.dirname(__FILE__)), "vendor", "dictionary")
+
+module Enumerable
+ def max_by(&blk)
+ self.sort_by(&blk).last
+ end
+end
+
+module Benchmark
+
+ def self.warmer(times, &blk)
+ Warmer.new(times).run(&blk)
+ end
+
+ class Warmer
+
+ attr_reader :times, :columns, :groups
+ def initialize(times)
+ @times = times
+ end
+
+ def line!
+ size = @name_max + @group_max + 1
+ @columns.each do |name, val|
+ size += (@columns[name].size <= 5 ? 5 : @columns[name].size) + 3
+ end
+ puts "-" * size
+ end
+
+ def blanks(size)
+ print " " * size
+ end
+
+ def run(&blk)
+ puts "Running the benchmarks #{@times} times each..."
+ puts
+
+ self.instance_eval(&blk)
+
+ unless @columns
+ @columns = Dictionary.new
+ @columns[:results] = "Results"
+ end
+
+ @name_max = @groups.keys.max_by {|x| x.size}.size
+ @group_max = @groups.values.map {|x| x.keys }.flatten.max_by {|x| x.size}.size
+
+ print " " * (@name_max + @group_max + 2)
+
+ puts @columns.map {|col,val| "%5s" % val }.join(" | ") + " |"
+
+ line!
+
+ @groups.each do |group_name,runs|
+ # Print the group's name, left-justified and filling up as much space as the max
+ # group name
+ print "%-#{@name_max + 1}s" % group_name
+
+ # Go through the registered runs
+ runs.each_with_index do |(name, procs), i|
+ blanks(@name_max + 1) if i > 0
+ # The name has to take up all the space of the group name, and then some
+ print "%#{@group_max}s" % name
+
+ # Actually run the benchmarks
+ procs.each_with_index do |proc, i|
+ head = @columns[@columns.order[i]]
+ bench = Benchmark.measure { @times.times(&proc)}
+ print (" %#{head.size >= 5 ? head.size : 5}.2f |" % bench.real)
+ end
+ puts
+ end
+
+ line!
+ end
+ end
+
+ def columns(*list)
+ @columns = list.inject(Dictionary.new) do |accum, col|
+ accum[col] = col.to_s.upcase
+ accum
+ end
+ end
+
+ def titles(titles)
+ @columns.merge!(titles)
+ end
+
+ def group(str, &blk)
+ @groups ||= Dictionary.new {|h,k| h[k] = Dictionary.new}
+ @current_group = str
+ self.instance_eval(&blk)
+ @current_group = nil
+ end
+
+ def report(str, &blk)
+ @groups ||= Dictionary.new
+ if !@columns || @columns.size == 1
+ @groups[@current_group || "default"][str] = [blk]
+ else
+ report = GroupReport.new(@columns.keys)
+ report.instance_eval(&blk)
+ @groups[@current_group || "default"][str] = report.runs
+ end
+ end
+
+ end
+
+ class GroupReport
+ self.instance_methods.each do |meth|
+ send(:undef_method, meth) unless meth =~ /^(__|instance_eval)/
+ end
+
+ attr_accessor :runs, :cols
+
+ def initialize(cols)
+ new_self = (class << self; self end)
+ cols.each do |col|
+ new_self.class_eval <<-RUBY
+ def #{col}(&blk)
+ @runs ||= []
+ @runs << blk
+ end
+ RUBY
+ end
+ end
+ end
+
+end
433 lib/vendor/dictionary.rb
View
@@ -0,0 +1,433 @@
+# TITLE:
+#
+# Dictionary
+#
+# AUTHORS:
+#
+# - Jan Molic
+# - Thomas Sawyer
+#
+# CREDIT:
+#
+# - Andrew Johnson (merge, to_a, inspect, shift and Hash[])
+# - Jeff Sharpe (reverse and reverse!)
+# - Thomas Leitner (has_key? and key?)
+#
+# LICENSE:
+#
+# Copyright (c) 2005 Jan Molic, Thomas Sawyer
+#
+# Ruby License
+#
+# This module is free software. You may use, modify, and/or redistribute this
+# software under the same terms as Ruby.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+# Originally ported from OrderHash 2.0, Copyright (c) 2005 jan molic
+#
+# LOG:
+#
+# - 2007.10.31 trans
+# Fixed initialize so the constructor blocks correctly effected dictionary
+# rather then just the internal hash.
+
+# = Dictionary
+#
+# The Dictionary class is a Hash that preserves order.
+# So it has some array-like extensions also. By defualt
+# a Dictionary object preserves insertion order, but any
+# order can be specified including alphabetical key order.
+#
+# == Usage
+#
+# Just require this file and use Dictionary instead of Hash.
+#
+# # You can do simply
+# hsh = Dictionary.new
+# hsh['z'] = 1
+# hsh['a'] = 2
+# hsh['c'] = 3
+# p hsh.keys #=> ['z','a','c']
+#
+# # or using Dictionary[] method
+# hsh = Dictionary['z', 1, 'a', 2, 'c', 3]
+# p hsh.keys #=> ['z','a','c']
+#
+# # but this doesn't preserve order
+# hsh = Dictionary['z'=>1, 'a'=>2, 'c'=>3]
+# p hsh.keys #=> ['a','c','z']
+#
+# # Dictionary has useful extensions: push, pop and unshift
+# p hsh.push('to_end', 15) #=> true, key added
+# p hsh.push('to_end', 30) #=> false, already - nothing happen
+# p hsh.unshift('to_begin', 50) #=> true, key added
+# p hsh.unshift('to_begin', 60) #=> false, already - nothing happen
+# p hsh.keys #=> ["to_begin", "a", "c", "z", "to_end"]
+# p hsh.pop #=> ["to_end", 15], if nothing remains, return nil
+# p hsh.keys #=> ["to_begin", "a", "c", "z"]
+# p hsh.shift #=> ["to_begin", 30], if nothing remains, return nil
+#
+# == Usage Notes
+#
+# * You can use #order_by to set internal sort order.
+# * #<< takes a two element [k,v] array and inserts.
+# * Use ::auto which creates Dictionay sub-entries as needed.
+# * And ::alpha which creates a new Dictionary sorted by key.
+
+class Dictionary
+
+ include Enumerable
+
+ class << self
+ #--
+ # TODO is this needed? Doesn't the super class do this?
+ #++
+ def [](*args)
+ hsh = new
+ if Hash === args[0]
+ hsh.replace(args[0])
+ elsif (args.size % 2) != 0
+ raise ArgumentError, "odd number of elements for Hash"
+ else
+ while !args.empty?
+ hsh[args.shift] = args.shift
+ end
+ end
+ hsh
+ end
+
+ # Like #new but the block sets the order.
+ #
+ def new_by(*args, &blk)
+ new(*args).order_by(&blk)
+ end
+
+ # Alternate to #new which creates a dictionary sorted by key.
+ #
+ # d = Dictionary.alpha
+ # d["z"] = 1
+ # d["y"] = 2
+ # d["x"] = 3
+ # d #=> {"x"=>3,"y"=>2,"z"=>2}
+ #
+ # This is equivalent to:
+ #
+ # Dictionary.new.order_by { |key,value| key }
+ def alpha(*args, &block)
+ new(*args, &block).order_by_key
+ end
+
+ # Alternate to #new which auto-creates sub-dictionaries as needed.
+ #
+ # d = Dictionary.auto
+ # d["a"]["b"]["c"] = "abc" #=> { "a"=>{"b"=>{"c"=>"abc"}}}
+ #
+ def auto(*args)
+ #AutoDictionary.new(*args)
+ leet = lambda { |hsh, key| hsh[key] = new(&leet) }
+ new(*args, &leet)
+ end
+ end
+
+ # New Dictiionary.
+ def initialize(*args, &blk)
+ @order = []
+ @order_by = nil
+ if blk
+ dict = self # This ensure autmatic key entry effect the
+ oblk = lambda{ |hsh, key| blk[dict,key] } # dictionary rather then just the interal hash.
+ @hash = Hash.new(*args, &oblk)
+ else
+ @hash = Hash.new(*args)
+ end
+ end
+
+ def order
+ reorder if @order_by
+ @order
+ end
+
+ # Keep dictionary sorted by a specific sort order.
+ def order_by( &block )
+ @order_by = block
+ order
+ self
+ end
+
+ # Keep dictionary sorted by key.
+ #
+ # d = Dictionary.new.order_by_key
+ # d["z"] = 1
+ # d["y"] = 2
+ # d["x"] = 3
+ # d #=> {"x"=>3,"y"=>2,"z"=>2}
+ #
+ # This is equivalent to:
+ #
+ # Dictionary.new.order_by { |key,value| key }
+ #
+ # The initializer Dictionary#alpha also provides this.
+ def order_by_key
+ @order_by = lambda { |k,v| k }
+ order
+ self
+ end
+
+ # Keep dictionary sorted by value.
+ #
+ # d = Dictionary.new.order_by_value
+ # d["z"] = 1
+ # d["y"] = 2
+ # d["x"] = 3
+ # d #=> {"x"=>3,"y"=>2,"z"=>2}
+ #
+ # This is equivalent to:
+ #
+ # Dictionary.new.order_by { |key,value| value }
+ def order_by_value
+ @order_by = lambda { |k,v| v }
+ order
+ self
+ end
+
+ #
+ def reorder
+ if @order_by
+ assoc = @order.collect{ |k| [k,@hash[k]] }.sort_by(&@order_by)
+ @order = assoc.collect{ |k,v| k }
+ end
+ @order
+ end
+
+ def ==(hsh2)
+ if hsh2.is_a?( Dictionary )
+ @order == hsh2.order &&
+ @hash == hsh2.instance_variable_get("@hash")
+ else
+ false
+ end
+ end
+
+ def [] k
+ @hash[ k ]
+ end
+
+ def fetch(k, *a, &b)
+ @hash.fetch(k, *a, &b)
+ end
+
+ # Store operator.
+ #
+ # h[key] = value
+ #
+ # Or with additional index.
+ #
+ # h[key,index] = value
+ def []=(k, i=nil, v=nil)
+ if v
+ insert(i,k,v)
+ else
+ store(k,i)
+ end
+ end
+
+ def insert( i,k,v )
+ @order.insert( i,k )
+ @hash.store( k,v )
+ end
+
+ def store( a,b )
+ @order.push( a ) unless @hash.has_key?( a )
+ @hash.store( a,b )
+ end
+
+ def clear
+ @order = []
+ @hash.clear
+ end
+
+ def delete( key )
+ @order.delete( key )
+ @hash.delete( key )
+ end
+
+ def each_key
+ order.each { |k| yield( k ) }
+ self
+ end
+
+ def each_value
+ order.each { |k| yield( @hash[k] ) }
+ self
+ end
+
+ def each
+ order.each { |k| yield( k,@hash[k] ) }
+ self
+ end
+ alias each_pair each
+
+ def delete_if
+ order.clone.each { |k| delete k if yield(k,@hash[k]) }
+ self
+ end
+
+ def values
+ ary = []
+ order.each { |k| ary.push @hash[k] }
+ ary
+ end
+
+ def keys
+ order
+ end
+
+ def invert
+ hsh2 = self.class.new
+ order.each { |k| hsh2[@hash[k]] = k }
+ hsh2
+ end
+
+ def reject( &block )
+ self.dup.delete_if &block
+ end
+
+ def reject!( &block )
+ hsh2 = reject &block
+ self == hsh2 ? nil : hsh2
+ end
+
+ def replace( hsh2 )
+ @order = hsh2.order
+ @hash = hsh2.hash
+ end
+
+ def shift
+ key = order.first
+ key ? [key,delete(key)] : super
+ end
+
+ def unshift( k,v )
+ unless @hash.include?( k )
+ @order.unshift( k )
+ @hash.store( k,v )
+ true
+ else
+ false
+ end
+ end
+
+ def <<(kv)
+ push *kv
+ end
+
+ def push( k,v )
+ unless @hash.include?( k )
+ @order.push( k )
+ @hash.store( k,v )
+ true
+ else
+ false
+ end
+ end
+
+ def pop
+ key = order.last
+ key ? [key,delete(key)] : nil
+ end
+
+ def inspect
+ ary = []
+ each {|k,v| ary << k.inspect + "=>" + v.inspect}
+ '{' + ary.join(", ") + '}'
+ end
+
+ def dup
+ a = []
+ each{ |k,v| a << k; a << v }
+ self.class[*a]
+ end
+
+ def update( hsh2 )
+ hsh2.each { |k,v| self[k] = v }
+ reorder
+ self
+ end
+ alias :merge! update
+
+ def merge( hsh2 )
+ self.dup.update(hsh2)
+ end
+
+ def select
+ ary = []
+ each { |k,v| ary << [k,v] if yield k,v }
+ ary
+ end
+
+ def reverse!
+ @order.reverse!
+ self
+ end
+
+ def reverse
+ dup.reverse!
+ end
+
+ def first
+ @hash[order.first]
+ end
+
+ def last
+ @hash[order.last]
+ end
+
+ def length
+ @order.length
+ end
+ alias :size :length
+
+ def empty?
+ @hash.empty?
+ end
+
+ def has_key?(key)
+ @hash.has_key?(key)
+ end
+
+ def key?(key)
+ @hash.key?(key)
+ end
+
+ def to_a
+ ary = []
+ each { |k,v| ary << [k,v] }
+ ary
+ end
+
+ def to_json
+ buf = "["
+ map do |k,v|
+ buf << k.to_json
+ buf << ", "
+ buf << v.to_json
+ end.join(", ")
+ buf << "]"
+ buf
+ end
+
+ def to_s
+ self.to_a.to_s
+ end
+
+ def to_hash
+ @hash.dup
+ end
+
+ def to_h
+ @hash.dup
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.