Сколько времени требуется для вызова пустой функции?

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

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 имеет другие методы, которые имеют код - это не просто объект, который ничего не делает.

Ответ 1

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

Очень маловероятно, чтобы влияние производительности значимо.

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

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

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

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

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

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

Ответ 2

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

В вашей ситуации у вас есть один интерфейс, который 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

  • Будет ли это иметь влияние на производительность?

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

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

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

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

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

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

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

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

Ответ 4

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

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

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

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

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

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

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

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

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

Ответ 5

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

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

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

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

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