【问题标题】:How to optimize a code using DataTable and Linq?如何使用 DataTable 和 Linq 优化代码?
【发布时间】:2020-03-05 11:17:12
【问题描述】:

我有 2 个数据表。大约有 17000 (table1)100000 (table2) 记录。

需要检查“FooName”字段是否包含“ItemName”。还需要获取 "FooId",然后将 "ItemId""FooId" 添加到 ConcurrentDictionary。

我有这个代码。

DataTable table1;
DataTable table2;           
var table1Select = table1.Select();

ConcurrentDictionary<double, double> compareDictionary = new ConcurrentDictionary<double, double>();

foreach (var item in table1)
{
         var fooItem = from foo in table2.AsEnumerable()
                  where foo.Field<string>("FooName").Contains(item.Field<string>("ItemName"))
                  select foo.Field<double>("FooId");
         if(fooItem != null && fooItem.FirstOrDefault() != 0)
                {
                    compareDictionary.TryAdd(item.Field<double>("ItemId"), fooItem.FirstOrDefault());
                }
}

它运行缓慢(执行任务大约需要 10 分钟)。

我想让它更快。我该如何优化它?

【问题讨论】:

  • ItemName 是一个词还是一个词组?
  • @mtkachenko 这是短语。

标签: c# algorithm linq datatable concurrentdictionary


【解决方案1】:

我看到你可以攻击三个点:

  1. 在字段访问器上抛弃强类型以支持直接强制转换:它会强制取消装箱,而 doubles 是值类型可以完全避免这种情况。 upd 正如 cmets 中所指出的,无论哪种方式,您都不会避免拆箱,但可能会节省一些方法调用开销(这又是有争议的)。这点大概可以忽略
  2. 缓存引用字符串,因此每个外循环只能访问一次
  3. (我认为这是最大的收获)因为您似乎总是首先获得结果 - 在 LINQ 中直接选择 FirstOrDefault() - 不要让它在找到匹配项时枚举整个事情
ConcurrentDictionary<double, double> compareDictionary = new ConcurrentDictionary<double, double>();

foreach (var item in table1)
    {
        var sample = (string)item["ItemName"]; // cache the value before looping through inner collection
        var fooItem = table2.AsEnumerable()
                            .FirstOrDefault(foo => ((string)foo["FooName"]).Contains(sample)); // you seem to always take First item, so you could instruct LINQ to stop after a match is found
        if (fooItem != null && (double)fooItem["FooId"] != 0)
        {
            compareDictionary.TryAdd((double)item["ItemId"], (double)fooItem["FooId"]);
        }
    }

看来,将.FirstOrDefault() 条件应用于LINQ 查询语法将sort of reduce it to method chain syntax 无论如何,所以我会一直选择方法链接,并留给你弄清楚美学

【讨论】:

  • 我不相信这是真的。值类型的DataRow.Field&lt;T&gt; 最终将返回(T)object,它将调用IL unbox.any !T,就像(T)DataRow[fieldname] 一样。但是,它确实消除了一些方法调用开销——当然 JIT 也可能内联它们。
  • @NetMage 我倾向于同意。最初,我快速浏览了反编译的代码,第一个调用是 Unbox ,它提示了结论。然后我又看了看,似乎拆箱是不可避免的。进行强制转换仍然会消除一些 null 和 DBNull 检查似乎。稍后我会更新答案以免误导
【解决方案2】:

如果您愿意为了速度而牺牲内存,从DataTable 转换为您需要的字段,与重复从table2 中提取列数据相比,速度提高了大约6 倍。 (这是对使用 FirstOrDefault 的加速的补充。)

var compareDictionary = new ConcurrentDictionary<double, double>();

var t2e = table2.AsEnumerable().Select(r => (FooName: r.Field<string>("FooName"), FooId: r.Field<double>("FooId"))).ToList();
foreach (var item in table1.AsEnumerable().Select(r => (ItemName: r.Field<string>("ItemName"), ItemId: r.Field<double>("ItemId")))) {
    var firstFooId = t2e.FirstOrDefault(foo => foo.FooName.Contains(item.ItemName)).FooId;

    if (firstFooId != 0.0) {
        compareDictionary.TryAdd(item.ItemId, firstFooId);
    }
}

我正在使用 C# ValueTuples 来避免来自匿名类的引用对象开销。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-23
    • 1970-01-01
    • 2015-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-22
    • 2015-01-15
    相关资源
    最近更新 更多