Benchmarking

The benchmarking codebase in this project is designed to understand and compare the performance of Value Objects, a specific approach to representing data in code. The primary motivation is to assess whether Value Objects, which emphasize immutability and strong type checking, offer performance advantages compared to traditional approaches that use primitive types.

Benchmarking Approach

Benchmarking Scenarios

The code focuses on two fundamental scenarios:

  1. Domain Logic: Benchmarking the performance of Value Objects when used within the business logic (domain) of an application.
  2. Infrastructure Logic: Benchmarking the performance of Value Objects when used within the infrastructure layer, such as data persistence or networking.

Testing Value Objects

The benchmarks test the performance of Value Objects in scenarios involving different types of data:

  • Integers: Benchmarks are executed with Value Objects containing integers, like Vo_int_1, Vo_int_2, etc. These Value Objects vary in complexity, ranging from simple structures to more elaborate compositions.
  • Doubles: Similar to integers, the benchmarks test the performance of Value Objects that hold double-precision floating-point numbers (Vo_double_1, Vo_double_2, etc.).
  • Strings: Benchmarks are performed with Value Objects that store string values (Vo_string_1, Vo_string_2, etc.).

Benchmarking Configuration

The benchmarks are configured to assess performance across various aspects:

  • Memory Usage: The MemoryDiagnoser attribute is used to analyze the memory consumption of each benchmark scenario.
  • Native Memory Profiling: The NativeMemoryProfiler attribute helps to track and analyze native memory allocation and usage, providing insights into the memory footprint of the code.

Example Benchmarks:

  • UsingPrimitivesInDomain: Benchmarks the performance of an example domain using primitive types (e.g., int, double, string).
  • UsingValueObjectsInDomain: Benchmarks the performance of an example domain using Value Objects.
  • UsingPrimitivesInInfrastructure: Benchmarks the performance of example infrastructure code using primitive types.
  • UsingValueObjectsInInfrastructure: Benchmarks the performance of example infrastructure code using Value Objects.

Benchmarking Implementation

The BenchmarkRunner class from the BenchmarkDotNet library is used to execute and analyze the benchmarks.

Running the Benchmarks:

  • The Program.cs file executes the benchmarks using BenchmarkRunner.Run().
  • The benchmarks are run multiple times to ensure accurate and statistically significant results.

Example Domain & Infrastructure Logic:

  • ExampleDomainUsingValueObjects: Demonstrates the use of Value Objects within a domain model (e.g., a Customer class using a CustomerId Value Object).
  • ExampleInfrastructureUsingValueObjects: Demonstrates the use of Value Objects within infrastructure code (e.g., a database repository working with Value Objects).

Performance Metrics:

The benchmarks are designed to collect metrics like:

  • Time Taken: Measures the duration of the benchmark operations.
  • Memory Allocation: Records the memory usage during the benchmark execution.
  • Native Memory Allocation: Identifies native memory allocation events and usage.
  • Other Relevant Metrics: Other metrics specific to the benchmark scenarios may be collected.

Analysis:

The benchmark results are analyzed to compare the performance of Value Objects with the use of primitive types in both domain and infrastructure scenarios. The metrics collected help to understand the tradeoffs and potential benefits of using Value Objects in terms of performance and memory consumption.

Example Code Snippets:

// Program.cs
          BenchmarkRunner.Run();
          
          // Benchmarks.cs
          [Benchmark]
          public void UsingValueObjectsInDomain()
          {
              new ExampleDomainUsingValueObjects().Run();
          }
          
          // ExampleDomainUsingValueObjects.cs
          internal class ExampleDomainUsingValueObjects
          {
              List _list = new List(1_000_000);
          
              internal void Run()
              {
                  for (int i = 0; i < 1000; i++)
                  {
                      _list.Add(new Customer { CustomerId = CustomerId.From(i) });
                  }
              }
          }
          

          

Top-Level Directory Explanations

tests/ - This directory contains unit tests for the StringlyTyped library. It includes benchmark tests and small tests.