sealed, virtual, невиртуальные методы в C# и производительность

179
08 февраля 2019, 17:30

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

class X
{
    public int NonVirtual() => DateTime.Now.Millisecond;
    public virtual int Virtual() => DateTime.Now.Minute;
}
class Y : X
{
    public override int Virtual() => DateTime.Now.Millisecond;
}
class Program
{
    private static volatile int TST = 0;
    static int Slow(Y x) => x.Virtual();
    static int Fast(Y y) => y.NonVirtual();
    static void Main(string[] args)
    {
        int i = 0;
        var stopwatch1 = new Stopwatch();
        var stopwatch2 = new Stopwatch();
        stopwatch1.Start();
        var y = new Y();
        for (i = 0; i < 100000000; i++)
        {
            TST = Fast(y);
        }
        stopwatch1.Stop();
        Console.WriteLine("Time elapsed: {0}", stopwatch1.Elapsed);
        stopwatch2.Start();
        for (i = 0; i < 100000000; i++)
        {
            TST = Slow(y);
        }
        stopwatch2.Stop();
        Console.WriteLine("Time elapsed: {0}", stopwatch2.Elapsed);
        Console.ReadKey();
    }
}

Поле TST объявлено как volatile, чтобы компилятор не оптимизировал вызовы.

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

Тогда я полез в IL:

IL_0001: callvirt  instance int32 PerformanceTests.X::Virtual()

Это вызов виртуального метода. Вроде всё нормально. Второй вызов:

IL_0001: callvirt  instance int32 PerformanceTests.X::NonVirtual()

Что, простите? callvirt? Разве тут не должно быть call? sealed так-же никак не влияет на вызов виртуального метода.

Хотел бы выяснить у более опытных коллег, всё-таки, есть ли смысл в запечатывании классов в плане производительности? А так-же, почему вызов невиртуальной функции в IL такой же как и виртуальной?

Update: В комментариях @Grundy написал, что callvirt - из-за того, что метод объявлен в базовом классе. Переписал код так, что теперь используется базовый класс (т.е. Y - не используется). callvirt так и вызывается.

Answer 1

Как уже сказали в комментариях, компилятор использует callvirt что бы генерировать NullReferenceException.

Что бы получить чистую call инструкцию компилятор должен быть уверен, что экземпляр класса не может быть null. Пример:

class Test
{
    public void Method() => Console.WriteLine(123);
}
static void Main(string[] args)
{
    new Test().Method();
}

IL-код:

IL_0001: newobj instance void ConsoleApp1.Program/Test::.ctor()
IL_0006: call instance void ConsoleApp1.Program/Test::Method()

Если код немного изменить:

static Test GetTest() => new Test();
static void Main(string[] args)
{
    GetTest().Method();
}

То уже получаем callvirt, так как компилятор предполает, что GetTest может вернуть null:

IL_0001: call class ConsoleApp1.Program/Test ConsoleApp1.Program::GetTest()
IL_0006: callvirt instance void ConsoleApp1.Program/Test::Method()

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

Для каждого callvirt невиртуального метода JIT вставляет одну дополнительную инструкцию перед каждым call:

cmp         dword ptr [/*здесь регистр с адресом экземляра*/],ecx  
call        00007FF9CD540098  // метод

Эффект от одной cmp инструкции очень незначителен.

READ ALSO
Как можно эффективно найти опечатки в словах?

Как можно эффективно найти опечатки в словах?

Задача: Есть предложения и нужно получить список похожих слов

178
Unity3d Реализация Мультитача

Unity3d Реализация Мультитача

У меня есть в GUI три 2D кнопки - влево, вправо, вверхИ мне необходимо реализовать одновременное нажатие кнопок

183
В каких случаях необходимо или удобнее использовать оператор yeild?

В каких случаях необходимо или удобнее использовать оператор yeild?

собственно вопрос - когда в рамках разумности - нельзя обойтись без этого оператора

173
Как использовать прокси с Symfony?

Как использовать прокси с Symfony?

Сайт парсит контент с определенного донор-сайта через CURLВ данный момент блокировка по IP с этого сайта

207