【问题标题】:DynamicObject and TrySetMember performance vs ExpandoObject performanceDynamicObject 和 TrySetMember 性能与 ExpandoObject 性能
【发布时间】:2012-03-02 22:48:47
【问题描述】:

我正在使用 DynamicObject 的自定义实现,它非常适合我的应用程序,除了我遇到了一些性能问题。动态会产生一些性能开销,但我看到即使使用 ExpandoObject 也会有显着的(读取:数量级)性能损失。

我不能使用 ExpandoObject 的原因是我想覆盖它的一些行为。我将问题归结为下面的一个非常简单的示例。

我的自定义 ExpandoObject 代码如下(简化为刚好足以展示问题的代码)--

public class SuperExpando : DynamicObject
{
    public Dictionary<string, object> dictionary = new Dictionary<string, object>();
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        dictionary[binder.Name] = value;
        return true;
    }
}

public dynamic m = new SuperExpando();

当我直接在 DynamicObject 的字典中设置值时 (iemdictionary["keyname"] = 500),然后我看到类似于 ExpandoObject 的性能,即在亚毫秒时间内设置字典中键的值.当我使用 TrySetMember 覆盖(即 m.keyname = 500)时,我看到每个键值集的性能下降多达 30 毫秒 - 50 毫秒。当写入很多键时,这显然会成为一个问题。即使我一遍又一遍地写入同一个键,通过 TrySetMember 访问它也需要相同的时间。

我真正的性能问题似乎与我使用动态的事实无关,因为它使用 TrySetMember 覆盖。对于踢球,我什至注释掉了

dictionary[binder.Name] = value;

在 TrySetMember 方法中,只留下一个“return true;”,性能是一样的。

如果我在我的 SuperExpando 类中添加如下内容 --

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
    if (dictionary.ContainsKey(binder.Name))
    {
        result = dictionary[binder.Name];
        return true;
    }
    return false; 
}

通过 TryGetMember 访问(读取)变量的性能问题是相同的,而直接读取字典提供了合理的性能。

有什么想法吗?

-BJ奎因

编辑:这是完整的示例代码。只需创建一个表单并在其上放置一个运行 go_Click 事件的按钮并将您的项目设置为控制台应用程序。对我来说,在 ExpandoObject 中设置所有 50 个键需要大约 30 毫秒,而 SuperExpando 至少需要大约 750 毫秒。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Dynamic;

namespace test
{
    public partial class ExpandoTest : Form
    {
        public ExpandoTest()
        {
            InitializeComponent();
        }

        public class SuperExpando : DynamicObject
        {
            public Dictionary<string, object> dictionary = new Dictionary<string, object>();

            public override bool TrySetMember(SetMemberBinder binder, object value)
            {
                //dictionary[binder.Name] = value;
                return true;
            }
        }

        DateTime lasttime = DateTime.Now;

        public void outputtime(string label = "")
        {
            TimeSpan elapsedtime = DateTime.Now - lasttime;
            Double elapsedms = elapsedtime.TotalMilliseconds;
            Console.WriteLine(label + " : " + elapsedms.ToString());
            lasttime = DateTime.Now;
        }

        private void go_Click(object sender, EventArgs e)
        {
            outputtime("Time spent waiting on user");
            dynamic se = new SuperExpando();
            outputtime("Declared SuperExpando");
            se.test120 = 5;
            se.test121 = 5;
            se.test122 = 5;
            se.test123 = 5;
            se.test124 = 5;
            se.test125 = 5;
            se.test126 = 5;
            se.test127 = 5;
            se.test128 = 5;
            se.test129 = 5;
            se.test130 = 5;
            se.test131 = 5;
            se.test132 = 5;
            se.test133 = 5;
            se.test134 = 5;
            se.test135 = 5;
            se.test136 = 5;
            se.test137 = 5;
            se.test138 = 5;
            se.test139 = 5;
            se.test140 = 5;
            se.test141 = 5;
            se.test142 = 5;
            se.test143 = 5;
            se.test144 = 5;
            se.test145 = 5;
            se.test146 = 5;
            se.test147 = 5;
            se.test148 = 5;
            se.test149 = 5;
            se.test150 = 5;
            se.test151 = 5;
            se.test152 = 5;
            se.test153 = 5;
            se.test154 = 5;
            se.test155 = 5;
            se.test156 = 5;
            se.test157 = 5;
            se.test158 = 5;
            se.test159 = 5;
            se.test160 = 5;
            se.test161 = 5;
            se.test162 = 5;
            se.test163 = 5;
            se.test164 = 5;
            se.test165 = 5;
            se.test166 = 5;
            se.test167 = 5;
            se.test168 = 5;
            se.test169 = 5;
            outputtime("Time to Run SuperExpando, set 50 test key/value pairs -- (not even setting values, just returning true from TrySetMember!)");

            dynamic eo = new ExpandoObject();
            outputtime("Declared ExpandoObject");
            eo.test120 = 5;
            eo.test121 = 5;
            eo.test122 = 5;
            eo.test123 = 5;
            eo.test124 = 5;
            eo.test125 = 5;
            eo.test126 = 5;
            eo.test127 = 5;
            eo.test128 = 5;
            eo.test129 = 5;
            eo.test130 = 5;
            eo.test131 = 5;
            eo.test132 = 5;
            eo.test133 = 5;
            eo.test134 = 5;
            eo.test135 = 5;
            eo.test136 = 5;
            eo.test137 = 5;
            eo.test138 = 5;
            eo.test139 = 5;
            eo.test140 = 5;
            eo.test141 = 5;
            eo.test142 = 5;
            eo.test143 = 5;
            eo.test144 = 5;
            eo.test145 = 5;
            eo.test146 = 5;
            eo.test147 = 5;
            eo.test148 = 5;
            eo.test149 = 5;
            eo.test150 = 5;
            eo.test151 = 5;
            eo.test152 = 5;
            eo.test153 = 5;
            eo.test154 = 5;
            eo.test155 = 5;
            eo.test156 = 5;
            eo.test157 = 5;
            eo.test158 = 5;
            eo.test159 = 5;
            eo.test160 = 5;
            eo.test161 = 5;
            eo.test162 = 5;
            eo.test163 = 5;
            eo.test164 = 5;
            eo.test165 = 5;
            eo.test166 = 5;
            eo.test167 = 5;
            eo.test168 = 5;
            eo.test169 = 5;
            outputtime("Time to Run ExpandoObject, set 50 test key/value pairs");
        }
    }
}

【问题讨论】:

  • 真的吗? 30ms这个?这是非常缓慢的。您确定它与您操作对象的方式没有任何关系吗?您如何在代码中使用您的 SuperExpando?
  • 是的,它非常缓慢。不足以注意到你是否只做一两个,但如果你做了几十/百/千,那么它会变得非常痛苦。除了上面显示的代码之外,我真的没有做任何事情。如上所述,我声明了 SuperExpando 的实例(public dynamic m = new SuperExpando();),我通过执行大量“m.var1 = 500;”来重现该问题一行一行地复制粘贴。
  • 您是否在发布版本中分析了您的代码?我已经看到一些令人痛苦的缓慢调试代码在发布模式下充分执行。祝你好运!
  • ExpandoObject 不会直接从DynamicObject 继承,因此您不会将苹果与苹果进行比较。如果您想要一个真正的测试,请尝试让SuperExpando 实现IDynamicMetaObjectProvider。即使这是真的,你正在编写一个 UI 应用程序......几毫秒有什么区别?
  • 您是否尝试在不附加到 Visual Studio 的情况下运行发行版 EXE(CTRL-F5 而不是 F5)?它会对性能产生巨大影响。

标签: c# dynamic expandoobject dynamicobject


【解决方案1】:

首先,您不应该这样测量时间。 DateTime.Now 不精确到毫秒。您应该为此使用Stopwatch

其次,在.Net 中,尤其是在处理dynamic 时,顺序很重要。这是因为 CLR 和 DLR 第一次必须计算一些东西,但第二次可以从缓存中检索。

第三,在我的测试中,我当然没有看到 750 毫秒,或任何接近的时间。

如果我先运行SuperExpando,然后两次测试,我会得到这样的时间:

SuperExpando: 50,7736 ms
EpandoObject: 27,786 ms
SuperExpando: 0,0285 ms
EpandoObject: 0,0373 ms

所以,SuperExpando 速度较慢,差异可能很大,但这只是第一次。当你再次运行相同类型的相同代码时,速度会快得多。

当我们颠倒顺序时会发生什么?

EpandoObject: 33,3107 ms
SuperExpando: 43,7383 ms
EpandoObject: 0,0348 ms
SuperExpando: 0,0186 ms

SuperExpando 仍然较慢,但现在差异较小。同样,两者的第二次运行都快了几个数量级。

【讨论】:

  • 谢谢,现在使用秒表。但是,无论我运行的顺序如何,我仍然看到 750ms+。另一个有趣的事情是,如果我增加对 TrySetMember 的调用次数(通过添加更多行,例如“se.test120 = 5”),即使我只是复制并粘贴了 50 个调用,所需的时间也会线性增加。已经在做(因此为每个键调用两次 TrySetMember)。此外,我第二次按下按钮再次运行代码时,时间几乎降为零。这很有帮助,但如果第一次运行时间仍然是线性的并且这么慢,那仍然是一个主要问题。
  • 哦,还有一件事——如果我确保第一次运行的代码与第二次运行的代码不完全相同(设置一个计数器并复制和粘贴相同的代码多次并让计数器递增并每次运行相同代码的不同副本),然后每次运行的代码有不同之处时,我都会得到缓慢的“第一次运行”时间。这仍然只是我的“SuperExpando”和 TrySetMember 的问题。 ExpandoObject 在所有情况下都表现良好。
  • 经过更多测试,我确定我的问题是综合问题。首先,正如 sgorozco 所建议的,调试构建非常缓慢。执行 Ctrl+F5 会分离调试器并运行得更快。此外,正如 svick 建议的那样,第一次运行速度较慢,之后它会变得更快,但是当没有附加调试器时,第一次与第二次运行速度并不那么明显。这在开发过程中很烦人,但在发布模式下并不是什么大问题。我在发布模式下的真正问题完全不同,但调试模式下的这个问题让我分心了真正的问题。
  • 那么发布模式的真正问题是什么,所以其他人可以避免同样的问题?
猜你喜欢
  • 2011-04-03
  • 1970-01-01
  • 2011-03-11
  • 1970-01-01
  • 2019-09-01
  • 2018-12-03
  • 2018-08-19
  • 1970-01-01
  • 2016-09-11
相关资源
最近更新 更多