Действительно ли закрытые классы дают преимущества в производительности?

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

Я провел несколько тестов, чтобы проверить разницу в производительности, и ничего не обнаружил. Я делаю что-то неправильно? Я упустил случай, когда закрытые классы дадут лучшие результаты?

Кто-нибудь запускал тесты и видел разницу?

Помогите мне узнать :)


person Vaibhav    schedule 05.08.2008    source источник
comment
Я не думаю, что закрытые классы были предназначены для увеличения производительности. То, что они делают, может быть случайным. Кроме того, профилируйте свое приложение после его рефакторинга для использования запечатанных классов и определите, стоит ли оно затраченных усилий. Ограничение расширяемости для ненужной микрооптимизации в конечном итоге обойдется вам дорого. Конечно, если вы профилировали, и это позволяет вам достичь своих показателей производительности (а не производительности ради производительности), тогда вы можете принять решение как команда, стоит ли оно потраченных денег. Если у вас есть закрытые классы по причинам, не связанным с производительностью, оставьте их :)   -  person Merlyn Morgan-Graham    schedule 03.09.2010
comment
Вы пробовали с рефлексией? Я где-то читал, что создание экземпляров с помощью отражения быстрее с запечатанными классами   -  person onof    schedule 03.09.2010
comment
Насколько мне известно, его нет. Sealed существует по другой причине - для блокировки расширяемости, которая может быть полезна / необходима во многих случаях. Оптимизация производительности здесь не была целью.   -  person TomTom    schedule 03.09.2010
comment
... но если вы думаете о компиляторе: если ваш класс запечатан, вы знаете адрес метода, который вы вызываете в своем классе во время компиляции. Если ваш класс не запечатан, вы должны разрешить метод во время выполнения, потому что вам может потребоваться вызвать переопределение. Это, конечно, будет незначительным, но я видел, что будет некоторая разница.   -  person Dan Puzey    schedule 03.09.2010
comment
Да, но, как отметила ОП, это не дает реальных выгод. Архитектурные различия могут быть гораздо более актуальными.   -  person TomTom    schedule 03.09.2010
comment
См. Соответствующий вопрос по той же теме, касающийся Java / JVM: stackoverflow.com/questions/3961881/   -  person andersoj    schedule 18.10.2010
comment
Классы уплотнений должны обеспечивать производительность только при наличии виртуального метода / метода переопределения.   -  person mathk    schedule 28.06.2012
comment
И если у JIT есть какой-то встроенный механизм кэширования, это менее выгодно для производительности.   -  person mathk    schedule 28.06.2012
comment
Для меня единственное преимущество использования запечатанного класса - это упрощение кода. Компилятор не позволяет создавать защищенные члены в таком классе, поэтому вы можете сделать на одну ошибку меньше. Также IDisposable реализация для запечатанного класса немного проще.   -  person Harry    schedule 22.07.2021


Ответы (11)


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

Существуют сложные правила относительно типа вызова, виртуальный / невиртуальный, и я не знаю их всех, поэтому я не могу описать их для вас, но если вы используете Google для запечатанных классов и виртуальных методов, вы можете найти несколько статей по этой теме.

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

Вот одна ссылка, в которой упоминается об этом: Rambling по запечатанному ключевому слову

person Lasse V. Karlsen    schedule 05.08.2008
comment
«бессвязная» ссылка интересна тем, что звучит как техническая безупречность, но на самом деле это ерунда. Подробнее читайте в комментариях к статье. Резюме: приведены 3 причины: управление версиями, производительность и безопасность / предсказуемость - [см. Следующий комментарий] - person Steven A. Lowe; 15.10.2008
comment
[продолжение] Управление версиями применяется только тогда, когда нет подклассов, да, но расширьте этот аргумент на каждый класс, и внезапно у вас нет наследования, и угадайте, что, язык больше не объектно-ориентированный (а просто объектно-ориентированный)! [см. далее] - person Steven A. Lowe; 15.10.2008
comment
[продолжение] пример производительности - это шутка: оптимизация вызова виртуального метода; почему у запечатанного класса вообще должен быть виртуальный метод, если он не может быть разделен на подклассы? Наконец, аргумент безопасности / предсказуемости явно бессмысленен: «вы не можете использовать его, поэтому он безопасен / предсказуем». РЖУ НЕ МОГУ! - person Steven A. Lowe; 15.10.2008
comment
Я уже упоминал, что ненавижу закрытые классы? Нет ничего хуже, чем выполнить приложение на 95% и обнаружить, что единственная функция, которую хочет клиент, которую библиотека не предоставляет, логически принадлежит подклассу закрытого базового класса. Гррррр! - person Steven A. Lowe; 15.10.2008
comment
@ Стивен А. Лоу - я думаю, что Джеффри Рихтер пытался сказать несколько окольным путем, что если вы оставите свой класс незапечатанным, вам нужно подумать о том, как производные классы могут / будут использовать его, и если у вас нет время или желание сделать это должным образом, а затем запечатать его, так как это с меньшей вероятностью вызовет критические изменения в чужом коде в будущем. Это вовсе не чушь, это здравый смысл. - person Greg Beech; 20.11.2009
comment
Запечатанный класс может иметь виртуальный метод, поскольку он может быть производным от класса, который его объявляет. Когда вы позже объявляете переменную запечатанного класса-потомка и вызываете этот метод, компилятор может послать прямой вызов известной реализации, так как он знает, что это никак не может отличаться от того, что в известном - at-compile-time vtable для этого класса. Что касается запечатанных / незапечатанных, это другое обсуждение, и я согласен с причинами, по которым классы по умолчанию запечатаны. - person Lasse V. Karlsen; 20.11.2009
comment
Предоставленная ссылка на Rambling по ключевому слову sealed очень интересна. На самом деле в статье говорится, что герметизация - это хорошо и должна стать стандартом по умолчанию, но в большинстве комментариев говорится, что это совершенно неправильно и, возможно, ее следует полностью удалить из языка C #. Обожаю эти контрасты. :-) - person Al Kepp; 19.12.2018

Ответ - нет, закрытые классы не работают лучше, чем незапечатанные.

Проблема сводится к кодам операций call vs callvirt IL. Call быстрее, чем callvirt, а callvirt в основном используется, когда вы не знаете, был ли объект подклассифицирован. Поэтому люди предполагают, что если вы запечатаете класс, все коды операций изменятся с calvirts на calls и будут быстрее.

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

В структурах используется call, потому что они не могут быть разделены на подклассы и никогда не имеют значения NULL.

См. Этот вопрос для получения дополнительной информации:

Звонок и callvirt

person Cameron MacFarland    schedule 24.12.2008
comment
AFAIK, обстоятельства, при которых используется call: в ситуации new T().Method(), для struct методов, для невиртуальных вызовов virtual методов (таких как base.Virtual()) или для static методов. В других местах используется callvirt. - person porges; 05.11.2010
comment
Эээ ... Я понимаю, что это старый, но это не совсем верно ... большая победа с запечатанными классами - это когда JIT-оптимизатор может встроить вызов ... в этом случае запечатанный класс может быть огромной победой . - person Brian Kennedy; 01.10.2011
comment
@BrianKennedy Inlining не дает большого повышения производительности. Если бы это было так, все бы сказали вам запечатать свои классы. И большинство методов нельзя встроить из-за сложности. - person Cameron MacFarland; 02.10.2011
comment
Почему этот ответ неверен. Из журнала изменений Mono: Оптимизация девиртуализации для закрытых классов и методов, повышающая производительность pystone IronPython 2.0 на 4%. Другие программы могут ожидать аналогичного улучшения [Родриго] .. Запечатанные классы могут улучшить производительность, но, как всегда, это зависит от ситуации. - person Smilediver; 15.11.2011
comment
Всем, кто думает, что это неправильно - попробуйте. Любое возможное улучшение скорости будет в лучшем случае минимальным. - person Cameron MacFarland; 15.11.2011
comment
@Smilediver Он может улучшить производительность, но только если у вас плохой JIT (хотя я не знаю, насколько хороши .NET JIT в настоящее время - раньше в этом отношении было довольно плохо). Например, Hotspot будет встроить виртуальные вызовы и при необходимости деоптимизировать позже - следовательно, вы оплачиваете дополнительные накладные расходы только в том случае, если вы фактически подклассифицируете класс (и даже тогда не обязательно). - person Voo; 21.02.2012
comment
-1 JIT не обязательно должен генерировать один и тот же машинный код для тех же кодов операций IL. Проверка нуля и виртуальный вызов - это ортогональные и отдельные шаги callvirt. В случае запечатанных типов JIT-компилятор все еще может оптимизировать часть callvirt. То же самое происходит, когда JIT-компилятор может гарантировать, что ссылка не будет нулевой. - person rightfold; 01.07.2014
comment
Этот ответ уже устарел: devblogs.microsoft.com/dotnet/ - person Sedat Kapanoglu; 30.04.2020

Обновление. Начиная с .NET Core 2.0 и .NET Desktop 4.7.1, среда CLR теперь поддерживает девиртуализацию. Он может принимать методы в запечатанных классах и заменять виртуальные вызовы прямыми вызовами - и он также может делать это для незапечатанных классов, если может понять, что это безопасно.

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

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

https://blogs.msdn.microsoft.com/dotnet/2017/06/29/performance-improvements-in-ryujit-in-net-core-and-net-framework/


Оригинальный ответ:

Я создал следующую тестовую программу, а затем декомпилировал ее с помощью Reflector, чтобы посмотреть, какой код MSIL был выдан.

public class NormalClass {
    public void WriteIt(string x) {
        Console.WriteLine("NormalClass");
        Console.WriteLine(x);
    }
}

public sealed class SealedClass {
    public void WriteIt(string x) {
        Console.WriteLine("SealedClass");
        Console.WriteLine(x);
    }
}

public static void CallNormal() {
    var n = new NormalClass();
    n.WriteIt("a string");
}

public static void CallSealed() {
    var n = new SealedClass();
    n.WriteIt("a string");
}

Во всех случаях компилятор C # (Visual Studio 2010 в конфигурации сборки Release) выдает идентичный MSIL, который выглядит следующим образом:

L_0000: newobj instance void <NormalClass or SealedClass>::.ctor()
L_0005: stloc.0 
L_0006: ldloc.0 
L_0007: ldstr "a string"
L_000c: callvirt instance void <NormalClass or SealedClass>::WriteIt(string)
L_0011: ret 

Часто цитируемая причина, по которой люди говорят, что герметичность обеспечивает преимущества в производительности, заключается в том, что компилятор знает, что класс не переопределяется, и поэтому может использовать call вместо callvirt, поскольку ему не нужно проверять виртуальные объекты и т. Д. Как показано выше, это не правда.

Моя следующая мысль заключалась в том, что, хотя MSIL идентичен, возможно, компилятор JIT по-разному обрабатывает запечатанные классы?

Я запустил сборку релиза в отладчике Visual Studio и просмотрел декомпилированный вывод x86. В обоих случаях код x86 был идентичен, за исключением имен классов и адресов памяти функций (которые, конечно, должны быть разными). Вот

//            var n = new NormalClass();
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  sub         esp,8 
00000006  cmp         dword ptr ds:[00585314h],0 
0000000d  je          00000014 
0000000f  call        70032C33 
00000014  xor         edx,edx 
00000016  mov         dword ptr [ebp-4],edx 
00000019  mov         ecx,588230h 
0000001e  call        FFEEEBC0 
00000023  mov         dword ptr [ebp-8],eax 
00000026  mov         ecx,dword ptr [ebp-8] 
00000029  call        dword ptr ds:[00588260h] 
0000002f  mov         eax,dword ptr [ebp-8] 
00000032  mov         dword ptr [ebp-4],eax 
//            n.WriteIt("a string");
00000035  mov         edx,dword ptr ds:[033220DCh] 
0000003b  mov         ecx,dword ptr [ebp-4] 
0000003e  cmp         dword ptr [ecx],ecx 
00000040  call        dword ptr ds:[0058827Ch] 
//        }
00000046  nop 
00000047  mov         esp,ebp 
00000049  pop         ebp 
0000004a  ret 

Затем я подумал, что, возможно, работа под отладчиком заставляет его выполнять менее агрессивную оптимизацию?

Затем я запустил автономный исполняемый файл сборки выпуска вне любых сред отладки и использовал WinDBG + SOS для взлома после завершения программы и просмотра разборки JIT-скомпилированного кода x86.

Как видно из приведенного ниже кода, при работе вне отладчика JIT-компилятор более агрессивен, и он встроил метод WriteIt прямо в вызывающую программу. Однако важно то, что он был идентичен при вызове класса sealed vs non-sealed. Нет никакой разницы между закрытым и незапечатанным классом.

Вот это при вызове обычного класса:

Normal JIT generated code
Begin 003c00b0, size 39
003c00b0 55              push    ebp
003c00b1 8bec            mov     ebp,esp
003c00b3 b994391800      mov     ecx,183994h (MT: ScratchConsoleApplicationFX4.NormalClass)
003c00b8 e8631fdbff      call    00172020 (JitHelp: CORINFO_HELP_NEWSFAST)
003c00bd e80e70106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c00c2 8bc8            mov     ecx,eax
003c00c4 8b1530203003    mov     edx,dword ptr ds:[3302030h] ("NormalClass")
003c00ca 8b01            mov     eax,dword ptr [ecx]
003c00cc 8b403c          mov     eax,dword ptr [eax+3Ch]
003c00cf ff5010          call    dword ptr [eax+10h]
003c00d2 e8f96f106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c00d7 8bc8            mov     ecx,eax
003c00d9 8b1534203003    mov     edx,dword ptr ds:[3302034h] ("a string")
003c00df 8b01            mov     eax,dword ptr [ecx]
003c00e1 8b403c          mov     eax,dword ptr [eax+3Ch]
003c00e4 ff5010          call    dword ptr [eax+10h]
003c00e7 5d              pop     ebp
003c00e8 c3              ret

Против запечатанного класса:

Normal JIT generated code
Begin 003c0100, size 39
003c0100 55              push    ebp
003c0101 8bec            mov     ebp,esp
003c0103 b90c3a1800      mov     ecx,183A0Ch (MT: ScratchConsoleApplicationFX4.SealedClass)
003c0108 e8131fdbff      call    00172020 (JitHelp: CORINFO_HELP_NEWSFAST)
003c010d e8be6f106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c0112 8bc8            mov     ecx,eax
003c0114 8b1538203003    mov     edx,dword ptr ds:[3302038h] ("SealedClass")
003c011a 8b01            mov     eax,dword ptr [ecx]
003c011c 8b403c          mov     eax,dword ptr [eax+3Ch]
003c011f ff5010          call    dword ptr [eax+10h]
003c0122 e8a96f106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c0127 8bc8            mov     ecx,eax
003c0129 8b1534203003    mov     edx,dword ptr ds:[3302034h] ("a string")
003c012f 8b01            mov     eax,dword ptr [ecx]
003c0131 8b403c          mov     eax,dword ptr [eax+3Ch]
003c0134 ff5010          call    dword ptr [eax+10h]
003c0137 5d              pop     ebp
003c0138 c3              ret

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

person Orion Edwards    schedule 20.02.2012
comment
Любая причина, по которой вы опубликовали этот ответ дважды, вместо того, чтобы закрыть другой как дубликат? Мне они кажутся действительными дубликатами, хотя это не моя территория. - person Flexo; 21.02.2012
comment
Что, если бы методы были длиннее (более 32 байтов кода IL), чтобы избежать встраивания и посмотреть, какая операция вызова будет использоваться? Если он встроен, вы не видите вызова, поэтому не можете судить о последствиях. - person ygoe; 28.06.2015
comment
Я сбит с толку: зачем callvirt, если эти методы изначально не виртуальные? - person freakish; 28.08.2018
comment
@freakish Я не могу вспомнить, где я это видел, но я читал, что CLR использовала callvirt для запечатанных методов, потому что ему все еще нужно проверять объект на ноль перед вызовом вызова метода, и как только вы это учитываете, вы также можете просто используйте callvirt. Чтобы удалить callvirt и просто перейти напрямую, им нужно либо изменить C #, чтобы разрешить ((string)null).methodCall(), как это делает C ++, либо им нужно будет статически доказать, что объект не является нулевым (что они могли бы сделать, но не беспокоили к) - person Orion Edwards; 18.09.2018
comment
Подсказки для попытки докопаться до уровня машинного кода, но будьте очень осторожны, делая такие утверждения, как «это дает твердое доказательство того, что не может быть какого-либо улучшения производительности». Вы показали, что для одного конкретного сценария нет разницы в исходном выводе. Это одна точка данных, и нельзя предположить, что она распространяется на все сценарии. Во-первых, ваши классы не определяют никаких виртуальных методов, поэтому виртуальные вызовы вообще не требуются. - person Matt Craig; 15.12.2019

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

Но это зависит от реализации компилятора и среды выполнения.


Подробности

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

Существует большое препятствие под названием динамическая отправка, которое нарушает эту «предварительную выборку». оптимизация. Вы можете понять это как условное ветвление.

// Value of `v` is unknown,
// and can be resolved only at runtime.
// CPU cannot know which code to prefetch.
// Therefore, just prefetch any one of a() or b().
// This is *speculative execution*.
int v = random();
if (v==1) a();
else b();

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

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

Некоторые процессоры (включая новейшие чипы Intel x86) используют технику, называемую спекулятивным исполнением < / strong> использовать конвейер даже в ситуации. Просто выберите один из путей выполнения. Но результативность этой техники не так уж и высока. А сбой из-за предположений вызывает остановку конвейера, что также значительно снижает производительность. (это полностью за счет реализации ЦП. Известно, что некоторые мобильные ЦП не используют такого рода оптимизацию для экономии энергии)

По сути, C # - это статически компилируемый язык. Но не всегда. Я не знаю точного условия, и это полностью зависит от реализации компилятора. Некоторые компиляторы могут исключить возможность динамической отправки, предотвращая переопределение метода, если метод помечен как sealed. Глупые компиляторы не могут. Это преимущество sealed в производительности.


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

person eonil    schedule 03.02.2011
comment
Процессоры класса Pentium напрямую предварительно выбирают косвенную диспетчеризацию. Иногда по этой причине перенаправление указателя на функцию происходит быстрее, чем if (нераспознаваемое). - person Joshua; 03.02.2011
comment
Одним из преимуществ невиртуальных или закрытых функций является то, что они могут быть встроены в большем количестве ситуаций. - person CodesInChaos; 04.02.2011

‹Off-topic-rant›

Я ненавижу закрытые классы. Даже если преимущества в производительности поразительны (в чем я сомневаюсь), они разрушают объектно-ориентированную модель, предотвращая повторное использование через наследование. Например, класс Thread запечатан. Хотя я вижу, что потоки могут быть как можно более эффективными, я также могу представить себе сценарии, в которых возможность создания подкласса Thread будет иметь большие преимущества. Авторы классов, если вы должны запечатать свои классы по соображениям "производительности", пожалуйста, предоставьте интерфейс по крайней мере, чтобы нам не приходилось оборачивать и заменять везде что нам нужна функция, которую вы забыли.

Пример: SafeThread пришлось обернуть класс Thread, потому что поток запечатан и есть нет интерфейса IThread; SafeThread автоматически перехватывает необработанные исключения в потоках, чего полностью не хватает в классе Thread. [и нет, необработанные события исключения не собирают необработанные исключения во вторичных потоках].

‹/Off-topic-rant›

person Steven A. Lowe    schedule 14.10.2008
comment
Я не закрываю свои классы по соображениям производительности. Я запечатываю их по соображениям дизайна. Проектировать наследование сложно, и большую часть времени эти усилия будут потрачены впустую. Я полностью согласен с предоставлением интерфейсов - это далеко лучшее решение для распечатывания классов. - person Jon Skeet; 31.10.2008
comment
@ [Джон Скит]: вы пишете классы для библиотек, которые будут использовать другие разработчики? Я не думаю, что проектирование для наследования сложно, что мне не хватает? Кроме того, интерфейсы великолепны, но они не заменяют наследование, они просто лучше, чем ничего. - person Steven A. Lowe; 31.10.2008
comment
Инкапсуляция обычно является лучшим решением, чем наследование. Чтобы взять ваш конкретный пример потока, перехват исключений потока нарушает принцип подстановки Лискова, потому что вы изменили задокументированное поведение класса Thread, поэтому, даже если вы могли бы унаследовать его, было бы неразумно утверждать, что вы можете использовать SafeThread повсюду. вы можете использовать Thread. В этом случае вам было бы лучше инкапсулировать Thread в другой класс, который имеет другое задокументированное поведение, которое вы можете сделать. Иногда все запечатано для вашего же блага. - person Greg Beech; 20.11.2009
comment
@ [Greg Beech]: мнение, а не факт - возможность унаследовать от Thread, чтобы исправить ужасную ошибку в его дизайне, НЕ плохо ;-) И я думаю, что вы переоцениваете LSP - доказуемое свойство q (x) в в данном случае «необработанное исключение уничтожает программу», что является нежелательным свойством :-) - person Steven A. Lowe; 23.11.2009
comment
-1 за непонимание того, что слишком большая гибкость кода обычно приводит только к злоупотреблениям. - person Turing Complete; 03.08.2010
comment
@ Тьюринга интересный набор предположений; Я предполагаю, что вы водите слот-машину ;-) - person Steven A. Lowe; 04.08.2010
comment
Нет, но у меня была своя доля дрянного кода, когда я позволял другим разработчикам злоупотреблять моими материалами, не запечатывая их или разрешая угловые случаи. Большая часть моего кода в настоящее время - это утверждения и другие вещи, связанные с контрактами. И я совершенно открыто говорю о том, что делаю это только для того, чтобы причинить боль в ***. - person Turing Complete; 04.08.2010
comment
Зачем SafeThread должен быть отдельный класс? Почему бы просто не иметь где-нибудь статический фабричный метод, который принимает Action для потока, который будет запущен, и Action<Exception>, который запускается после нормального или аварийного завершения? Такой метод может просто вернуть Thread. - person supercat; 30.01.2013
comment
@supercat: сейчас это было бы одним из полезных решений. SafeThread был впервые написан в .NET 1.1! - person Steven A. Lowe; 30.01.2013
comment
@ StevenA.Lowe: Есть ли причина, по которой тот же подход не мог быть использован в 1.1 (замена Action<Exception> на настраиваемый тип делегата, который принимает параметр типа Exception)? - person supercat; 30.01.2013
comment
@supercat: может быть, не подумал об этом - и я хотел, чтобы события сгенерированного исключения и завершенные операции подключились к - person Steven A. Lowe; 30.01.2013
comment
Поскольку мы здесь рассуждаем не по теме, точно так же, как вы ненавидите закрытые классы, я ненавижу проглатывать исключения. Нет ничего хуже, чем когда что-то идет, а программа продолжается. JavaScript - мой любимый. Вы вносите изменения в какой-то код, и внезапное нажатие кнопки абсолютно ничего не делает. Большой! Еще одно - ASP.NET и UpdatePanel; серьезно, если мой обработчик кнопок бросает это большое дело, и он должен быть АВАРИЙ, так что я знаю, что есть что-то, что нужно исправить! Кнопка, которая ничего не делает, более бесполезна, чем кнопка, которая вызывает аварийный экран! - person Roman Starkov; 27.08.2013
comment
Пока мы рассуждали не по теме, кто-то из моих знакомых взял свой Macbook в Apple Store, чтобы заменить батарею, и ему сказали, что батарея, вероятно, испортилась из-за перезарядки. Интересно, на каком основании они сделали это определение. Может, они просто всем это расскажут. Но вы знаете, что при проектировании ноутбука оставлять его подключенным кажется вариантом использования, для которого вы могли бы спроектировать? Может быть? грррр - person Jason Orendorff; 23.02.2016
comment
Я так рад, что наконец-то нашлось место для не по теме в Интернете. Это было крайне необходимо. - person Jason Orendorff; 23.02.2016

Маркировка класса sealed не должна влиять на производительность.

Бывают случаи, когда csc, возможно, придется передать код операции callvirt вместо кода операции call. Однако, похоже, такие случаи редки.

И мне кажется, что JIT должен иметь возможность генерировать тот же вызов невиртуальной функции для callvirt, что и для call, если он знает, что у этого класса нет подклассов (пока). Если существует только одна реализация метода, нет смысла загружать его адрес из vtable - просто вызовите эту реализацию напрямую. В этом отношении JIT может даже встроить функцию.

Со стороны JIT это немного рискованно, потому что, если подкласс будет загружен позже, JIT придется отбросить этот машинный код и снова скомпилировать код, испуская настоящий виртуальный вызов. Думаю, на практике такое случается нечасто.

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

person Jason Orendorff    schedule 20.11.2009

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

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

person Karl Seguin    schedule 05.08.2008
comment
Они должны, но похоже, что нет. Если бы в CLR была концепция ненулевых ссылочных типов, то запечатанный класс действительно был бы лучше, поскольку компилятор мог бы выдавать call вместо _2 _... Мне бы тоже понравились ненулевые ссылочные типы по многим другим причинам. .. вздох :-( - person Orion Edwards; 21.02.2012

Я считаю «запечатанные» классы нормальным случаем, и у меня ВСЕГДА есть причина опустить ключевое слово «запечатанные».

Для меня самые важные причины:

a) Лучшая проверка времени компиляции (приведение к нереализованным интерфейсам будет обнаружено во время компиляции, а не только во время выполнения)

и главная причина:

б) Злоупотребление моими занятиями таким образом невозможно

Хотелось бы, чтобы Microsoft сделала стандарт «запечатанным», а не «распечатанным».

person Turing Complete    schedule 03.08.2010
comment
Я считаю, что опустить (исключить) следует испускать (производить)? - person user2864740; 14.02.2017

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

Однако лучшая причина для запечатывания класса - это сказать: «Я проектировал это не для того, чтобы наследовать от него, поэтому я не позволю вам обжечься, предполагая, что он был так спроектирован, и я не собираюсь чтобы сжечь себя, будучи заблокированным в реализации, потому что я позволил вам извлечь из нее. "

Я знаю, что некоторые здесь говорят, что ненавидят закрытые классы, потому что им нужна возможность наследовать от чего угодно ... но это ЧАСТО не самый удобный в обслуживании выбор ... потому что раскрытие класса для создания производных блокирует вас в гораздо большей степени, чем не раскрытие всего что. Это похоже на высказывание «Я ненавижу классы, у которых есть частные члены ... Я часто не могу заставить класс делать то, что я хочу, потому что у меня нет доступа». Герметизация важна ... герметизация - это одна из форм герметизации.

person Brian Kennedy    schedule 01.10.2011
comment
Компилятор C # по-прежнему будет использовать callvirt (вызов виртуального метода) для методов экземпляра в запечатанных классах, потому что он по-прежнему должен выполнять для них проверку нулевого объекта. Что касается встраивания, CLR JIT может (и делает) встроенные вызовы виртуальных методов как для запечатанных, так и для незапечатанных классов ... так что да. Производительность - это миф. - person Orion Edwards; 21.02.2012

Чтобы действительно увидеть их, вам нужно проанализировать JIT-скомпилированную код e (последний).

Код C #

public sealed class Sealed
{
    public string Message { get; set; }
    public void DoStuff() { }
}
public class Derived : Base
{
    public sealed override void DoStuff() { }
}
public class Base
{
    public string Message { get; set; }
    public virtual void DoStuff() { }
}
static void Main()
{
    Sealed sealedClass = new Sealed();
    sealedClass.DoStuff();
    Derived derivedClass = new Derived();
    derivedClass.DoStuff();
    Base BaseClass = new Base();
    BaseClass.DoStuff();
}

Код MIL

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       41 (0x29)
  .maxstack  8
  IL_0000:  newobj     instance void ConsoleApp1.Program/Sealed::.ctor()
  IL_0005:  callvirt   instance void ConsoleApp1.Program/Sealed::DoStuff()
  IL_000a:  newobj     instance void ConsoleApp1.Program/Derived::.ctor()
  IL_000f:  callvirt   instance void ConsoleApp1.Program/Base::DoStuff()
  IL_0014:  newobj     instance void ConsoleApp1.Program/Base::.ctor()
  IL_0019:  callvirt   instance void ConsoleApp1.Program/Base::DoStuff()
  IL_0028:  ret
} // end of method Program::Main

JIT-скомпилированный код

--- C:\Users\Ivan Porta\source\repos\ConsoleApp1\Program.cs --------------------
        {
0066084A  in          al,dx  
0066084B  push        edi  
0066084C  push        esi  
0066084D  push        ebx  
0066084E  sub         esp,4Ch  
00660851  lea         edi,[ebp-58h]  
00660854  mov         ecx,13h  
00660859  xor         eax,eax  
0066085B  rep stos    dword ptr es:[edi]  
0066085D  cmp         dword ptr ds:[5842F0h],0  
00660864  je          0066086B  
00660866  call        744CFAD0  
0066086B  xor         edx,edx  
0066086D  mov         dword ptr [ebp-3Ch],edx  
00660870  xor         edx,edx  
00660872  mov         dword ptr [ebp-48h],edx  
00660875  xor         edx,edx  
00660877  mov         dword ptr [ebp-44h],edx  
0066087A  xor         edx,edx  
0066087C  mov         dword ptr [ebp-40h],edx  
0066087F  nop  
            Sealed sealedClass = new Sealed();
00660880  mov         ecx,584E1Ch  
00660885  call        005730F4  
0066088A  mov         dword ptr [ebp-4Ch],eax  
0066088D  mov         ecx,dword ptr [ebp-4Ch]  
00660890  call        00660468  
00660895  mov         eax,dword ptr [ebp-4Ch]  
00660898  mov         dword ptr [ebp-3Ch],eax  
            sealedClass.DoStuff();
0066089B  mov         ecx,dword ptr [ebp-3Ch]  
0066089E  cmp         dword ptr [ecx],ecx  
006608A0  call        00660460  
006608A5  nop  
            Derived derivedClass = new Derived();
006608A6  mov         ecx,584F3Ch  
006608AB  call        005730F4  
006608B0  mov         dword ptr [ebp-50h],eax  
006608B3  mov         ecx,dword ptr [ebp-50h]  
006608B6  call        006604A8  
006608BB  mov         eax,dword ptr [ebp-50h]  
006608BE  mov         dword ptr [ebp-40h],eax  
            derivedClass.DoStuff();
006608C1  mov         ecx,dword ptr [ebp-40h]  
006608C4  mov         eax,dword ptr [ecx]  
006608C6  mov         eax,dword ptr [eax+28h]  
006608C9  call        dword ptr [eax+10h]  
006608CC  nop  
            Base BaseClass = new Base();
006608CD  mov         ecx,584EC0h  
006608D2  call        005730F4  
006608D7  mov         dword ptr [ebp-54h],eax  
006608DA  mov         ecx,dword ptr [ebp-54h]  
006608DD  call        00660490  
006608E2  mov         eax,dword ptr [ebp-54h]  
006608E5  mov         dword ptr [ebp-44h],eax  
            BaseClass.DoStuff();
006608E8  mov         ecx,dword ptr [ebp-44h]  
006608EB  mov         eax,dword ptr [ecx]  
006608ED  mov         eax,dword ptr [eax+28h]  
006608F0  call        dword ptr [eax+10h]  
006608F3  nop  
        }
0066091A  nop  
0066091B  lea         esp,[ebp-0Ch]  
0066091E  pop         ebx  
0066091F  pop         esi  
00660920  pop         edi  
00660921  pop         ebp  

00660922  ret  

Хотя создание объектов одинаково, инструкции, выполняемые для вызова методов запечатанного и производного / базового классов, немного отличаются. После перемещения данных в регистры или ОЗУ (инструкция mov), вызов запечатанного метода, выполнение сравнения между dword ptr [ecx], ecx (инструкция cmp), а затем вызов метода, в то время как производный / базовый класс выполняет непосредственно метод. .

Согласно отчету, написанному Торбьорном Гранлундом, Задержки выполнения инструкций и пропускная способность для процессоров AMD и Intel x86, скорость выполнения следующей инструкции в Intel Pentium 4 составляет:

  • mov: задержка составляет 1 цикл, и процессор может выдержать 2,5 инструкции за цикл этого типа.
  • cmp: задержка составляет 1 цикл, и процессор может выдерживать 2 инструкции этого типа за цикл.

Ссылка: https://gmplib.org/~tege/x86-timing.pdf

Это означает, что в идеале время, необходимое для вызова запечатанного метода, составляет 2 цикла, в то время как время, необходимое для вызова метода производного или базового класса, составляет 3 цикла.

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

person Ivan Porta    schedule 25.02.2020

Запустите этот код, и вы увидите, что запечатанные классы работают в 2 раза быстрее:

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();

        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 10000000; i++)
        {
            new SealedClass().GetName();
        }
        watch.Stop();
        Console.WriteLine("Sealed class : {0}", watch.Elapsed.ToString());

        watch.Start();
        for (int i = 0; i < 10000000; i++)
        {
            new NonSealedClass().GetName();
        }
        watch.Stop();
        Console.WriteLine("NonSealed class : {0}", watch.Elapsed.ToString());

        Console.ReadKey();
    }
}

sealed class SealedClass
{
    public string GetName()
    {
        return "SealedClass";
    }
}

class NonSealedClass
{
    public string GetName()
    {
        return "NonSealedClass";
    }
}

вывод: класс Sealed: 00: 00: 00.1897568 NonSealed класс: 00: 00: 00.3826678

person RuslanG    schedule 26.11.2009
comment
С этим есть пара проблем. Во-первых, вы не сбрасываете секундомер между первым и вторым тестами. Во-вторых, способ вызова метода означает, что все коды операций будут вызывать, а не callvirt, поэтому тип не имеет значения. - person Cameron MacFarland; 18.12.2009
comment
RuslanG, Вы забыли вызвать watch.Reset () после запуска первого теста. о_О:] - person ; 15.01.2011
comment
Да, в приведенном выше коде есть ошибки. Опять же, кто может похвастаться тем, что никогда не допускает ошибок в коде? Но этот ответ имеет одну важную вещь лучше всех остальных: он пытается измерить рассматриваемый эффект. И этот ответ также содержит код, который вы все проверяете и [массовое] голосование против (интересно, почему необходимо массовое голосование против?). В отличие от других ответов. На мой взгляд, это заслуживает уважения ... Также обратите внимание, что пользователь новичок, так что тоже не очень гостеприимный подход. Несколько простых исправлений, и этот код станет полезным для всех нас. Исправьте + поделитесь фиксированной версией, если осмелитесь - person Roland Pihlakas; 08.12.2016
comment
Я не согласен с @RolandPihlakas. Как вы сказали, кто может похвастаться, чтобы никогда не вносить ошибок в код. Ответ - ›Нет, нет. Но дело не в этом. Дело в том, что это неверная информация, которая может ввести в заблуждение другого нового программиста. Многие люди могут легко упустить из виду, что секундомер не был сброшен. Они могли поверить, что контрольная информация верна. Разве это не вреднее? Кто-то, кто быстро ищет ответ, может даже не читать эти комментарии, скорее он / она увидит ответ и может поверить в это. - person Emran Hussain; 17.05.2017