Program Performance is a topic which gets less prevalent nowadays.
This is most likely related to the ever increasing computational speed of computers. It has become less relevant to optimize code. Because usually, you optimize until its "good enough". Hardware has become so fast, that even suboptimal solutions can often be "good enough" today.
This leads to lagging software and increased energy consumption, despite the efficiency and Speed gains of the hardware.
Despite the increased speed of modern hardware, optimizing code still offers significant benefits. Particularly when working with large datasets, real-time systems, or power-sensitive devices.
Optimized code can easily run 1000 up to millions of times faster than unoptimized Code
Some of the most impactful performance improvements can be achieved through:
Sorting a list of 1,000,000 elements with bubble sort might take minutes, while quicksort can do it in seconds.
// Poor example (O(n²)): Bubble Sort
for (int i = 0; i < n; i++)
for (int j = 0; j < n - i - 1; j++)
if (arr[j] > arr[j + 1])
Swap(arr, j, j + 1);
// Optimized example (O(n log n)): QuickSort
QuickSort(arr, 0, n - 1);
// Suboptimal (O(n²)): Nested loops
for (int i = 0; i < nums.Length; i++)
for (int j = i + 1; j < nums.Length; j++)
if (nums[i] + nums[j] == target) return new[] { i, j };
// Optimized (O(n)): Hash map lookup
Dictionary<int, int> remainders = new Dictionary<int, int>();
for (int i = 0; i < nums.Length; i++)
if (remainders.TryGetValue(nums[i], out int index)) return new[] { index, i };
// Sequential processing (single-threaded)
for (int i = 0; i < image.Height; i++)
for (int j = 0; j < image.Width; j++)
ProcessPixel(image[i, j]);
// Optimized processing (parallelized)
Parallel.For(0, image.Height, i =>
{
for (int j = 0; j < image.Width; j++)
ProcessPixel(image[i, j]);
});
Memory Management: Inefficient memory usage can slow down applications, especially when dealing with large objects or frequent allocations. Minimizing allocations, reusing objects, and applying pooling techniques can improve both speed and memory footprint.
Example: If you're constantly allocating large arrays or objects in a loop, consider object pooling, which reuses preallocated objects, avoiding costly allocations and garbage collection pauses.
I/O Optimization: Input/output operations (file reading/writing, database access) are often bottlenecks in programs. Techniques like buffering, asynchronous I/O, and caching frequently accessed data can greatly reduce wait times.
Example: Reading a file line by line synchronously can slow down an application, but using asynchronous file I/O allows the CPU to continue processing while waiting for I/O operations to complete.
The Process of Optimization is iterative. In general you try to find the Choke point, fix it and repeat.
Imagine you are on a 5 lane Highway. there is not much traffic on it. But there is one location with a construction with only one lane. This causes a 30 Minute traffic jam.
You could add additional lanes to the highway but there would not be a speed improvement.
What you need to do is find the biggest congestion (the construction site) and releave it. This will cause a 30 Minute speed improvement for trespassing cars.
You will now find new congestions in other Places. Perhaps somewhere 2 Lanes are Blocked which you resolve.
This is usually done using a Performance profiler. The Performance analyzer lets you investigate the Memory consumption and which functions take up the most processor time on your Program. This is called "the hot path"

IDEs often can also overlay the time taken per Line in the editor:
