为什么投射数组(向量)如此之慢?

时间:2011-09-28 20:45:10

标签: c# arrays

我的印象是在.NET中(非转换)非常便宜且快速。但是,这似乎不是阵列的情况。我想在这里做一个非常简单的演员,拿一个T1 []并演员为T2 []。其中T1:T2。

有3种方法可以做到这一点,我称之为以下::

DropCasting: T2[] array2 = array;
CastClass: (T2[])array;
IsInst: array as T2[]; 

我创建了这样做的方法,不幸的是,C#似乎创建了一些相当奇怪的代码,具体取决于它是否是通用的。 (如果它的通用DropCasting使用了castclass运算符。在两种情况下,当T1:T2时拒绝发出'as'运算符。

无论如何,我写了一些动态方法,我测试了一些令人惊讶的结果(string [] => object []):

DropCast :    223ms
IsInst   :   3648ms
CastClass:   3732ms

Dropcast比任何一个演员都快~18倍。为什么阵列的播放速度如此之慢? 对于像string =>对象这样的普通对象,差异要小得多。

DropCast :    386ms
IsInst   :    611ms
CastClass:    519ms

以下基准代码:

class Program
{
    static readonly String[] strings = Enumerable.Range(0, 10).Select(x => x.ToString()).ToArray();

    static Func<string[], object[]> Dropcast = new Func<Func<string[], object[]>>(
        () =>
        {
            var method = new DynamicMethod("DropCast", typeof(object[]), new[] { typeof(object), typeof(string[]) },true);
            var ilgen = method.GetILGenerator();
            ilgen.Emit(OpCodes.Ldarg_1);
            ilgen.Emit(OpCodes.Ret);
            return method.CreateDelegate(typeof(Func<string[], object[]>)) as Func<string[], object[]>;
        })();
    static Func<string[], object[]> CastClass = new Func<Func<string[], object[]>>(
        () =>
        {
            var method = new DynamicMethod("CastClass", typeof(object[]), new[] { typeof(object), typeof(string[]) },true);
            var ilgen = method.GetILGenerator();
            ilgen.Emit(OpCodes.Ldarg_1);
            ilgen.Emit(OpCodes.Castclass, typeof(object[]));
            ilgen.Emit(OpCodes.Ret);
            return method.CreateDelegate(typeof(Func<string[], object[]>)) as Func<string[], object[]>;
        })();

    static Func<string[], object[]> IsInst = new Func<Func<string[], object[]>>(
        () =>
        {
            var method = new DynamicMethod("IsInst", typeof(object[]), new[] { typeof(object), typeof(string[]) },true);
            var ilgen = method.GetILGenerator();
            ilgen.Emit(OpCodes.Ldarg_1);
            ilgen.Emit(OpCodes.Isinst, typeof(object[]));
            ilgen.Emit(OpCodes.Ret);
            return method.CreateDelegate(typeof(Func<string[], object[]>)) as Func<string[], object[]>;
        })();

    static Func<string[], object[]>[] Tests = new Func<string[], object[]>[]{
        Dropcast,
        IsInst,
        CastClass
    };
    static void Main(string[] args)
    {
        int maxMethodLength = Tests.Select(x => GetMethodName(x.Method).Length).Max();
        RunTests(1, false, maxMethodLength);
        RunTests(100000000, true, maxMethodLength);
    }

    static string GetMethodName(MethodInfo method)
    {
        return method.IsGenericMethod ?
        string.Format(@"{0}<{1}>", method.Name, string.Join<Type>(",", method.GetGenericArguments())) : method.Name;
    }

    static void RunTests(int count, bool displayResults, int maxLength)
    {
        foreach (var action in Tests)
        {
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < count; i++)
            {
                action(strings);
            }
            sw.Stop();
            if (displayResults)
            {
                Console.WriteLine("{0}: {1}ms", GetMethodName(action.Method).PadRight(maxLength),
                ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
            }
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }
    }
}

编辑之前,任何人都要求int [] - &gt; uint []这样的事情,其中​​clr规范应该在没有转换的情况下进行转换。

2 个答案:

答案 0 :(得分:0)

对我而言,使用as运算符(几乎)与使用InvalidCastException运算符一样昂贵。在这两种情况下,都必须对对象的类型进行运行时检查,并且必须确定它是否与目标类型兼容。需要进行检查以允许强制转换操作在必要时抛出as

换句话说,is运算符一个强制转换操作 - 它还具有允许强制转换失败而不抛出异常(通过返回null)的优点。这也可以通过{{1}}运算符和强制转换的组合来完成,但这会使工作量加倍。

答案 1 :(得分:0)

因为你正在构建数组。

IL代码的3个片段之间的区别在于后两个添加了IsInst和CastClass操作。关于类型知之甚少,因此CLR必须检查它是否是有效的操作。这需要时间。

CastClass和IsInst之间的细微差别可以解释为CastClass首先进行空检查并且如果参数为null则立即成功。

我怀疑减速是因为你在阵列之间进行投射。可能需要做更多的工作才能确保数组转换有效。可能需要查看每个元素以查看是否可以将其强制转换为目标元素类型。所以我猜想,JIT不会在“内联”机器代码中执行所有操作,而是会发出对验证函数的调用。

事实上,如果您运行性能分析,您可以看到确实发生了什么。几乎90%的时间花在一个名为“JIT_ChkCastArray”的函数上。