开发者

Why is casting arrays (vectors) so slow?

开发者 https://www.devze.com 2023-04-09 07:26 出处:网络
I was under the impression that in .NET casting (not converting) is very cheap and fast. However, this does not seem to be the case for array. I\'m trying to do a very simple cast here, take a T1[] an

I was under the impression that in .NET casting (not converting) is very cheap and fast. However, this does not seem to be the case for array. I'm trying to do a very simple cast here, take a T1[] and cast as T2[]. where T1:T2.

There are 3 ways to do this and I'm calling them the following::

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

And I created methods to do this, unfortunately, C# seems to create some rather strange code depending on if this is generic or not. (If its generic DropCasting uses the castclass operator. And in both cases refuse to emit an 'as' operator when T1:T2.

Anyway, I wrote some Dynamic methods and I tested it to some surprising results (string[]=>object[]):

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

Dropcasting was ~18 times faster than either of the cast operators. Why is casting so slow for arrays? For normal objects like string=>object, the difference was much less severe.

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

Benchmark code below:

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 metho开发者_StackOverflowd.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();
        }
    }
}

Edit before anyone asks the same hold true for things like int[]->uint[] which the clr specs should be cast without conversion.


Because you are casting arrays.

The difference between the 3 snippets of IL code is that the latter two add an IsInst and a CastClass operation. Very little is known about the types, so the CLR has to check whether it is a valid operation. This takes time.

The slight difference between CastClass and IsInst can be explained by the fact that CastClass does a null check first and succeeds right away if the argument is null.

I suspect that the slowdown is because you are casting between arrays. A lot more work may need to be done to make sure that an array cast is valid. It may be necessary to look at each element to see if it can be cast to the target element type. So I would guess that, rather than do all this in 'inline' machine code, the JIT emits a call to a validation function.

In fact, if you run a performance analysis, you can see that is indeed what is happening. Almost 90% of the time is spent in a function called "JIT_ChkCastArray".


It makes sense to me that casting would be (almost) exactly as expensive as using the as operator. In both scenarios, a runtime check on the type of the object must be made, and it must be determined whether it is compatible with the target type. The check is required to allow the cast operation to throw an InvalidCastException if necessary.

To put it another way, the as operator is a cast operation -- it just also has the virtue of allowing the cast to fail without throwing an exception (by returning null). This could also be done with a combination of the is operator and a cast, but that would double the workload.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号