On Sat, 02 Sep 2006 05:26:04 +0200, Eric Hodel <drbrain / segment7.net>  
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

      ##