By Developers, For Developers

Historical errata for Ruby Performance Optimization

PDF PgPaper PgTypeDescriptionFixed onComments
15TYPO

“As usual with thse measurements”

2015-04-14
24ERROR

In the signal1.rb example, commenting out the ‘trap(“TERM”)’ line does not change the output: ObjectSpace still reports one LargeObject instance. However, if I change do_something to allocate two or more LargeObject instances, all but one of them do get garbage collected if the trap line is absent.

2015-05-05Thanks! \n \nThe problem is that in Ruby 2.2 you need to force it to perform the major GC, like this: \n \n GC.start(full_mark: true, immediate_sweep: true) \n \nOtherwise, the object may be marked, but not swept. \n \nI've also found that Ruby 2.1 doesn't need to be forced. When I wrote this chapter, I used 2.1 and didn't notice this behaviour. \n
26TYPO

In the second part of each_bang.rb, it should probably create instances of Thing instead of instances of Object for consistency with the first part. Also, it should probably count the instances in the same way, (“ObjectSpace.each_object(Thing).count ”instead of “ObjectSpace.count_objects[:T_OBJECT]”).

2015-05-05Thanks! Corrected.
29TYPO

On iPhone5 screen, page 29/424

“To get an rough idea of how things”

Should probably be “a rough idea”.

2015-05-07
37ERROR

On iPhone5 screen, page 37 / 424

“On Linux and Mac OS you can get RSS from the ps command”

Strictly speaking, it should be Mac OS X, to be able to use `ps`. There are a couple other places in the book, but not always referring to commands.

2015-05-07
61TYPO

On iPhone5 screen, page 61 / 424

“you will have to run the wrapper several time”

Should be “several times”

2015-05-07
172TYPO

On iPhone5 screen, page 172 / 424

“to treat it as an craft” -> a craft

“precidely” -> precisely

2015-05-07
222ERROR

On iPhone5 screen, page 298 / 424

“String#downcase! doesn’t even appear”

It should probably be “upcase!” here.

2015-05-07
331SUGGEST

On iPhone5 screen, page 331 / 424

“PostgreSQL - the most often used database server in the Ruby on Rails world”

Would you have some reference to back up the claim? I see no problem in removing the whole sentence, actually (note: I am a PostgreSQL user :-) )

2015-05-07Agree, that was too strong of a statement. Fixed. \n \nThanks for this and all other reports. I do appreciate your time that you spent reading so closely.
336SUGGEST

On iPhone5 screen, page 336 / 424

“Forking ensures that any memory allocations happen in child process only”

This will not work on standard Windows installation. Perhaps a simple mention and suggestion to use batch / PowerShell can suffice at this point.

2015-05-29
344TYPO

On iPhone5 screen, page 344 / 424

“Yes, that is a shortcut, but an useful one”

Not sure, but I would write “a useful one”.

2015-05-07
54TYPO

arrag_agg instead of array_agg

2015-05-28Fixed. Thanks, Jennifer
31TYPO

“I made the map block more verbose to show you whether the problem is”

Page 31, paragraph 1 should read

“I made the map block more verbose to show you where the problem is”

2015-06-10
211TYPO

“That is already 4.6x faster. But avoiding regular expressions altogether is even more faster”

Middle of page, should read:

“That is already 4.6x faster. But avoiding regular expressions altogether is even faster”

2015-07-08
31ERROR

(A slightly nitpicky erratum here, forgive me, but I did hesitate for a moment wondering if I was missing something.)

In the description of how to parse dates more efficiently, it says “A better solution is to let the date parser know which regular expression to use”. This implies that strptime() takes a regular expression, but it doesn’t, it takes a format string. I think this occurrence of “regular expression” and the one further down the page, “avoiding regular expressions altogether is even more faster”, should be replaced with something more accurate like “template” or “date format”.

Also, in that second quote, it should read just “faster” instead of “more faster”.

2015-07-08
26SUGGEST

The code snippet chp2/each_bang.rb actually uses a while loop, so this code snippet might stand to be renamed.

2015-07-08Thanks, Chris. I'll probably leave it as is, because internally each! is a while statement anyways.
35TYPO

In the psql code at the bottom of the page it looks like the # symbol is causing the syntax highlighter to make the \\timing command and select statement look like comments.

2015-07-08
41TYPO

The psql vs Active Record memory usage comparison says “ActiveRecord uses 3.5x more memory.” The psql method shows 11 MB of memory and AR shows 33 MB, so that should be “3x more memory”.

2015-07-08I compare 33MB to the size of the data, which is 9.5MB. So it is almost 3.5x
43TYPO

When creating the minions table, the text says:

“Run the migration with bundle exec rake db:migrate and you will get 10 Minions for each Thing in the database.”

This needs to be run with RAILS_ENV=production so that the table is created in the production database, otherwise the code that follows won’t work.

2015-07-08Yeah, good catch!
43ERROR

This is more of a technical discrepancy than an error.

When I ran the two ActiveRecord calls to load all the Minions for all Things, I actually got better memory performance with the second one:

{2.2.1 MB"}}

{2.2.1 MB"}}

The text shows an increase in memory usage for the second, but running this under Rails 4.2.3 I got the results shown above. Perhaps Rails 4.2 has made improvements in this area?

2015-07-08Looks like it. I'll investigate why. Meanwhile, I've changed the explanation a bit to not emphasize the memory usage increase.
47TYPO

The first line of this snippet is missing the closing pipe: |object|

<% objects.each do |object %>
<%= render partial: ‘object’, locals: { object: object } %>
<% end %>

2015-07-08
48ERROR

The Rails partial rendering example says:

“That also performs two orders of magnitude better.”

The numbers quoted for Rails 4.x are 1.840 s and 0.100 s. That’s about 20x faster, but “two orders of magnitude” would be 100x faster.

2015-07-08Good catch, thanks. The text referred to the (incorrect) measurement I did a while ago. I fixed the numbers, but forgot to update the explanation.
50TYPO

“They take both time and memory, so be careful when using them in a loop, especially with link_to url_for and img_tag.”

There should be a comma between “link_to” and “url_for”.

2015-07-08
8TYPO

I made the map block more verbose to show you whether the problem is. s/b I made the map block more verbose to show you where the problem is.

2015-07-28
54SUGGEST

The link to KCachegrind in the footnote seems to be to an old page. Since I can’t include links in errata, all I can say is that the old page has a link to the new page, and that should probably be substituted in.

55ERROR

Using ruby-prof version 0.15.9 (on Ruby 2.2.4p230), in order to get the final line of chp4/ruby_prof_example_api1.rb printing the result of the FlatPrinter to the file, I had to slightly change the syntax, otherwise I ended up with an empty file.

The API is `RubyProf::AbstractPrinter#print(output = STDOUT, options = {})`, so I needed to change

`printer.print(File.open(“ruby_prof_example_api1_profile.txt”, “w+”))`

to

`printer.print(File.open(“ruby_prof_example_api1_profile.txt”, “w+”), {})`

71SUGGEST

Call Graph rendering didn’t work for me out of the box using QCacheGrind on Mac OSX, so I would suggest adding in some extra installation instructions for the Graphviz library on each platform.

In order for me to get the graphs rendering, I needed to perform the following steps:

$ brew install graphviz
$ sudo ln -s /usr/local/bin/dot /usr/bin/dot

The symlink was because QCacheGrind couldn’t seem to find where dot was.

Since the call graphs are great, I really think that adding extra installation information where possible would go far to prevent disappointment when you say you’re just about to show “the most useful [graph] in KCachegrind”, yet you just get error messages and have to go searching for a solution to render them properly.

74SUGGEST

class AppTest < MiniTest::Unit::TestCase

is now

class AppTest < MiniTest::Test

in Minitest 5.8.

55ERROR

ruby: 2.3.0
ruby-prof: 0.15.9

This line `printer.print(File.open(“ruby_prof_example_api1_profile.txt”, “w+”))`

I needed to change it to:

`File.open(“ruby_prof_example_api1_profile.txt”, “w+”) { |file| printer.print(file) }`

I tried: `printer.print(File.open(“ruby_prof_example_api1_profile.txt”, “w+”), {})` but that didn’t work either.

41-42ERROR

Command passes gc: :disable, but results showgc with a gc_count: 1. If GC is disabled, gc_count should be 0.

= 2.2.0 :001 > Measure.run(gc: :disable) { Thing.all.load } {"2.2.0":{"gc":"enable","time":0.32,"gc_count":1,"memory":"33 MB"}} => nil =

INVALID COMPARISON
The above command is run using gc: :disable and compared to the following command where gc is enabled.

===
2.2.0 :001 > Measure.run { Thing.all.select([:id, :col1, :col5]).load }
{2.2.0 MB"}}
=> nil

“This uses 5 times less memory and runs 1.5 times faster than Thing.all.load.” (Comparing 33 MB to 7 MB)
===

This is an invalid comparison as the first command runs without GC and is 33 MB, while the 2nd command runs with GC and reports 7 MB, but also has a gc_count of 1. When testing these commands myself, I get something more like the following:

===
2.2.0 :001 > Measure.run(gc: :disable) { Thing.all.load }
{2.2.0 MB"}}
=> nil

2.2.0 :001 > Measure.run (gc: :disable) { Thing.all.select([:id, :col1, :col5]).load }
{2.2.0 MB"}}
=> nil
===

With, gc_count: 0 on both memory is only half the amount of memory on the 2nd command, not 5 times.

15SUGGEST

“Let’s use the wrapper to run our unoptimized code example from the previous chapter” but then the sample code has nothing to do with the mentioned example from the previous chapter. The structure of the “data.csv” file used in the example is not shown anywhere and that makes it hard to reason about it.

161TYPO

Small typo. Instead of “Now that you how Ruby allocates, …” should be “Now that you know how Ruby allocates…”

102ERROR

Page 101 lists GC Time(ms) as 0.755 in the GC profiler report, but page 102 says “the single collection pass took almost 800 ms”, which is 1000 times that value. (I hope I’m looking in the right place, but I can’t see what else the almost 800 ms could be referring to.)

116TYPO

Missing word between “to” and “the performance” in this fragment:

“Should the test find a slowdown, we want to the performance before and after, and their difference.”

80ERROR

The description of chp5/app_optimized3.rb is incorrect/misleading in that it includes Regexp#initialize calls. However, the Regexp’s do not include any interpolation, so ruby generates one static regexp during compilation and uses it for all calls to the function, it does not initialize a new regexp per method call. This is true as far back as ruby 1.8, and probably even true before that, as I believe perl operates similarly.

Example, best tested with irb:

ObjectSpace.count_objects[:T_REGEXP]

  1. => 173
  1. Use regexp without interpolation, ruby creates static regexp on method definition:
    def a; //; end
    ObjectSpace.count_objects[:T_REGEXP]
  2. => 174
  1. Calling the method does not generate a new one:
    (0..100).map{a}
    ObjectSpace.count_objects[:T_REGEXP]
  2. => 174
  1. Use regexp with interpolation, ruby doesn’t create a static regexp on method definition:
    def b; /#{}/; end
    ObjectSpace.count_objects[:T_REGEXP]
  2. => 174
  1. It generates a new regexp every call:
    (0..100).map{b}
    ObjectSpace.count_objects[:T_REGEXP]
  2. => 275
  1. Use regexp with interpolation with o modifier, ruby doesn’t create a static regexp on method definition:
    def c; /#{}/o; end
    ObjectSpace.count_objects[:T_REGEXP]
  2. => 275
  1. But it creates one the first time the method is called, and uses it for subsequent calls:
    (0..100).map{c}
    ObjectSpace.count_objects[:T_REGEXP]
  2. => 276
106SUGGEST

For the trick that works on Linux to clear the filesystem cache, use just echo instead of sudo echo. If you require passwords for sudo and don’t support tickets, using sudo echo and sudo tee is going to require two password prompts, and the echo command does not need superuser privileges.

160ERROR

When describing memsizes of objects, the book says that the extra byte for strings that can’t be stored in the ruby object is for “upkeep”. That is incorrect, the extra byte is so the string is null terminated, so you can pass the string to C str* functions that expected null terminated strings without worrying about SIGSEGV (not that that is a good idea). All bytes for upkeep are stored inside the object.

14SUGGEST

On printed page 14, in file chp2/wrapper.rb there are two invocations to GC.start as follows:

GC.start
GC.start(full_mark: true, immediate_sweep: true, immediate_mark: false)

This second form (with options) seems to not be used anywhere else in the book, and looks like it is a leftover that was meant to be removed.

117SUGGEST

It seems somewhat strange that the methods which are meant to help readers establish their own peformance testing/benchmarking routines (specifically the methods: measure, performance_benchmark and assert_performance) are not coded in the performance-aware way that is discussed in the book.

(One could argue that they are helper functions not intended for high performance, but neverthless it was an obvious opportunity to show performance-aware code, which was missed.)

17ERROR

If you want to modify an array in place, you need only to modify each of its elements in place.

data.each{|str|str.upcase!}

When bench-marked, I averaged 0.107 vs 0.109 with

data.map!{|str|str.upcase!}

Barely any difference, but still… map! is not really doing anything after str.upcase!.

88-89TYPO

In the sentence “For example, how do we know that heap_page_allocate represents Ruby object creation?”, “heap_page_allocate” should be replaced by “heap_assign_page”.

Categories: