DEV Community

mohamed Tayel
mohamed Tayel

Posted on

Using Benchmarking to Explore Viable Redesigns

Meta Description:

Optimize pagination performance in C# with this in-depth guide. Learn how to use BenchmarkDotNet to compare full sorting vs. partial sorting with SortedList, explore trade-offs, and discover techniques to improve execution time for retrieving the first page of data efficiently.

Performance optimization often starts with understanding where time is being spent in code and exploring alternatives to improve efficiency. In this article, we delve into a practical use case: pagination optimization, focusing on extracting the first page of a sorted list. We'll use BenchmarkDotNet to measure the effectiveness of traditional sorting versus a tailored approach to improve performance.


The Problem: Pagination and Performance

Pagination, or dividing a dataset into smaller chunks, is a common requirement in applications. However, sorting and extracting pages from large datasets can be computationally expensive.

Key Use Case

  • Most users in our hypothetical application only view the first page of results.
  • Sorting the entire dataset to extract the first page is wasteful, as subsequent pages are often ignored.
  • The goal: Optimize the process of retrieving the first page by minimizing unnecessary computations.

Benchmark Setup

We use BenchmarkDotNet to measure two approaches:

  1. Baseline: Populating a list and sorting it entirely before extracting the first page.
  2. Optimized: Incrementally maintaining only the first page in a sorted collection while discarding irrelevant data.

Parameters for the Benchmark

  • SequenceLength: Total number of items.
  • PageSize: Number of items on the first page.

The results are prioritized first by SequenceLength and then by PageSize for clarity.


Approach 1: Full Sorting (Baseline)

The baseline approach involves:

  1. Sorting the entire list.
  2. Removing items beyond the first page.

Code Example

using BenchmarkDotNet.Attributes;
using System.Collections.Generic;

public class PaginationBenchmark
{
    private List<Worker> workers;

    [Params(1000, 10000, 100000)] // Sequence lengths
    public int SequenceLength;

    [Params(10, 50)] // Page sizes
    public int PageSize;

    [GlobalSetup]
    public void Setup()
    {
        workers = new List<Worker>();
        for (int i = 0; i < SequenceLength; i++)
        {
            workers.Add(new Worker { PayRate = i });
        }
    }

    [Benchmark(Baseline = true)]
    public List<Worker> FullSort()
    {
        workers.Sort(Worker.RateComparer);
        return workers.GetRange(0, PageSize);
    }
}
Enter fullscreen mode Exit fullscreen mode

Approach 2: Partial Sorting with SortedList

The optimized approach incrementally maintains only the smallest PageSize items in a SortedList, removing larger items as they arrive. This reduces unnecessary sorting.

Code Example

[Benchmark]
public List<Worker> PartialSort()
{
    var sortedList = new SortedList<int, Worker>();
    foreach (var worker in workers)
    {
        sortedList.Add(worker.PayRate, worker);
        if (sortedList.Count > PageSize)
        {
            sortedList.RemoveAt(sortedList.Count - 1); // Remove largest
        }
    }
    return new List<Worker>(sortedList.Values);
}
Enter fullscreen mode Exit fullscreen mode

Benchmark Results

The results highlight the performance difference:

SequenceLength PageSize Method Mean (ms) Relative
10000 10 FullSort 15.0 1.00
10000 10 PartialSort 5.0 0.33
10000 50 FullSort 17.0 1.00
10000 50 PartialSort 7.0 0.41

Insights

  • Partial sorting is significantly faster than full sorting.
  • The optimized approach reduces execution time by maintaining only the required elements for the first page.

Limitations and Next Steps

Challenges with SortedList

  • Quadratic Time Complexity: Adding items to a SortedList is expensive for larger datasets.
  • Uniqueness Constraint: Keys in a SortedList must be unique, which may not always be feasible.

Future Directions

  • Custom Data Structures: Design a more efficient collection with:
    • Log-linear time complexity for sorting.
    • No uniqueness constraints.
  • Short-Term Improvements: Before investing in a custom solution, analyze existing operations for potential optimizations.

Lessons Learned

  1. Target Narrow Use Cases:

    • Optimization efforts should focus on the most common scenarios, such as retrieving the first page in this example.
  2. Leverage Benchmarks:

    • Benchmarking highlights inefficiencies and quantifies the benefits of different approaches.
  3. Understand Trade-offs:

    • Incremental improvements, like using SortedList, can outperform naive solutions but may still have limitations in scalability.

Conclusion

This exploration demonstrates how benchmarking can guide optimization by focusing on specific use cases. While the SortedList approach showed promise, a truly efficient solution will require designing a custom data structure. By measuring performance and understanding trade-offs, you can make informed decisions to balance efficiency, complexity, and development costs.

Top comments (0)