Unusual Ways of Boosting Up App Performance. Strings
This is the second post in the series. The other ones can be found here:
- Unusual ways of boosting up app performance. Boxing and Collections
- Unusual Ways of Boosting Up App Performance. Lambdas and LINQs
This post will focus on best approaches of working with strings.
Changing String Contents
String is an immutable type, meaning that the contents of a string object cannot be changed. When you change string contents, a new string object is created. This fact is the main source of performance issues caused by strings. The more you change string contents, the more memory is allocated. This, in turn, triggers garbage collections that impact app performance. A relatively simple solution is to optimize your code so as to minimize the creation of new string objects.
How to Find
Check all string instances that are not created by your code, but by the methods of the
String class. The most obvious example is the
String.Concat method that creates a new string each time you combine strings with the + operator.
To do this in dotMemory:
In the Memory Traffic view, locate and select the
Find all methods of the
Stringclass that create the selected strings.
Consider an example of the function that reverses strings:
An app that uses this function to revert a 1000-character line generates enormous memory traffic (more than 5 MB of allocated and collected memory). A memory snapshot taken with dotMemory reveals that most of the traffic (4 MB of allocations) comes from the
String.Concat method, which, in turn, is called by the
The Heap Allocations Viewer plug-in will also warn you about allocations by highlighting the corresponding line of code:
How to Fix
In most cases, the fix is to use the
StringBuilder class or handle a string as an array of chars using specific array methods. Considering the ‘reverse string’ example, the code could be as follows:
dotMemory shows that traffic dropped by over 99% after the fix:
When seeking ways to optimize your project, take a look at the logging subsystem. In complex applications, for the sake of stability and support convenience, almost all actions are logged. This results in significant memory traffic from the logging subsystem. That’s why it is important to minimize allocations when writing messages to log. There are multiple ways to improve logging.*
*Actually, the optimization approaches shown in this section are universal. The logging subsystem was taken as an example because it works with strings most intensively.
Empty Arrays Allocation
LogMessage method looks as follows:
What are the pitfalls of such implementation? The main concern here is how you call this method. For example, the call
will cause allocation of an empty array. In other words, this line will be equivalent to
How to Find
These allocations would be difficult to detect in the memory snapshot manually, but you can use the Heap Allocations Viewer plug-in to find it very quickly:
How to Fix
The best solution is to create a number of method overloads with explicitly specified arguments. For instance:
The implementation above has a small drawback. What if you pass a value type to, say, the following method?
As the method accepts only the
object argument, which is a reference type, boxing will take place.
How to Find
As with any other boxing, the main clue is a value type on the heap. So, all you need to do is look at the memory traffic and find a value type. In our case this will look as follows:
Of course, the Heap Allocations Viewer will also warn you:
How to Fix
The easiest way is to use generics—a mechanism for deferring type specification until it is declared by client code. Thus, the revised version of the
LogMessage method should look as follows:
Early String Allocation
The advice to defer variable allocation as much as possible is quite obvious. Still, sometimes stating the obvious is useful.
Consider the code below. Here the
logmsg string is created regardless of whether logging is turned on or off:
A better solution would be:
If you use logging for debugging purposes, make sure log calls never reach the release build. You can do this by using the
In the example below, the
LogMessage method will be called only if the DEBUG attribute is explicitly defined.
Subscribe to Blog updates
Thanks, we've got you!
Eager, Lazy and Explicit Loading with Entity Framework Core
Entity Framework Core (EF Core) supports a number of ways to load related data. There’s eager loading, lazy loading, and explicit loading. Each of these approaches have their own advantages and drawbacks. In this post, let’s have a quick look at each of these ways to load data for navigational prope…
OSS Power-Ups: bUnit – Webinar Recording
The recording of our webinar, OSS Power-Ups: bUnit, with Egil Hansen and Steven Giesel, is available. This was the twelfth episode of our OSS Power-Ups series, where we put a spotlight on open-source .NET projects. Subscribe to our community newsletter to receive notifications about future webi…
Accelerating Your Testing Workflow with Unit Test Creation and Navigation
Unit tests play an important role in our daily development workflow. They help us ensure our codebase's correctness when writing new functionality or performing refactorings to improve readability and maintainability. In the process, we often create new test files that accompany the p…
Introducing Predictive Debugging: A Game-Changing Look into the Future
With the introduction of debugging tools, software developers were empowered to interactively investigate the control flow of software programs to find bugs in live environments. At JetBrains, we've always strived to improve the art of debugging. Besides the more standard things you expect from a de…