Profiling C# Code: Finding Bottlenecks in Practice

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Profiling C# Code: Finding Bottlenecks in Practice

\n

Performance optimization is one of the key tasks for a developer. Even the most beautiful code can run slowly if it has bottlenecks. In this article, we will look at how to profile C# code in the .NET environment, find problem areas, and eliminate them. You will learn about practical tools such as BenchmarkDotNet, dotMemory, and the built-in Stopwatch, and also get ready-made examples for analysis.

\n\n

Why is profiling important?

\n

Profiling is not just about running an application and measuring time. It is a systematic approach to identifying "bottlenecks": slow algorithms, memory leaks, suboptimal database queries, or frequent heap allocations. Without profiling, you risk spending hours optimizing parts of the code that are already fast, while overlooking the real problems.

\n\n

1. Profiling with Stopwatch (Quick Start)

\n

The simplest way to measure code execution time is to use the System.Diagnostics.Stopwatch class. It is suitable for a preliminary assessment, but not for precise benchmarks due to the influence of JIT compilation and background processes.

\n\n

Example of using Stopwatch

\n
using System.Diagnostics;\n\npublic class Example\n{\n    public void Run()\n    {\n        Stopwatch sw = Stopwatch.StartNew();\n\n        // Code to measure\n        SimulateWork();\n\n        sw.Stop();\n        Console.WriteLine($"Execution time: {sw.ElapsedMilliseconds} ms");\n    }\n\n    private void SimulateWork()\n    {\n        for (int i = 0; i < 1_000_000; i++)\n        {\n            // Simulating work\n        }\n    }\n}
\n

Drawbacks: Stopwatch does not account for JIT warm-up, garbage collection, or other factors. For serious analysis, use specialized libraries.

\n\n

2. Professional Profiling with BenchmarkDotNet

\n

BenchmarkDotNet is the gold standard for benchmarking in .NET. It automatically warms up the code, runs multiple iterations, analyzes statistics, and accounts for the impact of the garbage collector. Install the package via NuGet:

\n
dotnet add package BenchmarkDotNet
\n\n

Example of a benchmark comparing two methods

\n
using BenchmarkDotNet.Attributes;\nusing BenchmarkDotNet.Running;\n\npublic class StringBenchmark\n{\n    private string[] data = { "apple", "banana", "cherry", "date" };\n\n    [Benchmark]\n    public string ConcatWithPlus()\n    {\n        string result = "";\n        foreach (var s in data)\n            result += s;\n        return result;\n    }\n\n    [Benchmark]\n    public string ConcatWithStringBuilder()\n    {\n        var sb = new System.Text.StringBuilder();\n        foreach (var s in data)\n            sb.Append(s);\n        return sb.ToString();\n    }\n}\n\npublic class Program\n{\n    public static void Main()\n    {\n        var summary = BenchmarkRunner.Run<StringBenchmark>();\n    }\n}
\n

After running, you will see a table with time, memory allocations, and errors. This allows you to objectively compare StringBuilder and concatenation using +.

\n\n

3. Memory Analysis with dotMemory and Visual Studio Diagnostic Tools

\n

Bottlenecks can occur not only in speed but also in memory consumption. The dotMemory tool from JetBrains and the built-in Visual Studio tools (Diagnostic Tools) help find leaks, frequent allocations, and objects that live long on the heap.

\n\n

How to use Visual Studio Diagnostic Tools

\n
    \n
  • Run the application in Debug mode.
  • \n
  • Open the Diagnostic Tools window (Debug → Windows → Show Diagnostic Tools).
  • \n
  • Enable memory profiling (Memory Usage) or CPU profiling (CPU Usage).
  • \n
  • Take a memory snapshot (Snapshot) before and after executing the code.

Blogs

Book Recommendations