Что стоимость использования является указателем на функцию членства по сравнению с переключателем?

Если кому-то нужно перебирать объекты массива с условием:

var arrayObjects = [{"building":"A", "status":"good"},{"building":"B","status":"horrible"}];

for (var i=0; i< arrayObjects.length; i++) {
  console.log(arrayObjects[i]);
  
  for(key in arrayObjects[i]) {      
    
      if (key == "status" && arrayObjects[i][key] == "good") {
        
          console.log(key + "->" + arrayObjects[i][key]);
      }else{
          console.log("nothing found");
      }
   }
}

12
задан Dima 23 February 2010 в 14:04
поделиться

12 ответов

Насколько уверенный Вы, что вызов функции членства через указатель медленнее, чем просто вызов его непосредственно? Можно ли измерить различие?

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

Подробнее: Существует превосходная статья Member Function Pointers и Самые Быстрые Делегаты C++, который вдается в очень глубокие подробности о реализации указателей функции членства.

11
ответ дан 2 December 2019 в 04:43
поделиться

Отвечать на заданный вопрос: на уровне с самыми прекрасными зернами указатель на функцию членства будет работать лучше.

Рассматривать добровольный вопрос: что действительно "лучше" означает здесь? В большинстве случаев я ожидал бы, что различие будет незначительно. В зависимости от того, что класс это выполнение, однако, различие может быть значительным. Тестирование производительности прежде, чем вызвать беспокойство о различии является, очевидно, правильным первым шагом.

2
ответ дан 2 December 2019 в 04:43
поделиться

Можно записать это:

class Foo {
public:
  Foo() {
    calls[0] = &Foo::call0;
    calls[1] = &Foo::call1;
    calls[2] = &Foo::call2;
    calls[3] = &Foo::call3;
  }
  void call(int number, int arg) {
    assert(number < 4);
    (this->*(calls[number]))(arg);
  }
  void call0(int arg) {
    cout<<"call0("<<arg<<")\n";
  }
  void call1(int arg) {
    cout<<"call1("<<arg<<")\n";
  }
  void call2(int arg) {
    cout<<"call2("<<arg<<")\n";
  }
  void call3(int arg) {
    cout<<"call3("<<arg<<")\n";
  }
private:
  FooCall calls[4];
};

Вычисление фактического указателя функции линейно и быстро:

  (this->*(calls[number]))(arg);
004142E7  mov         esi,esp 
004142E9  mov         eax,dword ptr [arg] 
004142EC  push        eax  
004142ED  mov         edx,dword ptr [number] 
004142F0  mov         eax,dword ptr [this] 
004142F3  mov         ecx,dword ptr [this] 
004142F6  mov         edx,dword ptr [eax+edx*4] 
004142F9  call        edx 

Обратите внимание, что Вы не должны даже фиксировать фактическое функциональное число в конструкторе.

Я сравнил этот код с asm, сгенерированным a switch. switch версия не обеспечивает увеличения производительности.

8
ответ дан 2 December 2019 в 04:43
поделиться

Если Вы собираетесь продолжать использовать переключатель, который прекрасно подходит, то, вероятно, необходимо поместить логику во вспомогательный метод и вызов если от конструктора. С другой стороны, это - классический случай Стратегической модели. Вы могли создать интерфейс (или абстрактный класс) названный IFoo, который имеет один метод с подписью Foo. Вы сделали бы, чтобы конструктор взял в экземпляре IFoo (Инжекция Зависимости конструктора, которая реализовала метод нечто, который Вы хотите. У Вас был бы частный IFoo, который будет установлен с этим конструктором, и каждый раз, когда Вы хотели позвонить Foo, которого Вы назовете версией своего IFOO.

Примечание: Я не работал с C++ начиная с колледжа, таким образом, мой малопонятный жаргон мог бы быть прочь здесь, единое время, общие представления содержат для большинства языков OO.

2
ответ дан 2 December 2019 в 04:43
поделиться

Если Вашим примером является реальный код, то я думаю, что необходимо пересмотреть дизайн класса. Передача в значении конструктору и использование этого для изменения поведения действительно эквивалентны созданию подкласса. Полагайте, что рефакторинг делает это более явным. Эффект выполнения так состоит в том, что Ваш код закончит тем, что использовал указатель функции (все виртуальные методы, действительно, указатели функции в таблицах переходов).

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

Одна вещь является бесспорной, оператор переключения никогда не будет медленнее, чем таблица переходов. Причем причина состоит в том, что лучшим, который может сделать оптимизатор компилятора, будет также поворот серия условных тестов (т.е. переключатель) в таблицу переходов. Таким образом, если Вы действительно хотите быть уверенными, вынуть компилятор из процесса принятия решений и использовать таблицу переходов.

2
ответ дан 2 December 2019 в 04:43
поделиться

Указатели функции почти всегда лучше, чем цепочечная IFS. Они делают более чистый код и почти всегда быстрее (кроме, возможно, в случае, где его единственное выбор между двумя функциями и всегда правильно предсказывается).

1
ответ дан 2 December 2019 в 04:43
поделиться

Кажется, что необходимо сделать callFoo чистая виртуальная функция и создает некоторые подклассы A.

Если Вы действительно не нуждаетесь в скорости, сделали обширное профилирование и оснащение, и решили что вызовы к callFoo действительно узкое место. Вы имеете?

1
ответ дан 2 December 2019 в 04:43
поделиться

Я должен думать, что указатель был бы быстрее.

Современные центральные процессоры выбирают инструкции с упреждением; неправильные предсказанные ответвления сбрасывают кэш, что означает, что он останавливается, в то время как он снова наполняет кэш. Указатель doens't делает это.

Конечно, необходимо измерить обоих.

1
ответ дан 2 December 2019 в 04:43
поделиться

Оптимизируйте только при необходимости

Во-первых: Большую часть времени Вы, скорее всего, не заботитесь, разница будет очень небольшой. Удостоверьтесь оптимизировав этот вызов, действительно имеет смысл сначала. Только если Ваши измерения показывают, что существует действительно значительное время, проведенное в вызове наверху, продолжите двигаться к оптимизации его (бесстыдный разъем - Cf. Как оптимизировать приложение для создания его быстрее?), Если оптимизация не является значительной, предпочтите более читаемый код.

Стоимость непрямого вызова зависит от целевой платформы

После того как Вы решили, что стоит для применения оптимизации низкого уровня, затем это - время для понимания целевой платформы. Стоимость, которой можно избежать здесь, является ответвлением misprediction штраф. На современном x86/x64 ЦП этот misprediction, вероятно, будет очень маленьким (они могут предсказать непрямые вызовы вполне хорошо большую часть времени), но при предназначении для PowerPC или других платформ RISC, косвенные вызовы/переходы часто не предсказываются вообще, и предотвращение их может вызвать значительное увеличение производительности. См., что также стоимость Виртуального вызова зависит от платформы.

Компилятор может реализовать переключатель с помощью таблицы переходов также

Один глюк: Переключатель может иногда реализовываться как непрямой вызов (использующий таблицу) также, особенно при переключении между многими возможными значениями. Такой переключатель показывает тот же misprediction как виртуальная функция. Для создания этой оптимизации надежной можно было бы, вероятно, предпочесть использовать если вместо переключателя для наиболее распространенного случая.

1
ответ дан 2 December 2019 в 04:43
поделиться

Одно часто пропускаемое преимущество для переключателя (даже по сортировке и индексации) состоит в том, если Вы знаете, что конкретное значение используется в подавляющем большинстве случаев. Легко заказать переключатель так, чтобы наиболее распространенные были проверены сначала.

PS. Укрепить ответ greg, если Вы заботитесь о скорости - мера. Рассмотрение ассемблера не помогает, когда центральные процессоры имеют упреждающую выборку / прогнозирующее ветвление и остановы конвейерной обработки и т.д.

1
ответ дан 2 December 2019 в 04:43
поделиться

Используйте таймеры для наблюдения, который более быстр. Хотя, если этот код не будет много раз затем, маловероятно, что Вы заметите любое различие.

Убедитесь это при выполнении кода от конструктора что если сбои конструкции что Вы память утечки привычки.

Эта техника используется в большой степени с Symbian ОС: http://www.titu.jyu.fi/modpa/Patterns/pattern-TwoPhaseConstruction.html

1
ответ дан 2 December 2019 в 04:43
поделиться

Если Вы только называете callFoo () однажды, чем, скорее всего, указатель функции будет медленнее незначительной суммой. Если Вы называете его много раз, чем, скорее всего, указатель функции будет быстрее незначительной суммой (потому что это не должно продолжать проходить переключатель).

Так или иначе посмотрите на собранный код для обнаружения наверняка, что он делает то, что Вы думаете, что он делает.

1
ответ дан 2 December 2019 в 04:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: