为什么 ExpandoObject 比 Dictionary 慢很多?

why is ExpandoObject much slower than Dictionary?

我做了两个测试。

1-添加100k个元素需要多长时间

2- 他可以在 10 秒内使用 100k 个元素进行多少次搜索。

我的结果是这些

ExpandoObject 添加计数器 97075

字典添加计数器 35

ExpandoObject 搜索计数器 2396

词典搜索计数器 1957637

结论:

添加 ExpandoObject 元素的速度慢了 2773 倍。

搜索 ExpandoObject 元素的速度慢了 817 倍。

为什么 ExpandoObject 比 Dictionary 慢很多?

using System;
using System.Dynamic;
using System.Collections.Generic;
using System.Threading;

namespace c_sharp_benchmark

{
    class Program
    {
        static void Main(string[] args)
        {
            BenchmarkExpandoObjectAdd();
            BenchmarkDictionaryAdd();
            BenchmarkExpandoObjectSearch();
            BenchmarkDictionarySearch();
        }
        static void BenchmarkExpandoObjectAdd()
        {
            dynamic exp = new ExpandoObject();
            var expid = (IDictionary<string, object>)exp;
            Random rnd = new Random();
            long old = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            int i;
            for (i = 0; i < 100000; i++)
            {
                expid.Add("Prop" + i, i);
            }
            Console.WriteLine("ExpandoObject Add counter " + (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - old));
        }
        static void BenchmarkDictionaryAdd()
        {
            Dictionary<string, object> dic = new Dictionary<string, object>();
            Random rnd = new Random();


            long old = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            int i;
            for (i = 0; i < 100000; i++)
            {
                dic.Add("Prop" + i, i);
            }
            Console.WriteLine("Dictionary Add counter " + (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - old));
        }

        static void BenchmarkExpandoObjectSearch()
        {
            dynamic exp = new ExpandoObject();
            var expid = (IDictionary<string, object>)exp;
            Random rnd = new Random();
            int i;
            for (i = 0; i < 100000; i++)
            {
                expid.Add("Prop" + i, i);
            }
            int auxval;
            long when_stop = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + 10000;
            int counter = 0;
            while (when_stop > DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())
            {
                ++counter;
                auxval = (int)expid["Prop" + rnd.Next(100000)];
            }
            Console.WriteLine("ExpandoObject Search counter " + counter / 10);
        }
        static void BenchmarkDictionarySearch()
        {
            Dictionary<string, object> dic = new Dictionary<string, object>();
            Random rnd = new Random();
            int i;
            for (i = 0; i < 100000; i++)
            {
                dic.Add("Prop" + i, i);
            }
            int auxval;
            long when_stop = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + 10000;
            int counter = 0;
            while (when_stop > DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())
            {
                ++counter;
                auxval = (int)dic["Prop" + rnd.Next(100000)];
            }
            Console.WriteLine("Dictionary Search counter " + counter / 10);

        }
    }
}

主要区别在于 Expando 对象执行线性搜索 o(n) 对于每个 TryGetValue / TrySetValue。

而真正的词典使用 GetHashCode() 并在非常小的存储桶中搜索项目。

当您在测试中创建大型 ExpandoObject 时,这一点尤其明显。

这是ExpandoObject的TryGetValue和TrySetValue都使用的源代码:

internal int GetValueIndexCaseSensitive(string name)
{
    for (int i = 0; i < _keys.Length; i++)
    {
        if (string.Equals(_keys[i], name, StringComparison.Ordinal))
        {
            return i;
        }
    }
    return -1;
}

或者查看 BindGetOrInvokeMember

的代码
private DynamicMetaObject BindGetOrInvokeMember(DynamicMetaObjectBinder binder, string name, bool ignoreCase, DynamicMetaObject fallback, Func<DynamicMetaObject, DynamicMetaObject> fallbackInvoke)
{
    ExpandoClass @class = Value.Class;
    int valueIndex = @class.GetValueIndex(name, ignoreCase, Value);
    ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "value");
    Expression test = Expression.Call(typeof(RuntimeOps).GetMethod("ExpandoTryGetValue"), GetLimitedSelf(), Expression.Constant(@class, typeof(object)), Expression.Constant(valueIndex), Expression.Constant(name), Expression.Constant(ignoreCase), parameterExpression);
    DynamicMetaObject dynamicMetaObject = new DynamicMetaObject(parameterExpression, BindingRestrictions.Empty);
    if (fallbackInvoke != null)
    {
        dynamicMetaObject = fallbackInvoke(dynamicMetaObject);
    }
    dynamicMetaObject = new DynamicMetaObject(Expression.Block(new ParameterExpression[1]
    {
        parameterExpression
    }, Expression.Condition(test, dynamicMetaObject.Expression, fallback.Expression, typeof(object))), dynamicMetaObject.Restrictions.Merge(fallback.Restrictions));
    return AddDynamicTestAndDefer(binder, Value.Class, null, dynamicMetaObject);
}

此外还有一些 ExpandoObject 的反射伏都教、锁定和转换开销