Почему мой код такой медленный? - PullRequest
1 голос
/ 18 августа 2011

Я написал следующий метод расширения для получения элемента из словаря, или ноль, если ключ отсутствует :

public static TValue ItemOrNull<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
{
    try
    {
        return dict[key];
    }
    catch (KeyNotFoundException ex)
    {
        return default(TValue);
    }
}

Я заметил, что моя программа работает очень медленно, поэтому я отследил проблему до этого метода расширения, используя класс таймера высокой точности. Я получаю похожие результаты ~ 100 раз подряд:

DebugTimer.ResetTimer();
    dict1.ItemOrNull(key);
    dict2.ItemOrNull(key);
DebugTimer.StopTimer();

занимает около 110 000 000 тиков (более 0,03 секунды на моем процессоре). Пока более многословная версия:

DebugTimer.ResetTimer();
    if (dict1.ContainsKey(key))
        y = dict1[key];
    if (dict2.ContainsKey(key))
        z = dict2[key];
DebugTimer.StopTimer();
MessageBox.Show(y.ToString(), z.ToString()) // so the compiler doesn't optimize away y and z

занимает около 6000 тиков (менее 0,000002 секунд).

Понятно ли кому-либо, почему моя версия метода расширения занимает более чем на 4 порядка больше, чем подробная версия?

Ответы [ 5 ]

15 голосов
/ 18 августа 2011

Не перехватывайте исключения для управления потоком данных - дело не только в том, что это может вызвать проблемы с производительностью (хотя они не так плохи, как думает большинство людей), как говорит Эрик, большинство людей боятся производительности исключений из-за использования отладчика).Это больше о логической природе исключений.Лично я бы не использовал исключения таким образом , даже если бы они были абсолютно свободными .

Что-нибудь плохое здесь произошло?В любом случае недопустимо, чтобы пользователь запрашивал значение этого ключа?Абсолютно нет - основная цель метода - предоставить значение по умолчанию.Не исключено, что ключ отсутствует, поэтому вы должны искать способ работы без исключений.

Теперь Dictionary<,> уже имеет метод, позволяющий вам получить значение, еслиключ существует и сообщит вам, был ли он найден: TryGetValue.

public static TValue GetValueOrDefault<TKey, TValue>(
    this IDictionary<TKey, TValue> dict,
    TKey key)
{
    TValue ret;
    // We don't care about the return value - we want default(TValue)
    // if it returns false anyway!
    dict.TryGetValue(key, out ret);
    return ret;
}

Методы расширения просто компилируются в обычные статические вызовы методов и, таким образом, не имеют разницы в производительности.

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

public static TValue GetValueOrDefault<TKey, TValue>(
    this IDictionary<TKey, TValue> dict,
    TKey key, TValue defaultValue)
{
    TValue ret;
    return dict.TryGetValue(key, out value) ? ret : defaultValue;
}

Кстати, я изменил название вашего метода, чтобы оно совпадало с TryGetValue.Очевидно, вам не нужно следовать этому - это всего лишь предложение.

3 голосов
/ 18 августа 2011

Это потому, что вы не используете исключения в подробной версии.Создайте метод расширения, который использует ContainsKey и сравните.

Чтобы уточнить: исключения могут быть очень медленными.Это одна из причин не полагаться на них для контроля нормального рабочего процесса.Они предназначены для ситуаций, которые на самом деле являются неожиданными и нежелательными.

Для FW3.5 + используйте TryGetValue, как предложили другие.

1 голос
/ 18 августа 2011

Здесь есть две проблемы:

  1. Ваш метод расширения не использует тот же код, что и ваш тест.Если вы переключите его, чтобы использовать тот же код (или, что еще лучше, Dictionary.TryGetValue), время будет намного ближе друг к другу.
  2. Я подозреваю, что вы делаете свои часыв режиме отладки.Соберите время в полной версии сборки вне Visual Studio .Время в режиме отладки (или даже в хост-процессе VS в выпуске) очень неточное, поскольку процесс хостинга действительно сильно замедляет выполнение кода.Методы расширения на самом деле не имеют дополнительных издержек (они компилируются в обычные статические вызовы методов), но вызовы методов в целом имеют чрезмерные издержки при запуске внутри отладчика Visual Studio, поэтому это будет выглядеть хуже, чем реальность.
0 голосов
/ 18 августа 2011

Вы только что добавили значения времени к моему заявлению профессора: «Обработка исключений - дорогостоящий процесс! Не пишите всю логику в блоке Catch, попробуйте кодировать так, чтобы у вас были проверки, которые могут избежать исключения». Используйте обработку исключений для непредвиденных исключений, а не для очевидного, что когда элемент отсутствует в словаре, он может потерпеть крах!

0 голосов
/ 18 августа 2011

Словарь - это хеш-таблица, поэтому Dictionary.Contains работает быстро (назовите здесь один шаг), он ничего не стоит, потому что он непосредственно проверяет память на значение, связанное с ключом. [key-> hash, а затем проверьте память]

Но первая функция попробуйте уже сделать проверку! перед возвратом значения ключ хэшируется и память проверяется на это. Вскоре он уже включает шаги словаря. Содержит.

Причина, по которой ваш код работает медленно, в блоке try, который включает шаги Dictionary. Содержит отсутствующее значение bool, которое приводит к падению в блоке catch. Это пустая трата вашего времени. Ответ выше правильный и более подробный.

...