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 point | Typical symptom | Possible 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.
.Resultor.Wait()on the UI thread block the user interface again and can cause deadlocks. async voidoutside event handlers. Exceptions cannot be caught there. Useasync Taskeverywhere 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
- We analyse your software, record the starting points in a concept paper and implement them with you: Quality review and code review.
- We safeguard the speed you have gained with automated tests: Test automation.
- All our services for stable and secure software: Quality and security.
- Is your software freezing? Arrange an initial consultation.
Sources: case study “Own CAD system: 12.5 to 15 times faster”, Cadida case studies; Microsoft Learn: Asynchronous programming with async and await.
