On Sat, 02 Sep 2006 05:26:04 +0200, Eric H. [email protected]
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
- 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))
- 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)
@@ -151,7 +157,7 @@
end
def inspect # :nodoc:
-
"#<%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
}