#c_sharp
Добрый день! Прошу Вас подсказать мне. Вопрос: какой код будет выполняться быстрее и почему? Или разницы в скорости не будет, т.к передается ссылка на область в куче. Допустим, есть некоторый тип: class MyClass { internal int a; public MyClass(int a) { this.a = a; } } Вариант 1 class Program { static void Main(string[] args) { MyClass myClass = new MyClass(5); Work(myClass.a); } static void Work(int a) { int b = a + 5; Console.WriteLine(b); } } Вариант 2 class Program { static void Main(string[] args) { MyClass myClass = new MyClass(5); Work(myClass); } static void Work(MyClass myClass) { int b = myClass.a + 5; Console.WriteLine(b); } }
Ответы
Ответ 1
Все значения переменных передаются по значению:в ссылочных типах значение это ссылка, в значимых - значение. Конкретно в вашем случае: пример 1 Work(myClass.a); - Вы обращаетесь к куче и копируете значение из кучи, далее уже работаете с локальной переменной, которая будет храниться в стеке пример 2 Work(myClass); - Вы обращаетесь к переменной myClass и копируете ее значение, т.е. ссылку на область памяти, где хранится объект. В данном случае объектом является класс, поэтому и хранится он в куче. Далее в методе Вы обращаетесь уже к локальной переменной, хранящейся в куче, для извлечения адреса памяти и извлечения значения объекта. На первый взгляд разница очевидна: передавать по значению проще, но представьте ситуацию, когда вам надо передать миллион значений, а использовать только 1. Тогда быстрее будет передать массив ссылок, чем массив значений. Если посмотреть на Ваш случай, то разницы практически не видно, но первый метод будет работать быстрее. О том, где хранятся переменные, можно прочитать здесьОтвет 2
Честнее всего будет ответить на ваш вопрос - "невозможно предсказать, возьмите и померяйте на реальном коде". И вот почему: Вот IL, в который компилируются ваши примеры: IL1: Main IL_0006: ldc.i4.5 IL_0007: newobj instance void ConsoleApplication15.MyClass::.ctor(int32) IL_000c: ldfld int32 ConsoleApplication15.MyClass::a IL_0011: call void ConsoleApplication15.Program::Work(int32) Work IL_0000: ldarg.0 IL_0001: ldc.i4.5 IL_0002: add IL_0003: call void [mscorlib]System.Console::WriteLine(int32) IL_0008: ret IL2: Main IL_0006: ldc.i4.5 IL_0007: newobj instance void ConsoleApplication15.MyClass::.ctor(int32) IL_000c: call void ConsoleApplication15.Program::Work(class ConsoleApplication15.MyClass) Work IL_0000: ldarg.0 IL_0001: ldfld int32 ConsoleApplication15.MyClass::a IL_0006: ldc.i4.5 IL_0007: add IL_0008: call void [mscorlib]System.Console::WriteLine(int32) IL_000d: ret Ок, что видно из этого кода? Компилятор уже немного оптимизировал выполнение. Например, в IL1.Main он пользуется удачным совпадением - параметры в функции передаются через стек, результат вызова конструктора также складывается в стек - а значит для передачи myClass в качестве параметра Work никаких телодвижений делать не нужно. Т.е. того, что видно в C# коде: Вы обращаетесь к переменной myClass и копируете ее значение, т.е. ссылку на область памяти, где хранится объект В IL не происходит. Но все равно - явно видно, что в первом примере кода меньше. А значит можно сделать вывод, что он работает быстрее. Но на самом деле из IL выводы делать нельзя. И вот почему: IL код не выполняется напрямую. Он компилируется в машинный код под конкретную платформу. А в конкретной платформе кроме стека могут использоваться и другие механизмы передачи параметров В реальности передача параметров может происходить не через железный стек. Более того, в CLR на x86/x64 первые два параметра передаются через регистры rcx/rdx (ecx/edx). А результат из метода возвращается через rax/eax Вот во что реально компилируется ваш код на x64 (на моей машине): ASM1: Main MyClass myClass = new MyClass(5); 00007FF8F6F20489 mov rcx,7FF8F6E15A88h 00007FF8F6F20493 call 00007FF956512510 00007FF8F6F20498 mov dword ptr [rax+8],5 Work(myClass.a); 00007FF8F6F2049F mov ecx,dword ptr [rax+8] 00007FF8F6F204A2 call 00007FF8F6F20080 Work: 00007FF8F6F204D4 add ecx,5 00007FF8F6F204D7 call 00007FF954497BE0 ASM2: Main: MyClass myClass = new MyClass(5); 00007FF8F6EF0489 mov rcx,7FF8F6DE5A98h 00007FF8F6EF0493 call 00007FF956512510 00007FF8F6EF0498 mov rcx,rax 00007FF8F6EF049B mov dword ptr [rcx+8],5 Work(myClass); 00007FF8F6EF04A2 call 00007FF8F6EF0088 Work 00007FF8F6F204D4 mov ecx,dword ptr [rcx+8] 00007FF8F6F204D7 add ecx,5 00007FF8F6F204DA call 00007FF954497BE0 Ок, чем это отличается от IL? Тем, что стек не используется. Совсем. Оптимизатор все запихнул в регистры. Чем реально отличаются варианты? Одной инструкцией: 00007FF8F6EF0498 mov rcx,rax mov из регистра в регистр занимает 1 такт процессора. Т.е. если какой-то из примеров и будет работать быстрее на x64 - вы этого скорее всего не заметите. Производительность зависит от слишком многих параметров - платформы, версии JIT, типа компиляции - и невозможно учесть их все при написании кода. Не заморачивайтесь. Решайте проблемы по мере их появления.
Комментариев нет:
Отправить комментарий