Как ускорить LINQ ГДЕ? - PullRequest
       1

Как ускорить LINQ ГДЕ?

0 голосов
/ 15 февраля 2019

Я запустил профилировщик в моем приложении .NET winforms (скомпилировано с .NET 4.7.1), и он указывает на следующую функцию, которая потребляет 73% процессорного времени моего приложения, что кажется слишком большим для простогослужебная функция:

public static bool DoesRecordExist(string keyColumn1, string keyColumn2, string keyColumn3,
        string keyValue1, string keyValue2, string keyValue3, DataTable dt)
{
    if (dt != null && dt.Rows.Count > 0) {
        bool exists = dt.AsEnumerable()
            .Where(r =>
                string.Equals(SafeTrim(r[keyColumn1]), keyValue1, StringComparison.CurrentCultureIgnoreCase) &&
                string.Equals(SafeTrim(r[keyColumn2]), keyValue2, StringComparison.CurrentCultureIgnoreCase) &&
                string.Equals(SafeTrim(r[keyColumn3]), keyValue3, StringComparison.CurrentCultureIgnoreCase)
            )
            .Any();
        return exists;
    } else {
        return false;
    }
}

Цель этой функции - передать имена некоторых ключевых столбцов и соответствующие им значения ключей и проверить, существует ли какая-либо соответствующая запись в памяти c # DataTable.

Мое приложение обрабатывает сотни тысяч записей, и для каждой записи эта функция должна вызываться несколько раз.Приложение выполняет много вставок, и перед любой вставкой оно должно проверить, существует ли эта запись в базе данных.Я подумал, что проверка в памяти по DataTable будет намного быстрее, чем каждый раз возвращаться к физической базе данных, поэтому я делаю эту проверку в памяти.Каждый раз, когда я делаю вставку в базу данных, я делаю соответствующую вставку в DataTable, чтобы последующие проверки того, существует ли запись, будут точными.

Итак, на мой вопрос: Есть либолее быстрый подход? (я не думаю, что смогу избежать проверки на наличие записей каждый раз, иначе у меня останутся дублирующие вставки и нарушения ключа.)

РЕДАКТИРОВАТЬ # 1 В дополнение к попыткам поступивших предложений, которые я сейчас пытаюсь сделать, мне пришло в голову, что, возможно, мне также следует выполнить .AsEnumerable() только один раз и передать EnumerableRowCollection<DataRow> вместо DataTable.Как вы думаете, это поможет?

РЕДАКТИРОВАТЬ # 2 Я только что провел контролируемый тест и обнаружил, что запрос к базе данных напрямую, чтобы увидеть, существует ли запись, значительно медленнее чем поиск в памяти.

Ответы [ 5 ]

0 голосов
/ 15 февраля 2019

Вы должны попробовать параллельное выполнение, это должно быть очень хорошим случаем, поскольку, как вы упоминали, вы работаете с огромным набором, и упорядоченность не требуется, если вы просто хотите проверить, существует ли уже запись.

bool exists = dt.AsEnumerable().AsParallel().Any((r =>
            string.Equals(SafeTrim(r[keyColumn1]), keyValue1, StringComparison.CurrentCultureIgnoreCase) &&
            string.Equals(SafeTrim(r[keyColumn2]), keyValue2, StringComparison.CurrentCultureIgnoreCase) &&
            string.Equals(SafeTrim(r[keyColumn3]), keyValue3, StringComparison.CurrentCultureIgnoreCase)
        )
0 голосов
/ 15 февраля 2019

В некоторых случаях использование LINQ не оптимизирует так же хорошо, как последовательный запрос, поэтому вам может быть лучше написать запрос просто старомодным способом:

public static bool DoesRecordExist(string keyColumn1, string keyColumn2, string keyColumn3,
        string keyValue1, string keyValue2, string keyValue3, DataTable dt)
{
    if (dt != null) 
    {
        foreach (var r in dt.Rows)
        {
            if (string.Equals(SafeTrim(r[keyColumn1]), keyValue1, StringComparison.CurrentCultureIgnoreCase) &&
                string.Equals(SafeTrim(r[keyColumn2]), keyValue2, StringComparison.CurrentCultureIgnoreCase) &&
                string.Equals(SafeTrim(r[keyColumn3]), keyValue3, StringComparison.CurrentCultureIgnoreCase)
            {
                return true;
            }
        }
    }
    return false;
}

Но может быть более структурныйулучшения, но это зависит от ситуации, можете ли вы использовать его.

Вариант 1. Выбор уже в базе данных Вы используете DataTable, так что есть вероятность, чтоВы получаете данные из базы данных.Если у вас много записей, то может иметь смысл перенести эту проверку в базу данных.При использовании правильных индексов это может быть намного быстрее, чем сканирование таблицы в памяти.

Вариант 2: Заменить string.Equals+SafeTrim пользовательским методом Вы используете SafeTrim до трех разза строку, что создает много новых строк.Когда вы создаете свой собственный метод, который сравнивает обе строки (string.Equals) по отношению к начальным / конечным пробелам (SafeTrim), но без создания новой строки, это может быть намного быстрее, уменьшить нагрузку на память и уменьшитьвывоз мусора.Если реализация достаточно хороша для встраивания, вы получите большую производительность.

Вариант 3: Проверьте столбцы в правильном порядке Убедитесь, что вы используете правильный порядок и укажитестолбец с наименьшей вероятностью совпадения как keyColumn1.Это приведет к тому, что выражение if станет ложным раньше.Если keyColumn1 соответствует в 80% случаев, то вам нужно выполнить намного больше сравнений.

0 голосов
/ 15 февраля 2019

Возможно, вы захотите транспонировать свою структуру данных.Вместо DataTable, где каждая строка имеет keyColumn1, keyColumn2 и keyColumn3, имеют 3 HashSet<string>, где первая содержит все значения keyColumn1 и т. Д.

.быть намного быстрее, чем итерация по каждой из строк:

var hashSetColumn1 = new HashSet<string>(
    dt.Rows.Select(x => x[keyColumn1]),
   StringComparison.CurrentCultureIgnoreCase);

var hashSetColumn2 = new HashSet<string>(
    dt.Rows.Select(x => x[keyColumn2]),
   StringComparison.CurrentCultureIgnoreCase);

var hashSetColumn3 = new HashSet<string>(
    dt.Rows.Select(x => x[keyColumn3]),
   StringComparison.CurrentCultureIgnoreCase);

Очевидно, создайте их один раз, а затем поддерживайте их (как вы сейчас поддерживаете DataTable).Их дорого создавать, но дешево запрашивать.

Тогда:

bool exists = hashSetColumn1.Contains(keyValue1) &&
    hashSetColumn2.Contains(keyValue2) &&
    hashSetColumn3.Contains(keyValue3);

В качестве альтернативы (и более аккуратно) вы можете определить свою собственную структуру, которая содержит значения из 3 столбцов,и используйте один HashSet:

public struct Row : IEquatable<Row>
{
    // Convenience
    private static readonly IEqualityComparer<string> comparer = StringComparer.CurrentCultureIngoreCase;

    public string Value1 { get; }
    public string Value2 { get; }
    public string Value3 { get; }

    public Row(string value1, string value2, string value3)
    {
        Value1 = value1;
        Value2 = value2;
        Value3 = value3;
    }

    public override bool Equals(object obj) => obj is Row row && Equals(row);

    public bool Equals(Row other)
    {
        return comparer.Equals(Value1, other.Value1) &&
               comparer.Equals(Value2, other.Value2) &&
               comparer.Equals(Value3, other.Value3);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + comparer.GetHashCode(Value1);
            hash = hash * 23 + comparer.GetHashCode(Value2);
            hash = hash * 23 + comparer.GetHashCode(Value3);
            return hash;
        }
    }

    public static bool operator ==(Row left, Row right) => left.Equals(right);
    public static bool operator !=(Row left, Row right) => !(left == right);
}

Тогда вы можете сделать:

var hashSet = new HashSet<Row>(dt.Select(x => new Row(x[keyColumn1], x[keyColumn2], x[keyColumn3]));

и кэшировать это.Запрос это как:

hashSet.Contains(new Row(keyValue1, keyValue2, keyValue3));
0 голосов
/ 15 февраля 2019

Я предлагаю вам сохранить ключевые столбцы существующих записей в HashSet.Здесь я использую кортежи, но вы также можете создать свою собственную Key структуру или класс, переопределив GetHashCode и Equals.

private HashSet<(string, string, string)> _existingKeys =
    new HashSet<(string, string, string)>();

Тогда вы сможете очень быстро проверить наличие ключас

if (_existingKeys.Contains((keyValue1, keyValue2, keyValue3))) {
    ...
}

Не забудьте синхронизировать HashSet с вашими добавлениями и удалениями.Обратите внимание, что кортежи нельзя сравнивать с CurrentCultureIgnoreCase.Поэтому либо преобразуйте все ключи в нижний регистр, либо используйте подход с пользовательской структурой, в котором вы можете использовать нужный метод сравнения.

public readonly struct Key
{
    public Key(string key1, string key2, string key3) : this()
    {
        Key1 = key1?.Trim() ?? "";
        Key2 = key2?.Trim() ?? "";
        Key3 = key3?.Trim() ?? "";
    }

    public string Key1 { get; }
    public string Key2 { get; }
    public string Key3 { get; }

    public override bool Equals(object obj)
    {
        if (!(obj is Key)) {
            return false;
        }

        var key = (Key)obj;
        return
            String.Equals(Key1, key.Key1, StringComparison.CurrentCultureIgnoreCase) &&
            String.Equals(Key2, key.Key2, StringComparison.CurrentCultureIgnoreCase) &&
            String.Equals(Key3, key.Key3, StringComparison.CurrentCultureIgnoreCase);
    }

    public override int GetHashCode()
    {
        int hashCode = -2131266610;
        unchecked {
            hashCode = hashCode * -1521134295 + StringComparer.CurrentCultureIgnoreCase.GetHashCode(Key1);
            hashCode = hashCode * -1521134295 + StringComparer.CurrentCultureIgnoreCase.GetHashCode(Key2);
            hashCode = hashCode * -1521134295 + StringComparer.CurrentCultureIgnoreCase.GetHashCode(Key3);
        }
        return hashCode;
    }
}

Другой вопрос заключается в том, стоит ли использовать текущую культуру при сравнениидб ключи.Пользователи с разными культурами могут получить разные результаты.Лучше явно указать ту же культуру, что и БД.

0 голосов
/ 15 февраля 2019

Ваше решение находит все случаи, которые оценивают истину в условии, и затем вы спрашиваете, есть ли они.Вместо этого используйте Any напрямую.Заменить где на любой.Он остановит обработку, когда попадет в первую истинную оценку состояния.

bool exists = dt.AsEnumerable().Any(r => condition);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...