List<T>.RemoveAll() 效率/编译器优化
Posted
技术标签:
【中文标题】List<T>.RemoveAll() 效率/编译器优化【英文标题】:List<T>.RemoveAll() efficiency / compiler optimisation 【发布时间】:2019-08-27 02:49:36 【问题描述】:关于效率,有谁知道编译器是否足够聪明不为以下代码中的循环的每次迭代创建包含1, 3, 5
的数组?
var foo = new List<int> 1, 2, 3, 4, 5 ;
foo.RemoveAll(i => new[] 1, 3, 5 .Contains(i));
我更喜欢它的可读性,但不是为了性能。
【问题讨论】:
尝试你自己sharplab.io @MichaelRandall 很棒的网站。你应该用一个简短的例子来写一个答案。我认为这对每个人来说都很有趣。 我真的很惊讶,但看起来它确实为方法internal bool <M>b__0_0(int i)
内的每次迭代创建了一个 3 项数组的新实例。
【参考方案1】:
答案是否定的,它没有优化数组的分配
基本上,每次调用谓词时,它都会检查编译器生成的类并初始化一个新数组以调用Contains
(如您所见here)
private sealed class <>c
public static readonly <>c <>9 = new <>c();
public static Predicate<int> <>9__0_0;
internal bool <M>b__0_0(int i)
// bam!
int[] obj = new int[3];
RuntimeHelpers.InitializeArray(obj, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
return Enumerable.Contains(obj, i);
【讨论】:
【参考方案2】:正如@Michael Randall 已经写的那样,看起来这是不可能的。
我同意,您质疑的代码可读性很好,在 RemoveAll 方法中有列表。但是只有一次实例,我有三个想法:
int[] a = null;
foo.RemoveAll(i => (a ?? (a = new[] 1, 3, 5 )).Contains(i));
这实际上是你的,几乎不需要外部变量。
foo = foo.Except(new[] 1, 3, 5 ).ToList();
这实际上是使用 Linq 的非常好的解决方案。
new List<int>1, 3, 5.ForEach(x => foo.Remove(x));
new[] 1, 3, 5.Iterate(x => foo.Remove(x));
这是我会做的事情。在我几乎所有的代码中,我都有我的扩展方法“迭代”以避免需要 foreach。而且,我不想一直“列出”所有内容来制作 .ForEach(..)
static class Extensions
public static void Iterate<TSource>(this IEnumerable<TSource> source, Action<TSource> action)
foreach (var item in source)
action.Invoke(item);
【讨论】:
当心,RemoveAll
的性能要好于多次调用 Remove
。【参考方案3】:
既然编译器没那么聪明,我们就必须比他聪明。
var foo = new List<int> 1, 2, 3, 4, 5 ;
var bar = new HashSet<int>() 1, 3, 5 ;
foo.RemoveAll(i => bar.Contains(i));
【讨论】:
一个包含 3 个条目的哈希集肯定不会表现得更好,它可能会(我没有测量过)表现更差。 @Sefe 虽然哈希集和数组之间可能存在差异,但我认为这个答案的重点是创建匿名函数的bar
outside 所以那就是它只创建一次,以避免创建开销。
@Sefe 你说的是真的,但我认为 OP 提供了一个简单的例子来说明一点。在现实世界的场景中,列表可能会比这更长。以上是关于List<T>.RemoveAll() 效率/编译器优化的主要内容,如果未能解决你的问题,请参考以下文章