NULL против броска и производительности - PullRequest
2 голосов
/ 24 октября 2011

У меня есть класс:

class Vector {
    public:
    element* get(int i);
    private:
    element* getIfExists(int i):
};

get вызывает getIfExists; если элемент существует, он возвращается, если нет, выполняется какое-либо действие. getIfExists может сигнализировать, что некоторый элемент i отсутствует либо сгенерировав исключение, либо вернув NULL.

Вопрос: будет ли разница в производительности? В одном случае get нужно будет проверить ==NULL, в другом try... catch.

Ответы [ 6 ]

5 голосов
/ 24 октября 2011

Это вопрос дизайна, а не производительности.Если это исключительная ситуация - как в вашей get функции - тогда выведите исключение;или даже лучше запустить утверждение, так как нарушение предусловия функции является ошибкой программирования.Если это ожидаемый случай - как в вашей getIfExist функции - не создавайте исключение.

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

3 голосов
/ 24 октября 2011

Современные компиляторы реализуют исключения «нулевой стоимости» - они только несут стоимость, когда выбрасываются, а стоимость пропорциональна очистке плюс отсутствие кэша, чтобы очистить список. Поэтому, если исключения являются исключительными, они действительно могут быть быстрее, чем коды возврата. И если они являются исключительными, они могут быть медленнее. И если ваша ошибка в функции в функции при вызове функции, она на самом деле выполняет гораздо меньше работы. Детали интересны и достойны поиска.

Но стоимость очень незначительная. В тесной петле это может иметь значение, но, как правило, нет.

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

3 голосов
/ 24 октября 2011

(см. Комментарии!)

Без сомнения, вариант return NULL имеет лучшую производительность.

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

Если метод вызывается редко, вам вообще не нужно заботиться о микрооптимизации.

Какой переведенный метод выглядит вам проще?

$ g ++ -Os -c test.cpp

#include <cstddef>

void *get_null(int i) throw ();
void *get_throwing(int i) throw (void*);

int one(int i) {
    void *res = get_null(i);
    if(res != NULL) {
        return 1;
    }
    return 0;
}

int two(int i) {
    try {
        void *res = get_throwing(i);
        return 1;
    } catch(void *ex) {
        return 0;
    }
}

$ objdump -dC test.o

0000000000000000 <one(int)>:
   0:   50                      push   %rax
   1:   e8 00 00 00 00          callq  6 <one(int)+0x6>
   6:   48 85 c0                test   %rax,%rax
   9:   0f 95 c0                setne  %al
   c:   0f b6 c0                movzbl %al,%eax
   f:   5a                      pop    %rdx
  10:   c3                      retq   

0000000000000011 <two(int)>:
  11:   56                      push   %rsi
  12:   e8 00 00 00 00          callq  17 <two(int)+0x6>
  17:   b8 01 00 00 00          mov    $0x1,%eax
  1c:   59                      pop    %rcx
  1d:   c3                      retq   
  1e:   48 ff ca                dec    %rdx
  21:   48 89 c7                mov    %rax,%rdi
  24:   74 05                   je     2b <two(int)+0x1a>
  26:   e8 00 00 00 00          callq  2b <two(int)+0x1a>
  2b:   e8 00 00 00 00          callq  30 <two(int)+0x1f>
  30:   e8 00 00 00 00          callq  35 <two(int)+0x24>
  35:   31 c0                   xor    %eax,%eax
  37:   eb e3                   jmp    1c <two(int)+0xb>
1 голос
/ 24 октября 2011

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

  1. Шаблон Microsoft состоит в том, чтобы иметь метод Get (), который возвращает объект, если он существует, и генерирует исключение, если оно не существует, и метод TryGet (), который возвращает логическое значение, указывающее, существовал ли объект, и сохраняет объект (если он существует) с параметром Ref. Моя большая претензия к этому шаблону заключается в том, что интерфейсы, использующие его, не могут быть ковариантными.
  2. Вариант, который я часто предпочитаю для коллекций ссылочных типов, состоит в том, чтобы иметь методы Get и TryGet, и чтобы TryGet возвращал ноль для несуществующих элементов. Таким образом, ковариация интерфейса работает намного лучше.
  3. Небольшое отклонение от вышеприведенного, которое работает даже для типов значений или неограниченных обобщений, заключается в том, что метод TryGet принимает логическое значение по ссылке и сохраняет в нем логический индикатор успеха / неудачи. В случае сбоя код может вернуть неуказанный объект соответствующего типа (наиболее вероятно, по умолчанию .
  4. Другой подход, который особенно подходит для частных методов, заключается в передаче либо логического, либо перечислимого типа, указывающего, должна ли подпрограмма возвращать ноль или генерировать исключение в случае сбоя. Такой подход может улучшить качество сгенерированных исключений при минимизации дублированного кода. Например, если кто-то пытается получить пакет данных из канала связи, и вызывающая сторона не подготовлена ​​к сбою, а в подпрограмме, которая читает заголовок пакета, возникает ошибка, то, вероятно, пакет должен выдать исключение. процедура чтения заголовка. Однако, если вызывающая сторона будет готова не принимать пакет, процедура чтения заголовка пакета должна указывать на сбой без выброса. Самый простой способ учесть обе возможности - передать подпрограмме read-package команду «ошибки будут обработаны вызывающей стороной» в подпрограмме read-package-header.
  5. В некоторых контекстах для вызывающей программы может быть полезно передать делегат, который будет вызван в случае возникновения ожидаемых проблем. Делегат может попытаться решить проблему и сделать что-то, чтобы указать, следует ли повторить операцию, вызывающий должен вернуться с кодом ошибки, должно возникнуть исключение или что-то еще должно произойти полностью. Иногда это может быть лучшим подходом, но трудно понять, какие данные следует передать делегату ошибок и как им следует управлять обработкой ошибок.

На практике я часто использую # 2. Мне не нравится # 1, поскольку я чувствую, что возвращаемое значение функции должно соответствовать ее основной цели.

1 голос
/ 24 октября 2011

Конечно, будет разница в производительности (может быть, даже очень большая, если вы зададите Vector::getIfExists throw() спецификацию, но я немного рассуждаю здесь). Но ИМО не хватает леса для деревьев.

Вопрос о деньгах: Вы собираетесь вызывать этот метод так много раз с параметром вне пределов? И если да, то почему?

1 голос
/ 24 октября 2011

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

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

...