Skip to content

Commit

Permalink
Add Iterators examples
Browse files Browse the repository at this point in the history
  • Loading branch information
cema-sp committed Jul 27, 2016
1 parent 076e7fb commit ec078d5
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 2 deletions.
44 changes: 44 additions & 0 deletions 007_iter_1.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class Thing; end

list = Array.new(1000) { Thing.new }
puts ObjectSpace.each_object(Thing).count

list.each.with_index do |item, i|
GC.start
puts ObjectSpace.each_object(Thing).count if i == 500
end

list = nil
GC.start
puts ObjectSpace.each_object(Thing).count

puts 'With shift deallocation'

list = Array.new(1000) { Thing.new }
puts ObjectSpace.each_object(Thing).count

while list.count > 0
GC.start
puts ObjectSpace.each_object(Thing).count if list.count == 500
item = list.shift
end

GC.start
puts ObjectSpace.each_object(Thing).count

# each! Pattern

puts 'With each! pattern'

class Array
def each!
while count > 0
yield shift # Shift 1 item and execute block on it without &block capturing
end
end
end

Array.new(1000).each { |e| e }
GC.start
puts ObjectSpace.each_object(Thing).count

29 changes: 29 additions & 0 deletions 007_iter_2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
GC.disable
before = ObjectSpace.count_objects

Array.new(10000).each do |i|
[0,1].each do |j|
# j
end
end

after = ObjectSpace.count_objects
puts '# of arrays: %d' % (after[:T_ARRAY] - before[:T_ARRAY])
puts '# of nodes: %d' % (after[:T_NODE] - before[:T_NODE])

GC.enable
GC.start
GC.disable

before2 = ObjectSpace.count_objects

Array.new(10000).each do |i|
[0, 1].each_with_index do |j, index|
# [j, index]
end
end

after2 = ObjectSpace.count_objects
puts '# of arrays: %d' % (after2[:T_ARRAY] - before2[:T_ARRAY])
puts '# of nodes: %d' % (after2[:T_NODE] - before2[:T_NODE])

30 changes: 30 additions & 0 deletions 008_date_parsing.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'date'
require 'benchmark'

date = '2014-05-23'
time = Benchmark.realtime do
100_000.times do
Date.parse(date)
end
end

puts 'Date#parse: %.3f' % time

GC.start
time = Benchmark.realtime do
100_000.times do
Date.strptime(date, '%Y-%m-%d')
end
end

puts 'Date.strptime: %.3f' % time

GC.start
time = Benchmark.realtime do
100_000.times do
Date.civil(date[0, 4].to_i, date[5, 2].to_i, date[8, 2].to_i)
end
end

puts 'Manual parsing: %.3f' % time

53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ See [001_gc.rb](001_gc.rb)
1. 80% of performance optimization comes from memory optimization
See [002_memory.rb](002_memory.rb)

2. GC::Profiler has memory and CPU overhead (See [wrapper.rb] for custom wrapper example).
2. GC::Profiler has memory and CPU overhead (See [wrapper.rb](wrapper.rb) for custom wrapper example).

3. Save memory by avoiding copiyng objects, modify them in place if it is possible (use ! methods).

See [004_string_bang.rb](004_string_bang.rb)

4. If your String is less than 40 bytes - user '<<', not '+=' method to concatenate it and Ruby will not allocate additional object.
4. If your String is less than 40 bytes - user `<<`, not `+=` method to concatenate it and Ruby will not allocate additional object.

See [004_array_bang.rb](004_array_bang.rb) (w/ GC)

Expand All @@ -34,3 +34,52 @@ See [001_gc.rb](001_gc.rb)
See [006_callbacks_3.rb](006_callbacks_3.rb)
Try to avoid `&block` and use `yield` instead.

7. Iterators use block arguments, so use them carefuly

Issues:
1. GC will not collect iterable before iterator is finished
2. Iterators create temp objects

Solutions:

1. Free objects from collection during iteration & use `each!` pattern
See [007_iter_1.rb](007_iter_1.rb)
2. Look at C code to find object allocations
See [007_iter_2.rb](007_iter_2.rb) (for ruby < 2.3.0)

Table of `T_NODE` allocations per iterator item for ruby 2.1:

| Iterator | Enum | Array | Range |
| ---------------: | ---- | ----- | ----- |
| all? | 3 | 3 | 3 |
| any? | 2 | 2 | 2 |
| collect | 0 | 1 | 1 |
| cycle | 0 | 1 | 1 |
| delete_if | 0 || 0 |
| detect | 2 | 2 | 2 |
| each | 0 | 0 | 0 |
| each_index | 0 |||
| each_key ||| 0 |
| each_pair ||| 0 |
| each_value ||| 0 |
| each_with_index | 2 | 2 | 2 |
| each_with_object | 1 | 1 | 1 |
| fill | 0 |||
| find | 2 | 2 | 2 |
| find_all | 1 | 1 | 1 |
| grep | 2 | 2 | 2 |
| inject | 2 | 2 | 2 |
| map | 0 | 1 | 1 |
| none? | 2 | 2 | 2 |
| one? | 2 | 2 | 2 |
| reduce | 2 | 2 | 2 |
| reject | 0 | 1 | 0 |
| reverse | 0 |||
| reverse_each | 0 | 1 | 1 |
| select | 0 | 1 | 0 |

8. Date parsing is slow
See [008_date_parsing.rb](008_date_parsing.rb)

9. `Object#class`, `Object#is_a?`, `Object#kind_of?` are slow when used inside iterators

0 comments on commit ec078d5

Please sign in to comment.