Dominik Bathon
9/2/2006 5:43:00 PM
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 =3D []
blob << [137, 80, 78, 71, 13, 10, 26, 10].pack("C*") # PNG signatur=
e
blob << PNG.chunk('IHDR',
[ @height, @width, @bits, 6, 0, 0, 0 ].pack("N2C5=
"))
# 0 =3D=3D filter type code "none"
data =3D @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 =3D []
blob << "\211PNG\r\n\032\n" # PNG signature
blob << PNG.chunk('IHDR',
[ @height, @width, @bits, 6, 0, 0, 0 ].pack("N2C5=
"))
# 0 =3D=3D filter type code "none"
data =3D @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 =3D "%c%c%c%c"
data =3D @data.map { |row| "\0" < row.map { |p| format_str % p.valu=
es =
}.join }
instead of
data =3D @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 ||=3D "%c%c%c%c" % @values
end
Use
data =3D @data.map { |row| "\0" < row.map { |p| p.values_str }.join=
}
instead of
format_str =3D "%c%c%c%c"
data =3D @data.map { |row| "\0" < row.map { |p| format_str % p.valu=
es =
}.join }
$ time ruby -Ilib profile.rb
real 0m2.489s
user 0m2.463s
sys 0m0.013s
Improving PNG::Canvas#initialize:
Use
@data =3D Array.new(@width) { |x| Array.new(@height, background) }
instead of
@data =3D 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) (s=
ee =
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 signat=
ure
- f.write PNG.chunk('IHDR',
+ def to_blob
+ blob =3D []
+ blob << "\211PNG\r\n\032\n" # PNG signature
+ blob << PNG.chunk('IHDR',
[ @height, @width, @bits, 6, 0, 0, 0 =
].pack("N2C5"))
# 0 =3D=3D filter type code "none"
- data =3D @data.map { |row| [0] + row.map { |p| p.values } }.flatt=
en
- f.write PNG.chunk('IDAT', Zlib::Deflate.deflate(data.pack("C*"), =
9))
- f.write PNG.chunk('IEND', '')
+ data =3D @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 +alp=
ha+.
def initialize(red, green, blue, alpha)
- @values =3D [red, green, blue, alpha]
+ @values =3D "%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 =3D Color::White)
@height =3D height
@width =3D width
- @data =3D Array.new(@width) { |x| Array.new(@height) { background=
} }
+ @data =3D Array.new(@width) { |x| Array.new(@height, background) =
}
end
##