You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core 3.1中IEnumerable<int>的ToArray性能异常原因咨询

Why Do IEnumerable.ToArray()/ToList() Have Performance Spikes Between 530k-800k Elements?

Awesome question—let’s dig into why this weird performance blip only happens with int and that specific element count range! I’ll break this down using .NET’s internal implementation details and memory management behavior.

1. How ToArray()/ToList() Work for Yield-Based Enumerables

First, your GetEnumerableInts() uses yield return, which creates an enumerable that doesn’t implement ICollection<T>—meaning .NET has no way to know the total element count upfront. For these enumerables, ToArray() and ToList() follow this flow:

  • Start with a small buffer array (usually size 4)
  • Iterate through the enumerable, adding elements to the buffer
  • When the buffer is full, double its capacity and copy all existing elements to the new buffer
  • Repeat until all elements are processed, then trim the buffer to the exact element count

Each resize and copy adds overhead, especially for large collections.

2. The 530k Element Threshold: A CLR Optimization Trigger

The performance jump you see at 530k elements happens because at this size, the CLR switches from incremental resizing to allocating a single buffer large enough for all elements in one go. Here’s why this is faster for int:

  • int is a blittable value type—its memory layout is compatible with native code, so the CLR can use ultra-fast native memory copy instructions (like memcpy) to move elements between buffers.
  • For smaller counts, the overhead of multiple resizes + copies adds up. But at 530k elements, the CLR decides that allocating one large buffer and doing a single copy is cheaper than multiple small resizes. That’s why you see a sudden performance improvement.

3. Why Only int Has This Behavior

Other types (classes, non-blittable structs, strings) don’t show this spike because their element copy overhead works differently:

  • Reference types (classes/strings): Copying only moves pointer addresses, which is cheap. Multiple small resizes don’t add enough overhead to create a noticeable spike when switching to a single large allocation.
  • Non-blittable structs: Copying requires running per-element constructors or custom logic—even with a single large buffer, the copy step is still slow, so you don’t see a big performance jump.
  • Blittable value types like int: Only these get the benefit of native memcpy, making the switch from multiple resizes to a single allocation a huge win.

4. The 530k-800k Fluctuation: LOH Memory Quirks

The performance fluctuation in this range ties to the Large Object Heap (LOH). An array of 530k ints is ~2MB (530,000 * 4 bytes), which is way above the LOH threshold (85KB by default). LOH has different memory allocation rules than the Small Object Heap (SOH):

  • At 530k elements, you’re just crossing into a size where the CLR’s LOH allocation strategy shifts (e.g., using pre-allocated memory blocks instead of fragmented ones)
  • Between 530k-800k, you might hit LOH fragmentation or different caching behaviors, leading to minor performance swings before things stabilize for larger arrays.

Quick Test to Confirm

To verify this is all about resize overhead, modify your enumerable to implement ICollection<int> so .NET knows the count upfront:

private class CountedIntEnumerable : IEnumerable<int>, ICollection<int>
{
    private readonly int _count;
    public CountedIntEnumerable(int count) => _count = count;

    public int Count => _count;
    public bool IsReadOnly => false;

    public IEnumerator<int> GetEnumerator()
    {
        for (var i = 0; i < _count; i++)
            yield return 1;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    // These methods aren't used by ToArray()/ToList(), so we can stub them
    public void Add(int item) => throw new NotImplementedException();
    public void Clear() => throw new NotImplementedException();
    public bool Contains(int item) => throw new NotImplementedException();
    public void CopyTo(int[] array, int arrayIndex) => throw new NotImplementedException();
    public bool Remove(int item) => throw new NotImplementedException();
}

Replace your GetEnumerableInts() with this class, and you’ll see the performance spike disappear—since .ToArray()/.ToList() will directly allocate the correct-sized array from the start.

内容的提问来源于stack exchange,提问作者Evgeniy Terekhin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 10:43:14