optimisation

Avoid Premature Optimisation

In a recent article, we introduced an inefficiency while breaking up a function that did too much. 

Here is the original function:

  private int GetEmployeeSalaries(IEnumerable<Employee> employees)
  {
     int payroll = 0;
     foreach (var employee in employees)
     {
        employee.Salary = SalaryLookup.GetSalary(employee);
        payroll += employee.Salary;
     }
     return payroll;
  }

GetEmployeeSalaries() looks up employee salaries and calculates the payroll. It does two things—it does too much. 

We broke up the behaviour of GetEmployeeSalaries() into two separate methods: PopulateEmployeeSalaries() and CalcStaffPayroll():

  private void PopulateEmployeeSalaries(IEnumerable<Employee> employees)
  {
     foreach (var employee in employees)
        employee.Salary = SalaryLookup.GetSalary(employee);
  }

  private int CalcStaffPayroll(IEnumerable<Employee> employees)
  {
     int payroll = 0;
     foreach (var employee in employees)
        payroll += employee.Salary;
     return payroll;
  }

Can you spot the introduced inefficiency in the new functions?

While GetEmployeeSalaries() cycled through the collection of employees once, the result of our a-function-does-one-thing refactoring now loops through the employees twice—once for PopulateEmployeeSalaries() and again for CalcStaffPayroll().

No doubt this is inefficient. But is it a problem? 

It depends. Cycling through millions of employee records will likely lead to a detectable degradation in performance. However, when we have a few thousand employees, we are unlikely to see a noticeable difference in execution time[1] due to the extra loop.

The actual cost of maintaining software lies in how easy it is to understand and change.

98% of the time, it’s not about squeezing out maximum performance. Who cares whether the computer has to perform a few thousand extra clock cycles? Such waste will slow down our program by microseconds. Most of the time, it doesn’t matter whether overall execution times are even milliseconds slower. Therefore, let’s decide not to worry about performance in the first instance

If we discover program execution is too slow, we can locally refactor the code to improve performance. Unfortunately, when we do so, our program usually becomes a little harder to understand and maintain. 

There is always a cost. Programming is a trade-off. We don’t get optimal maintainability and optimal performance simultaneouslywe don’t get to have it all. Constantly optimising for low execution times, so-called Premature Optimisation, will leave us with a messy, hard-to-maintain system. The codebase might end up so untidy that the performance will ultimately suffer—a situation best avoided. 

Avoid Premature Optimisation and favour writing simple, clean code.

 

Footnotes:

[1] In reality, the SalaryLookup calls would likely be a performance bottleneck if they are calls over a network. An optimisation effort might see these parallelised. 

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply