【问题标题】:Reflection is too slow while deserialising JSON strings into .NET objects将 JSON 字符串反序列化为 .NET 对象时反射太慢
【发布时间】:2012-07-27 16:13:41
【问题描述】:

我在使用 C# 中的 System.Reflection 时遇到了一些问题。我正在从数据库中提取数据并在 JSON 字符串中检索该数据。我已经使用反射实现了将 JSON 中的数据处理到我自己声明的对象中的实现。但是,由于我通常会得到一个包含 50 到 100 个对象的数组的 JSON 字符串,因此我的程序运行速度非常慢,因为我使用了反射循环。

我听说反射很慢,但不应该这么慢。我觉得我的实现有些不对劲,因为我有一个不同的项目,我在其中使用 JSON.NET 序列化程序并用反射实例化我的对象有点不同,反射在相同的输出上运行得很好(不到一秒),而我的慢程序需要50 个对象大约需要 10 秒。

以下是我用来存储数据的类

class DC_Host
{
    public string name;

    public void printProperties()
    {
        //Prints all properties of a class usign reflection
        //Doesn't really matter, since I'm not usign this for processing
    }
}

class Host : DC_Host
{
    public string asset_tag;
    public string assigned;
    public string assigned_to;
    public string attributes;
    public bool? can_print;
    public string category;
    public bool? cd_rom;
    public int? cd_speed;
    public string change_control;
    public string chassis_type;
    //And some more properties (around 70 - 80 fields in total)

您将在下面找到我将信息处理成存储在列表中的对象的方法。 JSON 数据存储在一个字典中,该字典包含另一个字典,用于在 JSON 输入中定义的每个数组对象。反序列化 JSON 会在几毫秒内完成,因此应该不会有问题。

public List<DC_Host> readJSONTtoHost(ref Dictionary<string, dynamic> json)
{
    bool array = isContainer();

    List<DC_Host> hosts = new List<DC_Host>();

    //Do different processing on objects depending on table type (array/single)
    if (array)
    {
        foreach (Dictionary<string, dynamic> obj in json[json.First().Key])
        {
            hosts.Add(reflectToObject(obj));
        }
    }
    else
    {
        hosts.Add(reflectToObject(json[json.First().Key]));
    }

    return hosts;
}

private DC_Host reflectToObject(Dictionary<string,dynamic> obj)
{
    Host h = new Host();

    FieldInfo[] fields = h.GetType().GetFields();

    foreach (FieldInfo f in fields)
    {
        Object value = null;

        /* IF there are values that are not in the dictionairy or where wrong conversion is
         * utilised the values will not be processed and therefore not inserted into the 
         * host object or just ignored. On a later stage I might post specific error messages
         * in the Catch module. */

        /* TODO : Optimize and find out why this is soo slow */
        try
        {
            value = obj[convTable[f.Name]];
        }
        catch { }

        if (value == null)
        {
            f.SetValue(h, null);
            continue;
        }
        // Het systeem werkt met list containers, MAAAR dan mogen er geen losse values zijn dus dit hangt
        // zeer sterk af van de implementatie van Service Now.
        if (f.FieldType == typeof(List<int?>)) //Arrays voor strings,ints en bools dus nog definieren 
        {
            int count = obj[convTable[f.Name]].Count;
            List<int?> temp = new List<int?>();
            for (int i = 0; i < count; i++)
            {
                temp.Add(obj[convTable[f.Name]][i]);
                f.SetValue(h, temp);
            }
        }
        else if (f.FieldType == typeof(int?))
            f.SetValue(h, int.Parse((string)value));
        else if (f.FieldType == typeof(bool?))
            f.SetValue(h, bool.Parse((string)value));
        else
            f.SetValue(h, (string)value);
    }

    Console.WriteLine("Processed " + h.name);

    return h;
}

我不确定 JSON.NET 在后台使用反射的实现是什么,但我假设他们使用我缺少的东西来优化他们的反射。

【问题讨论】:

  • 为了获得最佳性能,需要生成新代码来填充数据。
  • 但即使没有这个,充分利用字典和缓存应该可以大大加快您的代码速度。
  • 然后摆脱那个空渔获物。 TryGetValueftw
  • 您可以在运行时发出新方法,将数据填充到对象中。所以你只需要反射来构建这些方法(Reflection.EmitExpression.Compile,...),而不是水合对象。
  • @JoeyDewd,尝试在不附加调试器的情况下运行慢速版本。你可能会对它的表现感到惊讶。杀死您的应用程序的是发生的许多异常。处理异常很昂贵,如果附加了调试器,则更是如此。经验教训:永远不要将异常用于常见的程序流控制。

标签: c# json optimization serialization reflection


【解决方案1】:

基本上,像这样的高性能代码倾向于广泛使用元编程;很多ILGenerator 等(或者Expression / CodeDom,如果你觉得这很可怕)。 PetaPoco 今天早些时候展示了一个类似的例子:prevent DynamicMethod VerificationException - operation could destabilize the runtime

您还可以查看其他序列化引擎的代码,例如 protobuf-net,它具有大量的元编程。

如果你不想相当那么远,你可以看看FastMember,它会为你处理疯狂的事情,所以你只需要担心对象/成员名称/值。

【讨论】:

  • 是的,老实说,我对反射很陌生,我知道有一些方法可以使用 IL 对 LowLevel 进行编程以获得最佳结果,甚至在运行时创建动态类,但现在我正在尝试避免这些路径,因为它们目前对我来说太难了。我已经通过使用 TryGetvalue 删除 Try Catch 语句解决了这个问题(尽管我不知道为什么这似乎可以解决性能问题)。不过,我将研究 FastMember 以解决未来的优化问题:)。谢谢
【解决方案2】:

对于遇到这篇文章的人,我将在此处发布我对我的问题的解决方案。 这个问题与反思无关。使用反射可以提高速度,例如 CodesInChaos 和 Marc Gravell 提到,Marc 甚至为没有太多低级反射经验的人创建了一个非常有用的库 (FastMember)。

然而,该解决方案与反射本身无关。我有一个 Try Catch 语句来评估我的字典中是否存在值。使用 try catch 语句来处理程序流不是一个好主意。处理异常对性能很重要,尤其是在运行调试器时,Try Catch 语句会严重影响性能。

//New implementation, use TryGetValue from Dictionary to check for excising values.
dynamic value = null;
obj.TryGetValue(convTable[f.Name], out value);

由于我省略了 TryCatch 语句,我的程序现在运行得非常好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-16
    • 2014-01-04
    • 2020-02-03
    • 1970-01-01
    • 2012-04-24
    • 1970-01-01
    相关资源
    最近更新 更多