Производительность статических методов и методов экземпляра

109

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

Рассматривать:

public sealed class InstanceClass
{
      public int DoOperation1(string input)
      {
          // Some operation.
      }

      public int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more instance methods.
}

public static class StaticClass
{
      public static int DoOperation1(string input)
      {
          // Some operation.
      }

      public static int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more static methods.
}

Вышеупомянутые классы представляют собой шаблон вспомогательного стиля.

В классе экземпляра для разрешения метода экземпляра требуется время, в отличие от StaticClass.

Мои вопросы:

  1. Когда сохранение состояния не является проблемой (поля или свойства не требуются), всегда ли лучше использовать статический класс?

  2. Если существует значительное количество этих определений статических классов (скажем, 100, например, с несколькими статическими методами каждый), это отрицательно повлияет на производительность выполнения или потребление памяти по сравнению с таким же количеством определений классов экземпляров?

  3. Когда вызывается другой метод в том же классе экземпляра, выполняется ли разрешение экземпляра? Например, используя ключевое слово [this], как this.DoOperation2("abc")в DoOperation1том же экземпляре.

Берни Уайт
источник
что вы подразумеваете под "разрешением экземпляра"? На уровне IL указатель this доступен так же, как и любая другая локальная переменная. Фактически, в некоторых старых версиях CLR / JIT вы могли вызвать метод экземпляра для NULL, если он не касался 'this' - код просто пролетел и разбился ни на чем .. теперь CLR / JIT содержит явный null- проверять каждый вызов участника ..
quetzalcoatl
> vijaymukhi.com/documents/books/ilbook/chap8.htm и «экземпляр вызова» вместо просто «вызов». Первый ожидает параметра this, а второй - нет.
quetzalcoatl
@Quetzalcoatl, извините за путаницу, вопрос заключался в том, чтобы использовать другой метод из одного и того же экземпляра, и если для этого требуется, чтобы экземпляр был разрешен для себя.
Берни Уайт
1
@quetzalcoatl Я предположил, что он имел в виду: «Избавился ли компилятор от проверки, которая thisуказывает на что-то, когда класс вызывает метод экземпляра для себя?»
Джон Ханна

Ответы:

153

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

На практике это так мало разницы, что будет скрыто в шуме различных решений компилятора. (Следовательно, два человека могут «доказать» один лучше другого с несовпадающими результатами). Не в последнюю очередь потому, что thisобычно передается в регистре и часто изначально находится в этом регистре.

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

Реальные различия в производительности будут зависеть от того, есть ли у вас в памяти искусственные объекты для выполнения чего-то, что естественно должно быть статическим, или вы запутываете цепочки передачи объектов сложными способами, чтобы делать то, что естественно должно быть экземпляром.

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

Номер 2. Без разницы. Существует определенная сумма затрат на класс для каждого члена, она зависит как от количества метаданных, так и от количества кода в фактическом файле DLL или EXE, а также от количества измененного кода. Это одно и то же, будь то экземпляр или статический.

С пунктом 3 thisвсе как thisесть. Однако обратите внимание:

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

  2. Поскольку будет ясно, что thisэто не NULL, в некоторых случаях это можно использовать для оптимизации вызовов.

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

  4. Тем не менее, нулевые чеки дешевы!

Стоит отметить, что общие статические методы, действующие на объект, а не методы экземпляра, могут снизить некоторые затраты, обсуждаемые на http://joeduffyblog.com/2011/10/23/on-generics-and-some-of- the-associated-overheads / в случае, когда эта статика не вызывается для данного типа. По его словам, «в стороне, оказывается, что методы расширения - отличный способ сделать общие абстракции более выгодными».

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

Резюме:

  1. В основном затраты на производительность экземпляра по сравнению со статическим ниже незначительны.
  2. Какие затраты обычно возникают при злоупотреблении статикой, например, или наоборот. Если вы не сделаете это частью своего выбора между статикой и экземпляром, у вас больше шансов получить правильный результат.
  3. В редких случаях статические универсальные методы в другом типе приводят к созданию меньшего количества типов, чем универсальные методы экземпляра, что иногда может иметь небольшое преимущество, так как становится редко используемым (и «редко» относится к тем типам, с которыми он используется в время жизни приложения, а не то, как часто оно вызывается). Как только вы поймете, о чем он говорит в этой статье, вы увидите, что в любом случае это на 100% не имеет отношения к большинству решений, связанных со статическим или экземпляром. Изменить: и в основном это стоит только с ngen, а не с jit-кодом.

Изменить: примечание о том, насколько дешевы нулевые проверки (что я утверждал выше). Большинство нулевых проверок в .NET вообще не проверяют наличие null, скорее они продолжают то, что собирались делать, с предположением, что это будет работать, и если возникает исключение доступа, оно превращается в файл NullReferenceException. Таким образом, в большинстве случаев, когда концептуально код C # включает проверку на null, поскольку он обращается к члену экземпляра, стоимость в случае успеха фактически равна нулю. Исключением могут быть некоторые встроенные вызовы (потому что они хотят вести себя так, как если бы они вызывали член экземпляра), и они просто попадают в поле, чтобы вызвать такое же поведение, поэтому они также очень дешевы, и их все равно часто можно не учитывать. (например, если первый шаг в методе включал доступ к полю как есть).

Джон Ханна
источник
Не могли бы вы прокомментировать, влияет ли вопрос статики и экземпляра на согласованность кеша? Является ли использование одного или другого более вероятным причиной промахов кеша? Есть ли хороший план, объясняющий почему?
скриптокалипсис
@scriptocalypse Не совсем. Кэш инструкций не увидит никакой разницы, и на этом уровне нет большой разницы между доступом к данным через thisили через явный параметр. Большее влияние здесь будет иметь то, насколько данные близки к связанным данным (поля типа значения или значения массива ближе, чем данные в полях ссылочного типа), и шаблоны доступа.
Джон Ханна
«Теоретически мы должны ожидать, что статический метод, который принимает объект в качестве параметра и что-то с ним делает, будет немного хуже, чем эквивалент в качестве экземпляра для того же объекта». - Вы имеете в виду, что если приведенный выше образец метода принимает параметр как объект вместо строки, лучше нестатический? например: у меня мой статический метод принимает объект в качестве параметра и сериализует его в строку и возвращает строку. вы предлагаете использовать нестатический в этом случае?
batmaci
1
@batmaci Я имею в виду, что есть хороший шанс, obj.DoSomehting(2)что это будет немного дешевле, DoSomething(obj, 2)но, как я также сказал, разница настолько мала и настолько зависит от крошечных вещей, которые могут оказаться разными в финальной компиляции, что на самом деле не стоит беспокоиться вообще. Если вы делаете что-то столь же дорогое (относительно различий в игре), как сериализацию чего-либо в строку, то это особенно бессмысленно.
Джон Ханна
В этом отличном ответе отсутствует одна, возможно, очевидная, но важная вещь: для метода экземпляра требуется экземпляр, а создание экземпляра обходится недешево. Даже по умолчанию по- ctorпрежнему требуется инициализация всех полей. Если у вас уже есть экземпляр, применяется этот ответ («при ​​прочих равных»). Конечно, из-за дороговизны cctorстатические методы тоже могут замедлиться, но только при первом вызове они одинаково применяются к методам экземпляра. См. Также docs.microsoft.com/en-us/previous-versions/dotnet/articles/…
Абель,
9

Когда сохранение состояния не является проблемой (поля или свойства не требуются), всегда ли лучше использовать статический класс?

Я бы сказал, да. Объявляя что-то, staticвы заявляете о намерении выполнения без сохранения состояния (это не обязательно, но есть намерение чего-то, чего можно было бы ожидать)

Если существует значительное количество этих статических классов (скажем, 100, например, с несколькими статическими методами каждый), будет ли это отрицательно влиять на производительность выполнения или потребление памяти по сравнению с тем же количеством классов экземпляров?

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

Когда ключевое слово [this] используется для вызова другого метода в том же классе экземпляра, происходит ли разрешение экземпляра?

Не уверен, насчет этого момента (это чисто деталь реализации CLR), но думаю, что да.

Тигран
источник
Статические методы нельзя смоделировать. Если вы выполняете TDD или даже просто модульное тестирование, это сильно повредит вашим тестам.
trampster
@trampster Почему? Это просто логика. Вы легко можете поиздеваться над тем, что даете? Чтобы добиться правильного поведения. И многие статические методы в любом случае будут частными частями логики в функции.
М. Мимпен 08
@ M.Mimpen до тех пор, пока вы оставляете его на небольшие частные части, ваш штраф, если это общедоступный метод, и вы используете его из других закрытий и вам нужно изменить то, что он делает в вашем тесте, тогда ваш застрял, такие вещи, как ввод-вывод файла или доступ к базе данных или сетевые вызовы и т. д., если они помещены в статический метод, станут незаменимыми, если, как вы говорите, вы не вставляете фиктивную зависимость в качестве параметра статического метода
trampster
-2

статические методы быстрее, но менее ООП, если вы будете использовать статический метод шаблонов проектирования, вероятно, плохой код, чтобы лучше писать бизнес-логику без статических, общих функций, таких как чтение файлов, WebRequest и т. д., лучше понимать как статические ... у вас нет универсальных вопросов ответ

горящий_LEGION
источник
17
Вы не привели аргументов в пользу своих утверждений.
ymajoros 05
2
@ fjch1997 2 сторонника, похоже, думают иначе (чего бы это ни стоило). Комментирование отрицательных голосов приветствуется на stackexchange: meta.stackexchange.com/questions/135/…
ymajoros