---
title: "ArrayPool Benchmarks: We have a problem"
description: "Many benchmarks that are using `ArrayPool`, or more specificly `ArryPool<T>.Shared`, are not actually measuring a \"real\" scenarion or the benchmark is flawed in some way. Let's examine a bit on what is going on."
updated: 2025-11-03
tags: ["C#", ".NET", "Performance"]
canonical: "https://steven-giesel.com/blogPost/b8dffa1a-84f7-49a2-9dad-3bf8f7f4e410/arraypool-benchmarks-we-have-a-problem"
---

# ArrayPool Benchmarks: We have a problem

Many benchmarks that are using `ArrayPool`, or more specificly `ArryPool<T>.Shared`, are not actually measuring a "real" scenarion or the benchmark is flawed in some way. Let's examine a bit on what is going on.

## What is an `ArrayPool`?

I wrote an article some time ago title [*"ObjectPool - Rent and return some instances"*](https://steven-giesel.com/blogPost/389e5983-9726-448d-a693-1f7379165c4d/objectpool-rent-and-return-some-instances). The basic idea is the same: You can rent some array space from a pool, use it and return it back to the pool. This way you can avoid allocating new arrays all the time and reduce pressure on the garbage collector. Because the **LOH** is the enemy!!!!!

## Flawed to the bones

Many benchmarks that are using `ArrayPool` are flawed in some way. Let's start with one I posted some time ago. Yes, I do "oopsies" as well. That is why I keep saying, you should check the underlying principles and not just trust benchmarks blindly.

[Here my code](https://linkdotnet.github.io/tips-and-tricks/advanced/arraypool/):

```csharp
[MemoryDiagnoser]
public class Benchmark
{
    const int NumberOfItems = 10000;

    [Benchmark]
    public void ArrayViaNew()
    {
        var array = new int[NumberOfItems];
    }
    [Benchmark]
    public void SharedArrayPool()
    {
        var array = ArrayPool<int>.Shared.Rent(NumberOfItems);
        ArrayPool<int>.Shared.Return(array);
    }
}
```

I hope you can spot the error. Because I am not returning anything and I am not using the array in any way, I am at the mercy of the JIT. The JIT is allowed to optimize away code that is not used. And that is exactly what is happening here. [Here a small repro](https://sharplab.io/#v2:C4LghgzgtgPgAgBgARwIwDoBCBXAZrgUwCcIBuAWACgqA3MIpXAeyaQF4kA7AgdyUwKcAxgAso9ANYAKAJQVKzJugDCRAmGAEAgkSJgAngHUAlsBE69+gAosANrPmKVajdt0GAcrwsGHVKnAAzCgATPyCouJEElQA3lRIiUgADkTGdJpIQkycEMBIxpz5PvoAysYAXgTsSKgI8klICUlBKAAsSKrqmiUmZiU2TPYyzYnxlI2NdAz0ljVIA3YAPIXAAHzopSL0BAAm6ABKgsBSJeVVcqOTC+7Wy6sbWzv7R8DYRJxSswaXE0kAvv4/olWnAOl1XCUvDwSrIruNrolpkhvvoatw+KsANpnSoEAC6DQBVH+QA==):

```csharp
var foo = new Benchmark();
foo.CreateArrayWithArrayPool();
foo.CreateArrayNewArray();

public class Benchmark
{
    private const int ArraySize = 10;
    
    public void CreateArrayWithArrayPool()
    {
        var array =  ArrayPool<int>.Shared.Rent(ArraySize);
        ArrayPool<int>.Shared.Return(array);
    }

    public void CreateArrayNewArray()
    {
        var array = new int[ArraySize];
    }
}
```

If we have a look what the JIT does:
```csharp
Benchmark.CreateArrayNewArray()
    L0000: ret
```

It just returns and does absolutely nothing! So the benchmark can be completely flawed! Now, we could fix it like this:

```csharp
[Benchmark]
public void ArrayViaNew() 
    => DeadCodeEliminationHelper.KeepAliveWithoutBoxing(new int[NumberOfItems]);
```

`DeadCodeEliminationHelper` is a small helper class from `BenchmarkDotNet` that prevents the JIT from optimizing away code. Now we have a more realistic benchmark. 

```no-class
| Method          | Mean       | Error      | StdDev     | Gen0   | Allocated |
|---------------- |-----------:|-----------:|-----------:|-------:|----------:|
| ArrayViaNew     | 795.766 ns | 13.1411 ns | 12.2922 ns | 4.7617 |   40024 B |
| SharedArrayPool |   6.341 ns |  0.0727 ns |  0.0680 ns |      - |         - |
```

Hmm why not returning the array from the `ArrayPool` method? Good question. Let's try that:

```csharp
[Benchmark]
public int[] ArrayViaNew() 
    => new int[NumberOfItems];

[Benchmark]
public int[] SharedArrayPool()
{
    var array = ArrayPool<int>.Shared.Rent(NumberOfItems);
    ArrayPool<int>.Shared.Return(array);
    return array;
}
```

The results would be the same. And those two types you see mainly (well probably more often than not the first one).

## The problem with that

But the issue, especially with returning the array from the `ArrayPool` method is, that you are now returning an array that is potentially already in use somewhere else. That is why it is a shared instance - if you return it, it might be mutated by someone else. Imagine you are renting a car, setting the seat to your liking and returning it. The next time you are entering that car, the seat might be in a completely different position. So that part is not really realistic and is "bad" by design.

The other option, the `void` one, is also a bit misleading to say the least. The question is do you want to return that array or not? If so, you might have to call `ToArray` or `Array.Copy` at some point in time to get your own copy of the data. 

That is why, you have to check against your concrete case and benchmark for that. `ArrayPool` is great, but it also can make things "worse" if you are not using it correctly (plus the additional complexity that comes with it).