When your own software freezes: improving performance systematically

CAD model of a machine part on a screen in front of a steeply rising performance chart

Quality and security

The software has grown over the years, and every version brought new features. At some point, users wait minutes for a response when working with large files, and the window shows “Not responding”. Isolated optimisations based on gut feeling rarely help at that stage. What you need is a systematic approach.

Measure first, then change

The most common reason for wasted optimisation effort is guessing. Measure first, and use realistic data: the large designs, measurement series or projects where your users actually wait. A profiler, such as the Performance Profiler in Visual Studio, shows where the time really goes. Record a baseline measurement for three to five typical workflows. Only then can you prove later what a measure has achieved.

Collect and prioritise the starting points

Grown software rarely has a single bottleneck. Usually there are many places that add up. Collect them in a concept paper and rate each one by impact and effort. Typical candidates are:

Starting pointTypical symptomPossible measure
Long calculation on the UI thread The window freezes Move the calculation to the background
Repeated calculation The same result is recalculated on every redraw Cache results, recalculate only on changes
Unsuitable data structure Run time grows disproportionately with the amount of data Look up via a dictionary or spatial index instead of long lists
Many small accesses Individual database or file accesses inside loops Batch the accesses
Constant redrawing The user interface refreshes on every single change Collect changes, refresh once
Memory pressure Many short-lived objects, frequent garbage collection Reuse objects, avoid unnecessary copies

Implement the most effective points first and measure again after each step.

Calculating in the background

Not every calculation can be made faster. But almost every one can be arranged so that users can keep working. In .NET, async and await make this possible: the calculation runs on a background thread, the user interface stays responsive, and the result then arrives back on the UI thread. An example for WPF:

private CancellationTokenSource cancellation;

private async void CalculateButton_Click(object sender, RoutedEventArgs e)
{
    calculateButton.IsEnabled = false;
    cancellation = new CancellationTokenSource();
    var progress = new Progress<int>(percent => progressBar.Value = percent);
    try
    {
        // Copy the data on the UI thread, work only on the copy in the background
        ModelData data = model.CreateCopy();
        Result result = await Task.Run(
            () => Calculation.Run(data, progress, cancellation.Token));
        ShowResult(result);
    }
    catch (OperationCanceledException)
    {
        statusText.Text = "Calculation cancelled.";
    }
    finally
    {
        calculateButton.IsEnabled = true;
    }
}

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
    cancellation?.Cancel();
}

Important: in the background, the calculation must not touch controls or the drawing database of a CAD system. It works on a copy of the data it needs and returns its result. It reports progress via IProgress<T>. Because the Progress object was created on the UI thread, every report arrives there. The calculation checks for cancellation regularly via the CancellationToken.

From practice: 12.5 to 15 times faster

A company had built its own CAD system and extended it with a great many features over the years. With larger designs, users waited several minutes for a response – the system was practically frozen. The customer’s goal was to double the speed.

We examined the software and recorded the starting points in a concept paper. We stopped counting at almost 50. We implemented the most effective measures ourselves. In several meetings, we guided the customer’s software department to implement the remaining points on its own. Complex calculations now run in the background. The result: instead of the hoped-for doubling, the system is 12.5 to 15 times as fast, measured at the interim stages. Where calculations run in the background, users no longer notice any delay at all.

Typical mistakes

  • Optimising without measuring. Guessing often leads to improving places that hardly cost any time.
  • Measuring with small test data. Many bottlenecks only show up with large amounts of data.
  • Blocking instead of awaiting. .Result or .Wait() on the UI thread block the user interface again and can cause deadlocks.
  • async void outside event handlers. Exceptions cannot be caught there. Use async Task everywhere else.
  • Accessing the user interface or the CAD database from the background. This leads to sporadic crashes that are hard to track down.
  • Not checking after the optimisation. Without repeated measurements or automated tests, the software quietly slows down again with the next versions.
  • Leaving the knowledge with the service provider. If your team understands why something became faster, the software stays fast in the future.

How we help

Sources: case study “Own CAD system: 12.5 to 15 times faster”, Cadida case studies; Microsoft Learn: Asynchronous programming with async and await.

CONTACT

Select your language

First meetingarrange nowFirst meetingarrange now