“Clean” Code, Horrible Performance
A few days ago, a video from Casey Muratori discussing the performance impacts of clean code caught my attention.
As I delved deeper into the topic, I realized that this video had gone viral, sparking a heated debate among developers. The video shed light on the potential tradeoff between clean code and performance, challenging the widely held belief that clean code always results in optimal performance. Inspired by the discussion surrounding Muratori’s video, I decided to write this article to explore the relationship between clean code and performance, using C# code examples to illustrate the concepts. So let’s get started.
Clean Code is a buzzword that has gained a lot of attention in recent years. The idea behind it is simple: write code that is easy to read, understand, and maintain. But while Clean Code can make your codebase more manageable and easier to work with, it can also come with a hidden cost: performance.
Let’s start by defining what we mean by “Clean Code”. In general, Clean Code is characterized by a number of key principles, such as:
- Code is easy to read and understand
- Code is well-organized and structured
- Code follows established best practices and design patterns
- Code is maintainable and scalable
All of these principles are essential for creating a codebase that is easy to work with over the long term. But there’s one important factor that Clean Code often overlooks: performance.
To illustrate this point, let’s take a look at some C# code examples. Here’s a simple function that takes a list of integers and returns the sum of all the even numbers:
public int SumEvenNumbers(List<int> numbers)
{
int sum = 0;
foreach (int number in numbers)
{
if (number % 2 == 0)
{
sum += number;
}
}
return sum;
}This code is relatively easy to read and understand, and it follows best practices like using a foreach loop instead of a for loop. But how does it perform?
To find out, let’s compare it to a different implementation of the same function:
public int SumEvenNumbers(List<int> numbers)
{
int sum = 0;
for (int i = 0; i < numbers.Count; i++)
{
int number = numbers[i];
if (number % 2 == 0)
{
sum += number;
}
}
return sum;
}This implementation uses a for loop instead of a foreach loop, which is generally considered to be less readable and maintainable. But it has one key advantage: it performs better.
To see how much of a difference this makes in practice, let’s benchmark the two implementations. Here’s the benchmarking code:
var numbers = Enumerable.Range(0, 1000000).ToList();
var sw = Stopwatch.StartNew();
SumEvenNumbersForeach(numbers);
sw.Stop();
Console.WriteLine($"Foreach: {sw.Elapsed}");
sw.Restart();
SumEvenNumbersFor(numbers);
sw.Stop();
Console.WriteLine($"For: {sw.Elapsed}");This code generates a list of one million integers and then benchmarks the two SumEvenNumbers implementations. Here are the results:
Foreach: 00:00:00.1075807 For: 00:00:00.0497208
As you can see, the forloop implementation is about twice as fast as the foreach loop implementation. And while this might not seem like a big deal for small code snippets like this, it can add up quickly in larger codebases.
Of course, performance isn’t the only factor to consider when writing code. Clean Code principles like readability and maintainability are also important. But it’s essential to keep performance in mind, especially when working on performance-critical applications.
So what’s the takeaway here? Clean Code is important, but it’s not the only thing that matters. When writing code, it’s important to strike a balance between readability, maintainability, and performance.
Here are a few tips to help you achieve that balance:
- Understand the requirements: Before diving into code, make sure you have a clear understanding of the performance requirements of your application. Not all code needs to be optimized for maximum performance, so prioritize based on the needs of your specific project.
- Profile your code: Use profiling tools to identify performance bottlenecks in your code. This will help you pinpoint the areas that require optimization and allow you to focus your efforts where they matter most.
- Optimize strategically: Once you’ve identified the performance-critical areas, optimize them strategically. Consider using more efficient algorithms, data structures, or low-level optimizations when appropriate. However, be mindful that these optimizations can sometimes compromise code readability.
- Measure and iterate: After making optimizations, measure the performance impact to ensure you’re achieving the desired improvements. Keep in mind that performance optimization is an iterative process, and you may need to revisit and fine-tune your code as you gather more data and insights.
- Document your code: When writing performance-critical code, make sure to document the rationale behind any optimizations. This will help other developers understand the tradeoffs you made and maintain the code in the future.