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

голоса
118

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

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

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

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

Задан 05/08/2008 в 13:00
источник пользователем
На других языках...                            


11 ответов

голоса
46

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

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

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

Вот одна ссылка упоминая об этом: Рамблинг на герметичном ключевое слово

Ответил 05/08/2008 в 13:32
источник пользователем

голоса
3

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

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

Ответил 05/08/2008 в 13:37
источник пользователем

голоса
2

@Vaibhav, какие из тестов вы выполнить для измерения производительности?

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

SSCLI (ротор)
SSCLI: Shared Source Common Language Infrastructure

Common Language Infrastructure (CLI) является стандартом ECMA, который описывает ядро ​​.NET Framework. Shared Source CLI (SSCLI), также известный как ротор, представляет собой сжатый архив исходного кода к рабочей реализации ECMA CLI и спецификация # языка ECMA C, технологии на основе .NET архитектуры Microsoft.

Ответил 05/08/2008 в 13:40
источник пользователем

голоса
3

<Вне темы-декламация>

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

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

</ Офф-тема-декламация>

Ответил 14/10/2008 в 21:04
источник пользователем

голоса
133

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

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

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

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

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

Вызов и callvirt

Ответил 24/12/2008 в 03:09
источник пользователем

голоса
4

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

Есть случаи , когда , cscвозможно , придется издавать callvirtопкод вместо callопкода. Тем не менее, кажется , эти случаи редки.

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

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

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

Ответил 20/11/2009 в 10:53
источник пользователем

голоса
-9

Выполнив этот код, и вы увидите, что запечатанные классы 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";
    }
}

Выход: Запечатанный класс: 00: 00: 00,1897568 NonSealed класс: 00: 00: 00,3826678

Ответил 26/11/2009 в 18:50
источник пользователем

голоса
3

Я считаю «запечатан» классов нормальный случай и у меня всегда есть повод опускать «запечатанный» ключевое слово.

Наиболее важные причины для меня являются:

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

и верхняя причина:

б) злоупотребление моих классов не исключено, что путь

Я желаю Microsoft сделал бы «запечатан» стандарт, а не «распечатал».

Ответил 03/08/2010 в 15:27
источник пользователем

голоса
20

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

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


Детали

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

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

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

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

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

Некоторые C (включая х86 от Intel недавнего) использует технику , называемую спекулятивным выполнение , чтобы использовать трубопровод даже на ситуации. Просто упреждающий один из пути выполнения. Но удар скорость этого метода не столь высока. И неудача спекуляции вызывает срыв трубопровода , который также делает огромные потери производительности. (это полностью за счет внедрения процессора. некоторые мобильный процессор известен как не такой оптимизации для экономии энергии)

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


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

Ответил 03/02/2011 в 16:22
источник пользователем

голоса
2

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

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

Я знаю, что некоторые здесь говорю, что они ненавидят запечатанные классы, потому что они хотят возможность извлечь из ничего ... но это ЧАСТО не самых ремонтопригоден выбора ... потому что выставляя класс ДИФФЕРЕНЦ замки вас в гораздо больше, чем не подвергая все что. Его подобно говоря: «Я ненавижу классы, которые имеют закрытые член ... Я часто не могу сделать класс делать то, что я хочу, потому что у меня нет доступа.» Инкапсуляция важно ... герметизация одна форма инкапсуляции.

Ответил 01/10/2011 в 11:37
источник пользователем

голоса
18

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

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

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

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


Ответ на языке оригинала:

Я сделал следующую тестовую программу, а затем декомпилирован его с помощью отражателя, чтобы увидеть, что излучаемый 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 ворваться после того как программа была завершена, и просмотреть dissasembly из JIT компилируется код x86.

Как вы можете видеть из приведенного ниже кода, при работе вне отладчика ЛТ - компилятор более агрессивен, и он встраиваются в WriteItметод прямо в вызывающей программе . Решающая вещь , однако, что она была идентична при вызове запечатанных против негерметичного класса. Там нет никакой разницы между запечатанным или nonsealed классом.

Здесь при вызове обычного класса:

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

Vs запечатанного класса:

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

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

Ответил 20/02/2012 в 21:45
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more