String.Where相对较差的表现

我有两个方法接受一个字符串并删除任何“无效”字符(哈希集中包含的字符)。 一种方法使用Linq.Where,另一种方法使用循环w / char数组。

Linq方法需要几乎两倍的长度(208756.9滴答)与循环(108688.2滴答)

LINQ:

string Linq(string field) { var c = field.Where(p => !hashChar.Contains(p)); return new string(c.ToArray()); } 

环:

  string CharArray(string field) { char[] c = new char[field.Length]; int count = 0; for (int i = 0; i < field.Length; i++) if (!hashChar.Contains(field[i])) { c[count] = field[i]; count++; } if (count == 0) return field; char[] f = new char[count]; Buffer.BlockCopy(c, 0, f, 0, count * sizeof(char)); return new string(f); } 

我的期望是LINQ会击败或至少可以与循环方法相媲美。 循环方法甚至没有优化。 我必须在这里遗漏一些东西。

Linq.Where是如何在幕后工作的,为什么它会失去我的方法?

如果Mono中ToArray的源代码是任何指示,那么您的实现将获胜,因为它执行的分配更少(向下滚动到第2874行以查看该方法)。

与LINQ的许多方法一样, ToArray方法包含用于集合和其他可枚举的单独代码路径:

 TSource[] array; var collection = source as ICollection; if (collection != null) { ... return array; } 

在您的情况下,不采用此分支,因此代码进入此循环:

 int pos = 0; array = EmptyOf.Instance; foreach (var element in source) { if (pos == array.Length) { if (pos == 0) array = new TSource [4]; else // If the number of returned character is significant, // this method will be called multiple times Array.Resize (ref array, pos * 2); } array[pos++] = element; } if (pos != array.Length) Array.Resize (ref array, pos); return array; 

如您所见,LINQ的版本可能会多次分配和重新分配数组。 另一方面,您的实现只进行两次分配 – 最大尺寸的前期分配,以及复制数据的最后一次分配。 这就是为什么你的代码更快。