111 lines
4.8 KiB
Plaintext
111 lines
4.8 KiB
Plaintext
== Why is this an issue?
|
|
|
|
It is important to be careful when searching for characters within a substring. Let's consider the following example:
|
|
|
|
[source,csharp,diff-id=1,diff-type=noncompliant]
|
|
----
|
|
if (str.SubString(startIndex).IndexOf(char1) == -1) // Noncompliant: a new string is going to be created by "Substring"
|
|
{
|
|
// ...
|
|
}
|
|
----
|
|
|
|
A new `string` object is created with every call to the https://learn.microsoft.com/en-us/dotnet/api/system.string.substring[`Substring`] method. When chaining it with any of the following methods, it creates a new object for one-time use only:
|
|
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.string.indexof[`IndexOf`]
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.string.indexofany[`IndexOfAny`]
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.string.lastindexof[`LastIndexOf`]
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.string.lastindexofany[`LastIndexOfAny`]
|
|
|
|
=== What is the potential impact?
|
|
|
|
Creating a new object consumes significant https://en.wikipedia.org/wiki/System_resource[system resources], especially CPU and memory. When using `Substring` repeatedly, such as in a loop, it can greatly impact the overall performance of the application or program.
|
|
|
|
To mitigate the creation of new objects while searching for characters within a substring, it is possible to use an overload of the mentioned methods with a `startIndex` parameter:
|
|
|
|
[source,csharp,diff-id=1,diff-type=compliant]
|
|
----
|
|
if (str.IndexOf(char1, startIndex) == -1) // Compliant: no new instance of string is created
|
|
{
|
|
// ...
|
|
}
|
|
----
|
|
|
|
Using these methods gives the same result while avoiding the creation of additional `string` objects.
|
|
|
|
== Resources
|
|
|
|
=== Documentation
|
|
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.string.substring[`String.Substring` Method]
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.string.indexof[`String.IndexOf` Method]
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.string.indexofany[`String.IndexOfAny` Method]
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.string.lastindexof[`String.LastIndexOf` Method]
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.string.lastindexofany[`String.LastIndexOfAny` Method]
|
|
|
|
=== Benchmarks
|
|
|
|
[options="header"]
|
|
|========================================================================================================
|
|
| Method | Runtime | StringSize | Mean | StdDev | Ratio | Allocated
|
|
| SubstringThenIndexOf | .NET 7.0 | 10 | 11.234 ms | 0.1319 ms | 1.00 | 40000008 B
|
|
| IndexOfOnly | .NET 7.0 | 10 | 2.477 ms | 0.0473 ms | 0.22 | 2 B
|
|
| SubstringThenIndexOf | .NET Framework 4.6.2 | 10 | 8.634 ms | 0.4195 ms | 1.00 | 48141349 B
|
|
| IndexOfOnly | .NET Framework 4.6.2 | 10 | 1.724 ms | 0.0346 ms | 0.19 | -
|
|
| SubstringThenIndexOf | .NET 7.0 | 100 | 14.651 ms | 0.2977 ms | 1.00 | 176000008 B
|
|
| IndexOfOnly | .NET 7.0 | 100 | 2.464 ms | 0.0501 ms | 0.17 | 2 B
|
|
| SubstringThenIndexOf | .NET Framework 4.6.2 | 100 | 13.363 ms | 0.2044 ms | 1.00 | 176518761 B
|
|
| IndexOfOnly | .NET Framework 4.6.2 | 100 | 1.689 ms | 0.0290 ms | 0.13 | -
|
|
| SubstringThenIndexOf | .NET 7.0 | 1000 | 78.688 ms | 2.4727 ms | 1.00 | 1528000072 B
|
|
| IndexOfOnly | .NET 7.0 | 1000 | 2.456 ms | 0.0397 ms | 0.03 | 2 B
|
|
| SubstringThenIndexOf | .NET Framework 4.6.2 | 1000 | 75.994 ms | 1.2650 ms | 1.00 | 1532637240 B
|
|
| IndexOfOnly | .NET Framework 4.6.2 | 1000 | 1.699 ms | 0.0216 ms | 0.02 | -
|
|
|========================================================================================================
|
|
|
|
The results were generated by running the following snippet with https://github.com/dotnet/BenchmarkDotNet[BenchmarkDotNet]:
|
|
|
|
[source,csharp]
|
|
----
|
|
private string theString;
|
|
private int iteration = 1_000_000;
|
|
|
|
[Params(10, 100, 1_000)]
|
|
public int StringSize { get; set; }
|
|
|
|
[GlobalSetup]
|
|
public void Setup() =>
|
|
theString = new string('a', StringSize);
|
|
|
|
[Benchmark(Baseline = true)]
|
|
public void SubstringThenIndexOf()
|
|
{
|
|
for (var i = 0; i < iteration; i++)
|
|
{
|
|
_ = theString.Substring(StringSize / 4).IndexOf('a');
|
|
}
|
|
}
|
|
|
|
[Benchmark]
|
|
public void IndexOfOnly()
|
|
{
|
|
for (var i = 0; i < iteration; i++)
|
|
{
|
|
_ = theString.IndexOf('a', StringSize / 4) - StringSize / 4;
|
|
}
|
|
}
|
|
----
|
|
|
|
Hardware configuration:
|
|
|
|
[source]
|
|
----
|
|
BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2965/22H2/2022Update)
|
|
12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores
|
|
.NET SDK=7.0.203
|
|
[Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
|
|
.NET 7.0 : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
|
|
.NET Framework 4.6.2 : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
|
|
----
|
|
|
|
include::../rspecator.adoc[]
|