Стоит задача максимального увеличения производительности в определенной части приложения. Почитав некоторые статьи хабра и где-то когда-то что-то слышав или читав, принялся, кроме всего прочего, запечатывать классы и методы (а также превращать некоторые небольшие классы в структуры). Уже закоммитив, решил в небольшом приложении проверить, а действительно ли это дает результат.
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
так и вызывается.
Как уже сказали в комментариях, компилятор использует 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
инструкции очень незначителен.
Оборудование для ресторана: новинки профессиональной кухонной техники
Частный дом престарелых в Киеве: комфорт, забота и профессиональный уход
Задача: Есть предложения и нужно получить список похожих слов
У меня есть в GUI три 2D кнопки - влево, вправо, вверхИ мне необходимо реализовать одновременное нажатие кнопок
собственно вопрос - когда в рамках разумности - нельзя обойтись без этого оператора
Сайт парсит контент с определенного донор-сайта через CURLВ данный момент блокировка по IP с этого сайта