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

C#性能对比:每次新建List还是使用List对象池更优?

C#中两种List实现的性能对比分析

问题背景

在名为MyStaticClass的类中,以下两种实现哪种性能更优?第二种通过对象池复用List<SomeClass>,避免频繁创建新实例,但每次复用前会调用Clear(),这种方式是否能提升性能,还是会因Clear()失去优化价值?


第一种实现:每次新建List

void FunctionThatGetsCalledAlot(...)
{
    List<SomeClass> previousAttributes = new List<SomeClass>();
    previousAttributes.AddRange(SomeStuff);
    node.RemoveAttributes();
    // 其他逻辑
}

第二种实现:基于对象池复用List

private class ThreadSafeObjectPool<T> where T : new()
{
    private readonly Stack<T> pool = new Stack<T>();

    public T Get()
    {
        lock (this.pool)
        {
            if (this.pool.Count > 0)
            {
                return this.pool.Pop();
            }
        }

        return new T();
    }

    public void Return(T v)
    {
        lock (this.pool)
        {
            this.pool.Push(v);
        }
    }
}

private static readonly ThreadSafeObjectPool<List<SomeClass>> previousAttributesPool = new ThreadSafeObjectPool<List<SomeClass>>();
void FunctionThatGetsCalledAlot(...)
{
    List<SomeClass> previousAttributes = MyStaticClass.previousAttributesPool.Get();

    try
    {
        previousAttributes.Clear();
        previousAttributes.AddRange(SomeStuff);
        // 其他逻辑
    }
    finally
    {
        MyStaticClass.previousAttributesPool.Return(previousAttributes);
    }
}

注:修正了原代码中对象池实例化的拼写错误(ObjectPool改为ThreadSafeObjectPool,SomeCLass改为SomeClass)


性能分析

核心差异:List.Clear()的行为

首先要明确:List<T>.Clear()不会释放底层的数组内存,它仅重置List内部的元素计数(_size字段),底层用于存储元素的数组容量保持不变。这是第二种实现能获得性能优势的关键。

两种实现的开销对比

  1. 第一种实现的开销

    • 每次调用都会创建新的List<SomeClass>实例,默认初始容量为4。如果SomeStuff的元素数量超过当前容量,List会触发多次数组扩容(每次容量翻倍),伴随内存分配和元素拷贝的开销。
    • 使用后的List会成为短生命周期对象,频繁创建和回收会增加GC的压力,尤其是Gen 0垃圾回收的频率,在高调用量场景下会显著影响性能。
  2. 第二种实现的优势与额外开销

    • 优势:复用List实例时,底层数组的容量被保留。如果SomeStuff的元素数量相对稳定(或不会远大于之前的容量),AddRange操作无需重新扩容,节省了内存分配和拷贝的开销;同时减少了短生命周期对象的产生,降低了GC的工作量。
    • 额外开销:线程安全的对象池在Get()和Return()时会加锁。单线程场景下这部分锁开销是不必要的,可以改用非线程安全的对象池;但多线程场景下锁是必须的,不过相比内存分配和GC的开销,高调用量场景下锁的影响通常更小。

结论

在FunctionThatGetsCalledAlot被频繁调用的场景下,第二种实现通常能提升性能——Clear()并没有失去优化价值,它保留了List的底层数组容量,避免了重复的扩容和内存分配,同时减轻了GC压力。

但也存在例外情况:

  • 如果SomeStuff的元素数量波动极大(每次都远大于List的现有容量,导致每次复用都需要扩容),第二种的优势会被削弱。
  • 如果调用频率很低,第一种实现的简单性反而更优,锁的开销可能超过对象池带来的收益。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 15:40:16