Categories

Optimizing code is generally an expensive process (read: time-consuming) and there are established ways of getting to the bottom of “what to optimize”. Thankfully, profilers are available to help with a lot of the guesswork, so it’s generally a good idea to make sure you work with one *most of the time*. Moving along, it was high time for me to look at some Ruby profiling.

The documentation for ruby-profÂ is pretty neat and the library itself is quick to get up and running with. And so we start. For my initial problem, I wrote a goal-seek algorithm for accurately estimating gross earnings, given a target nett earning using a tax table- as opposed to just using a base tax-rate. Anyhow, my first stab algorithm (a simple linear search) included the lines:

```def seek_annual_gross(m_nett, base_perc)
Â Â sample_gross = m_nett * base_perc
Â Â paye = Paye.new(sample_gross)
Â Â p_nett = sample_gross - paye.monthly_tax
Â Â margin = MARGIN*m_nett
Â Â if((p_nett-margin < m_nett) && (p_nett+margin > m_nett))
Â Â  Â return paye.annual_gross.round_to2.to_f
Â Â elsif(p_nett-margin > m_nett)
Â Â  Â return seek_annual_gross(m_nett, (base_perc - (margin)))
Â Â elsif(p_nett+margin < m_nett)
Â Â  Â return seek_annual_gross(m_nett, (base_perc + (margin)))
Â Â end
end
```

The profiler showed up what i kinda suspected- always a good sign. Essentially, my incremental margin for the next step was too small (fixed) and thus, getting closer to the solution was taking too long- and endangered the stack 🙂 What i needed was a better guess at how much to increment.
``` % cumulative self self total time seconds seconds calls ms/call ms/call name 22.58 0.28 0.28 177 1.58 3.22 Integer#times 16.13 0.48 0.20 171 1.17 259.18 NettGoalSeek#seek_annual_gross 6.45 0.56 0.08 179 0.45 0.50 Float#round_to2 ```

```increment = MARGIN*(p_nett - m_nett)/margin
```

and modifying the appropriate calls

```if((p_nett-margin < m_nett) && (p_nett+margin > m_nett))
Â Â return paye.annual_gross.round_to2.to_f
elsif(p_nett-margin > m_nett)
Â Â return seek_annual_gross(m_nett, (base_perc + increment))
elsif(p_nett+margin < m_nett)
Â Â return seek_annual_gross(m_nett, (base_perc - increment))
end
```

And the profiler now reports:
``` % cumulative self self total time seconds seconds calls ms/call ms/call name 2.78 0.16 0.01 11 0.91 6.36 NettGoalSeek#seek_annual_gross 2.78 0.35 0.01 17 0.59 1.18 Integer#times 0.00 0.36 0.00 19 0.00 0.00 Float#round_to2 ```
A significant difference! Incidentally, the time to run, according to the test harness, went down from 1.122316 seconds to 0.189661 seconds. The high-level indicator showing enough of a difference as well.

The by-product of this optimization included the ability to get even more accurate estimations since the stack never overflowed, despite the required margin of error.

The moral: optimization doesn’t need to sacrifice code readability. At the right time, in the right spot, for the right reasons, you can achieve a sweetspot (of sorts) between two opposing(?) constraints. But that’s not to assume i’ve found the nicest sweetspot in this little piece 🙂

So in between refactorings or when there’s a lull in production, indulge the geek inside you.