好吧,以我之前的问题为基础:Generically checking for null that won't box nullables on a non-constrained type.
一位用户建议对类和struct设置约束,我还实现了专门针对这三种类型的UnboxT
模式并将该逻辑存储在委托中。还告诉我尝试使用OfType<T>
。
这是我的方法:
public static class Check<T>
{
public static readonly Func<T,bool> IfNull = CreateIfNullDelegate();
private static bool AlwaysFalse(T obj)
{
return false;
}
private static bool ForRefType(T obj)
{
return object.ReferenceEquals(obj, null);
}
private static bool ForNullable<Tu>(Tu? obj) where Tu:struct
{
return !obj.HasValue;
}
private static Func<T,bool> CreateIfNullDelegate()
{
if (!typeof(T).IsValueType)
return ForRefType;
else
{
Type underlying;
if ((underlying = Nullable.GetUnderlyingType(typeof(T))) != null)
{
return Delegate.CreateDelegate(
typeof(Func<T,bool>),
typeof(Check<T>)
.GetMethod("ForNullable",BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(underlying)
) as Func<T,bool>;
}
else
{
return AlwaysFalse;
}
}
}
}
public static int CountNonNull<T>(this IEnumerable<T> enumerable) where T:class
{
return enumerable.Count(x=>Object.ReferenceEquals(x,null));
}
public static int CountNonNull<T>(this IEnumerable<T?> enumerable) where T : struct
{
return enumerable.Count(x=>x!=null);
}
public static int CountNonNull3<T>(this IEnumerable<T> enumerable)
{
return enumerable.OfType<T>().Count();
}
public static int CountNonNull2<T>(this IEnumerable<T> enumerable)
{
return enumerable.Count(Check<T>.IfNull);
}
public static void Profile(this Action action, string name, int times = 1 * 1000 * 1000, bool display = true)
{
for (int i = 0; i < 100; i++)
{
action();
}
var _stopwatch = Stopwatch.StartNew();
for (int i = 0; i < times; i++)
{
action();
}
_stopwatch.Stop();
if (display)
{
Console.WriteLine("{0}: did {1} iterations in {2} ms", name, times, _stopwatch.ElapsedMilliseconds);
}
}
这是我的测试集:
var ints = Enumerable.Range(0,10).ToArray();
var nullableInts = Array.ConvertAll(ints,x=>x as int?);
var strings = Array.ConvertAll(ints,x => x.ToString());
Profile(() => nullableInts.CountNonNull(), "int?");
Profile(() => strings.CountNonNull(), "strings");
Profile(() => nullableInts.CountNonNull2(), "int? 2");
Profile(() => strings.CountNonNull2(), "strings 2");
Profile(() => nullableInts.CountNonNull3(), "int? 3");
Profile(() => strings.CountNonNull3(), "strings 3");
这是我的结果:
int?: did 1000000 iterations in 188 ms
strings: did 1000000 iterations in 2346 ms
int? 2: did 1000000 iterations in 180 ms
strings 2: did 1000000 iterations in 147 ms
int? 3: did 1000000 iterations in 4120 ms
strings 3: did 1000000 iterations in 859 ms
OfType<T>
变慢意味着必须先执行is
然后进行强制转换。这意味着它必须在集合中执行两个循环才能确定其结果(尽管很难相信int?
时间)。但是第一种和第二种方法都执行相同的谓词。为什么第一个在字符串上的表现如此缓慢,而第二个却像冠军呢?
编辑:额外的疯狂:
在试用版中添加另一种方法:
public static int CountNonNull4(this System.Collections.IEnumerable enumerable)
{
return enumerable.Cast<object>().Count(x => object.ReferenceEquals(x, null));
}
此版本产生:
strings 4: did 1000000 iterations in 677 ms
这几乎没有任何意义。到底是怎么回事?
最佳答案
您意识到StopWatch并未考虑实际的线程活动,对吗?这相当于安排您的通勤时间在早上。每天都有很多可能会影响您进度的事情(一天的光线会阻止第二天的交通状况,交通堵塞等)。
这个比喻在计算机中很好地适用。操作系统可能中断了您的线程以执行其他操作,您的线程可能不得不等待页面文件操作(扩展,交换)等。尝试将每种算法运行2或3次并取平均值。另外,请确保您的应用程序正在FullTrust中运行,这会绕过所有安全性(但不是运行时完整性)权限检查。最后,如果您可以通过某种方式对这个事件探查器进行多线程处理,则可以从CPU获取有关算法所需的实际周期数的指标,而这些指标与线程调度延迟无关。
关于c# - LINQ to对象慢度。现在我只是完全困惑,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/4000178/