Сколько времени нужно, чтобы вызвать пустую функцию? - PullRequest
11 голосов
/ 25 августа 2011

У меня есть список элементов, реализующих интерфейс.Для вопроса, давайте использовать этот пример интерфейса:

interface Person
{
  void AgeAYear();
}

Есть два класса

class NormalPerson : Person
{
  int age = 0;

  void AgeAYear()
  {
    age++;
    //do some more stuff...
  }
}


class ImmortalPerson : Person
{
  void AgeAYear()
  {
    //do nothing...
  }
}

По другим причинам, мне нужны оба из списка.Но для этого вызова, когда я перебираю свой список Person s, я могу вызывать пустые функции. Повлияет ли это на производительность?Если да, то сколько? Будет ли оптимизирована пустая функция для всех намерений и целей?


ПРИМЕЧАНИЕ. В реальном примере ImmortalPerson имеет другие методы, которые имеютКод - это не просто объект, который ничего не делает.

Ответы [ 5 ]

9 голосов
/ 25 августа 2011

Повлияет ли это на производительность?

Маловероятно, что это повлияет на производительность .

Если да, то какмного?

Вы можете определить точно для ваших конкретных путей кода, используя профилировщик.Мы не можем, потому что мы не знаем пути кода.Мы можем догадаться, и сказать вам, что это почти наверняка не имеет значения, потому что это крайне маловероятно, чтобы быть узким местом в вашем приложении (вы действительно сидите там, вызывая Person.AgeAYear в узком цикле?).

Только вы можете точно узнать , вытащив профилировщик и измерив.

Будет ли оптимизирована пустая функция для всех целей и задач?

Это, конечно, возможно, но не может;это может даже измениться в будущей версии JITter или измениться с платформы на платформу (разные платформы имеют разные JITter).Если вы действительно хотите знать, скомпилируйте свое приложение и посмотрите на дизассемблированный код JITted (не IL!).

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

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

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

В вашей ситуации у вас есть один интерфейс, который Of Person.Вы утверждаете, что для того, чтобы быть человеком, вы должны быть в состоянии стареть, что обеспечивается вашим методом AgeAYear.Однако, согласно логике в вашем методе AgeAYear (или его отсутствии), ImmortalPerson не может стареть, но все же может быть Person.Ваша логика противоречит самой себе.Вы можете атаковать эту проблему несколькими способами, но это первое, что приходит мне в голову.

Один из способов сделать это - настроить два интерфейса:

interface IPerson { void Walk(); }
interface IAgeable : IPerson { void AgeAYear();}

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

class ImmortalPerson : IPerson
{
    public void Walk()
    {
        // Do Something
    }
}

class RegularPerson : IAgeable
{
    public void AgeAYear()
    {
        // Age A Year
    }

    public void Walk()
    {
       // Walk
    }
}

Таким образом, для вашего RegularPerson, реализуя IsAgeable, вам также необходимо реализовать IPerson.Для вашего ImmortalPerson вам нужно всего лишь реализовать IPerson.

Затем вы можете сделать что-то, как показано ниже, или его вариант:

List<IPerson> people = new List<IPerson>();

people.Add(new ImmortalPerson());
people.Add(new RegularPerson());

foreach (var person in people)
{
   if (person is IAgeable)
   {
      ((IAgeable)person).AgeAYear();
   }
}

Учитывая приведенную выше настройку, вашпо-прежнему следят за тем, чтобы ваши классы реализовывали IPerson, чтобы считаться личностью, но могут быть только в возрасте, если они также реализуют IAgeable.

3 голосов
/ 25 августа 2011
  • Повлияет ли это на производительность?

Да Возможно, если функция вызывается, то сам вызов займет небольшое количество времени.

  • Если да, то на сколько?

Вы никогда не заметите разницу в любом реальном приложении - стоимость вызова метода очень мала по сравнению со стоимостью выполнения любого"настоящая" работа.

  • Будет ли оптимизирована пустая функция для всех целей и задач?

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

foreach (IPerson person in people)
{
    person.AgeAYear();
}

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

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

1 голос
/ 30 мая 2017

На относительно современной рабочей станции вызов делегата или интерфейса C # занимает около 2 наносекунды .Для сравнения:

  • Выделение небольшого массива: 10 наносекунд
  • Распределение замыкания: 15 наносекунд
  • Принимая неоспоримую блокировку: 25 наносекунд
  • Поиск по словарю (100 коротких строковых ключей): 35 наносекунд
  • DateTime.Now (системный вызов): 750 наносекунд
  • Вызов базы данных по проводной сети (количество в небольшой таблице): 1 000 000 наносекунд (1миллисекунда)

Таким образом, если вы не оптимизируете жесткий цикл, вызовы методов вряд ли станут узким местом.Если вы оптимизируете жесткий цикл, рассмотрите лучший алгоритм, такой как индексирование объектов в Dictionary перед их обработкой.

Я тестировал их на Core i7 3770 с частотой 3,40 ГГц, используя 32-разрядный LINQPad соптимизация включена.Но из-за встраивания, оптимизации, распределения регистров и другого поведения компилятора / JIT время вызова метода будет сильно различаться в зависимости от контекста.2 наносекунды - всего лишь приблизительная цифра.

В вашем случае вы просматриваете списки.Затраты на циклы, вероятно, будут преобладать на издержках при вызове метода, так как внутреннее зацикливание включает множество вызовов метода .Производительность вряд ли будет проблемой в вашем случае, но если у вас есть миллионы элементов и / или вам необходимо регулярно их обновлять, рассмотрите возможность изменения способа представления данных.Например, у вас может быть одна переменная «year», которую вы увеличиваете глобально, вместо того, чтобы увеличивать «age» каждого.

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

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

Этого можно избежать, проверив некоторую переменнуювнутри Person, определяя его тип или использование dynamic_cast для его проверки.Если функция не нуждалась в вызове, вы можете проигнорировать ее.

Вызов функции состоит из нескольких инструкций:

  • выдвижение аргументов в стек процесса (в этом нет ни одного)case)
  • отправка адресов возврата и нескольких других данных
  • переход к функции

А когда функция заканчивается:

  • откат от функции
  • изменение указателя стека для эффективного удаления кадра стека вызываемой функции

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

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

Вызов пустых функций в больших количествах, возможно, миллионах, может оказать некоторое влияние на производительность вашей программы, но если такое происходит, это означает, что выделает что-то неправильно алгоритмическое (например, думая, что вы должны поместить NormalPersons и ImmortalPersons в один список)

...