From: Dominik Bathon Date: 2006-09-03T02:42:50+09:00 Subject: Re: [ANN] Using RubyInline for Optimization On Sat, 02 Sep 2006 05:26:04 +0200, Eric Hodel wrote: > I wrote an article on using RubyInline for optimization where I take > png.rb, sprinkle in a little profiling and a little C and make it go > over 100 times faster. Nice article, but in this case it is possible to get almost the same speedup in pure Ruby: Base version: def to_blob blob = [] blob << [137, 80, 78, 71, 13, 10, 26, 10].pack("C*") # PNG signature blob << PNG.chunk('IHDR', [ @height, @width, @bits, 6, 0, 0, 0 ].pack("N2C5")) # 0 == filter type code "none" data = @data.map { |row| [0] + row.map { |p| p.values } }.flatten blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(data.pack("C*"), 9)) blob << PNG.chunk('IEND', '') blob.join end $ time ruby -Ilib profile.rb real 0m15.504s user 0m15.119s sys 0m0.309s Avoiding flatten (and using a literal for the signature): def to_blob blob = [] blob << "\211PNG\r\n\032\n" # PNG signature blob << PNG.chunk('IHDR', [ @height, @width, @bits, 6, 0, 0, 0 ].pack("N2C5")) # 0 == filter type code "none" data = @data.map { |row| "\0" < row.map { |p| p.values.pack("C*") }.join } blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(data.join, 9)) blob << PNG.chunk('IEND', '') blob.join end $ time ruby -Ilib profile.rb real 0m10.190s user 0m10.081s sys 0m0.043s Using String#% instead of Array#pack: format_str = "%c%c%c%c" data = @data.map { |row| "\0" < row.map { |p| format_str % p.values }.join } instead of data = @data.map { |row| "\0" < row.map { |p| p.values.pack("C*") }.join } $ time ruby -Ilib profile.rb real 0m4.974s user 0m4.911s sys 0m0.031s Caching the string representation of the values in PNG::Color (because each pixel is the same instance of color in this case): Add to PNG::Color def values_str @values_str ||= "%c%c%c%c" % @values end Use data = @data.map { |row| "\0" < row.map { |p| p.values_str }.join } instead of format_str = "%c%c%c%c" data = @data.map { |row| "\0" < row.map { |p| format_str % p.values }.join } $ time ruby -Ilib profile.rb real 0m2.489s user 0m2.463s sys 0m0.013s Improving PNG::Canvas#initialize: Use @data = Array.new(@width) { |x| Array.new(@height, background) } instead of @data = Array.new(@width) { |x| Array.new(@height) { background } } $ time ruby -Ilib profile.rb real 0m1.941s user 0m1.914s sys 0m0.014s Representing the values in PNG::Color as String (instead of as Array) (see complete patch below): $ time ruby -Ilib profile.rb real 0m1.492s user 0m1.445s sys 0m0.015s So, it is ten times faster in pure Ruby. Dominik --- png-1.0.0/lib/png.rb 2006-08-31 22:57:13.000000000 +0200 +++ png-1.0.0_opt/lib/png.rb 2006-09-02 19:26:46.000000000 +0200 @@ -71,15 +71,21 @@ ## # Writes the PNG to +path+. - def save(path) - File.open(path, "w") do |f| - f.write [137, 80, 78, 71, 13, 10, 26, 10].pack("C*") # PNG signature - f.write PNG.chunk('IHDR', + def to_blob + blob = [] + blob << "\211PNG\r\n\032\n" # PNG signature + blob << PNG.chunk('IHDR', [ @height, @width, @bits, 6, 0, 0, 0 ].pack("N2C5")) # 0 == filter type code "none" - data = @data.map { |row| [0] + row.map { |p| p.values } }.flatten - f.write PNG.chunk('IDAT', Zlib::Deflate.deflate(data.pack("C*"), 9)) - f.write PNG.chunk('IEND', '') + data = @data.map { |row| "\0" < row.map { |p| p.values }.join } + blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(data.join, 9)) + blob << PNG.chunk('IEND', '') + blob.join + end + + def save(path) + File.open(path, "w") do |f| + f.write to_blob end end @@ -94,7 +100,7 @@ # Creates a new color with values +red+, +green+, +blue+, and +alpha+. def initialize(red, green, blue, alpha) - @values = [red, green, blue, alpha] + @values = "%c%c%c%c" % [red, green, blue, alpha] end ## @@ -151,7 +157,7 @@ end def inspect # :nodoc: - "#<%s %02x %02x %02x %02x>" % [self.class, *@values] + "#<%s %02x %02x %02x %02x>" % [self.class, r, b, g, a] end end @@ -179,7 +185,7 @@ def initialize(height, width, background = Color::White) @height = height @width = width - @data = Array.new(@width) { |x| Array.new(@height) { background } } + @data = Array.new(@width) { |x| Array.new(@height, background) } end ##