为什么 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 的反射伏都教、锁定和转换开销
我做了两个测试。
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 的反射伏都教、锁定和转换开销