Main ideas for Performance Optimization
Sat 18 Jan 2020

How do people work good in the morning and performance decreases gradually until Evening? If we don't mention the spirit cause computers don't have it (lol), so the cause is the amount of workload. They are not only what people are doing but also the activities within body including metabolic ones.

The same idea with computer performance. 

  • Memory: limitation of human body.
  • Concurrency: amount of works at same time.
  • The heat: hot weather, dangerous environment, raining days, the dust etc.
  • Background process overtime: lassitude.

There are two main things which affect performance in programing in general and ruby in particular:

  • Memory consumption. (MC)
  • Garbage collection. (GC)

Speaking of Memory consumption we might think that if we just store information so why it affects performance? The reason is that, it stores information equals it does objects allocation which increase the workload. In addition, more objects created, more things to do for GC. The solution is if we can reduce the memory footprint, it would reduce the time for GC.

How about disable GC? Imagine you did many things in your house but you have not cleaned them up. How messy they are? One day you don't have much space in your house to do things. The same idea for disabling GC. System consumes memory without releasing them. It would run out of memory sooner or later. This is a bad idea. Thus, the good thing we can do is reducing number of creating objects. It's like we want to fix television but we just take necessary stuffs (tools) out, put them all together in a container so that we don't have to spend more efforts to clean them up after finished. So we have to do following:

  • Limiting the number of objects.
  • Clean coding.

Looking at example below:

def square
  arr = (1..10).to_a
  res = []
  arr.each do |num|
    num2 = num*num
    res << num2
  end
  res
end

require 'benchmark'
Benchmark.bm do |x|
  x.report('Enable GC: ') {
    GC.enable
    square
  }
  x.report('Disable GC: ') {
    GC.disable
    square
  }
  x.report('Optimized: ') do
    GC.enable
    (1..10).to_a.each_with_object([]) do |num, obj|
      obj << num*num
    end
  end
end

Result:

We can see that after disabling GC, It run faster. However, it is dangerous. In case we can control the input, we know exactly the memory it can take, we could use this method and cleaning up manually then. Regarding to the optimized code. It reduces the number of objects by not create num2 variable in loop so reducing the number of workload for GC. This method is faster than normal way (enable GC) although it is slower than disabling GC. The is no the best, only the better.

We have already known those things, the root causes which make performance worse so how can we learn from that? How can we code better? 

Each languages programing has its own mission. So if you are leader or any one who can make decision for starting projects (or wanting to rebuild specific one) you should think it carefully about them. For coder:

  • How much memory the code use? - Reducing memory workload of GC.
  • Complexity of Algorithm: doing more things, taking more time.

Extra example about how GC does.

Copy to play with it:

class Thing; end
list = Array.new(1000) { Thing.new } # allocate 1000 objects again
puts ObjectSpace.each_object(Thing).count
while list.count > 0
  GC.start # this will garbage collect item from previous iteration
  puts ObjectSpace.each_object(Thing).count # watch the counter decreasing
  item = list.shift
end
GC.start # this will garbage collect item from previous iteration
puts ObjectSpace.each_object(Thing).count # watch the counter decreasing

Memory consumption, Garbage collection and Algorithm complexity are main factors for optimizing performance. It depends on the language we are using so we will have the proper implementation. I will create best practices for ruby language in particular in next article.


Thanks for reading!

Reference: