Как оптимизировать код, используя DataTable и Linq? - PullRequest
0 голосов
/ 05 марта 2020

У меня есть 2 DataTables . Имеется около 17000 (таблица1) и 100000 (таблица2) записей.

Необходимо проверить, содержит ли поле "FooName"" Имя элемента". Также необходимо взять "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 минут).

Я хочу сделать это быстрее. Как я могу это оптимизировать?

Ответы [ 2 ]

1 голос
/ 05 марта 2020

Я вижу три точки, на которые вы можете атаковать:

  1. строгая типизация на средствах доступа к полям в пользу прямого приведения: это вызывает распаковку, которую вы можете полностью избежать, если doubles является типом значения. UPD , как указано в комментариях, вы не избежите распаковка так или иначе, но потенциально может сохранить некоторые накладные расходы вызова метода (что опять же, спорно). Эта точка, вероятно, может быть проигнорирована
  2. ссылочной строкой кэша, так что вы обращаетесь к ней только один раз за внешнюю l oop
  3. (я думаю, что именно здесь самые большие выгоды), так как вы, кажется, всегда принимаете первый результат - выберите FirstOrDefault() прямо в LINQ - не позволяйте ему перечислять все, когда найдено совпадение
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 В любом случае в некотором роде сократит его до синтаксиса цепочки методов , поэтому я бы предпочел, чтобы метод цеплялся по цепочкам, и предоставил вам возможность выяснить эстетику

0 голосов
/ 06 марта 2020

Если вы готовы пожертвовать памятью ради скорости, преобразование из DataTable в нужные вам поля дает примерно 6-кратное ускорение по сравнению с многократным извлечением данных столбца из table2. (Это в дополнение к ускорению использования 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# ValueTuple с, чтобы избежать накладных расходов на ссылочный объект от анонимных классов.

...