Является ли «With ... End With» действительно более эффективным? - PullRequest
4 голосов
/ 04 ноября 2010

Так что я играю с ILDASM и заметил странность, которой не могу найти действительно хорошее объяснение в Google.

Кажется, что при использовании блоков With в VB.NET результирующий MSIL будет больше, чем без. Так что это заставляет меня спросить, действительно ли With Blocks более эффективны? MSIL - это то, что превращает JIT в собственный машинный код, поэтому меньший размер кода должен означать более эффективный код, верно?

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

Friend Class Class2
    Friend Sub New()
        Dim c1 As New Class1

        c1.One = "foobar"
        c1.Two = 23009
        c1.Three = 3987231665
        c1.Four = 2874090071765301873
        c1.Five = 3.1415973801462975
        c1.Six = "a"c
    End Sub
End Class

Friend Class Class3
    Friend Sub New()
        Dim c1 As New Class1

        With c1
            .One = "foobar"
            .Two = 23009
            .Three = 3987231665
            .Four = 2874090071765301873
            .Five = 3.1415973801462975
            .Six = "a"c
        End With
    End Sub
End Class

Вот полученный MSIL для Class2:

.method assembly specialname rtspecialname 
        instance void  .ctor() cil managed
{
    // Code size       84 (0x54)
    .maxstack  2
    .locals init ([0] class WindowsApplication1.Class1 c1)
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  newobj     instance void WindowsApplication1.Class1::.ctor()
    IL_000b:  stloc.0
    IL_000c:  ldloc.0
    IL_000d:  ldstr      "foobar"
    IL_0012:  callvirt   instance void WindowsApplication1.Class1::set_One(string)
    IL_0017:  ldloc.0
    IL_0018:  ldc.i4     0x59e1
    IL_001d:  callvirt   instance void WindowsApplication1.Class1::set_Two(int16)
    IL_0022:  ldloc.0
    IL_0023:  ldc.i4     0xeda853b1
    IL_0028:  callvirt   instance void WindowsApplication1.Class1::set_Three(uint32)
    IL_002d:  ldloc.0
    IL_002e:  ldc.i8     0x27e2d1b1540c3a71
    IL_0037:  callvirt   instance void WindowsApplication1.Class1::set_Four(uint64)
    IL_003c:  ldloc.0
    IL_003d:  ldc.r8     3.1415973801462975
    IL_0046:  callvirt   instance void WindowsApplication1.Class1::set_Five(float64)
    IL_004b:  ldloc.0
    IL_004c:  ldc.i4.s   97
    IL_004e:  callvirt   instance void WindowsApplication1.Class1::set_Six(char)
    IL_0053:  ret
} // end of method Class2::.ctor

А вот MSIL для Class3:

.method assembly specialname rtspecialname 
        instance void  .ctor() cil managed
{
    // Code size       88 (0x58)
    .maxstack  2
    .locals init ([0] class WindowsApplication1.Class1 c1,
                  [1] class WindowsApplication1.Class1 VB$t_ref$L0)
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  newobj     instance void WindowsApplication1.Class1::.ctor()
    IL_000b:  stloc.0
    IL_000c:  ldloc.0
    IL_000d:  stloc.1
    IL_000e:  ldloc.1
    IL_000f:  ldstr      "foobar"
    IL_0014:  callvirt   instance void WindowsApplication1.Class1::set_One(string)
    IL_0019:  ldloc.1
    IL_001a:  ldc.i4     0x59e1
    IL_001f:  callvirt   instance void WindowsApplication1.Class1::set_Two(int16)
    IL_0024:  ldloc.1
    IL_0025:  ldc.i4     0xeda853b1
    IL_002a:  callvirt   instance void WindowsApplication1.Class1::set_Three(uint32)
    IL_002f:  ldloc.1
    IL_0030:  ldc.i8     0x27e2d1b1540c3a71
    IL_0039:  callvirt   instance void WindowsApplication1.Class1::set_Four(uint64)
    IL_003e:  ldloc.1
    IL_003f:  ldc.r8     3.1415973801462975
    IL_0048:  callvirt   instance void WindowsApplication1.Class1::set_Five(float64)
    IL_004d:  ldloc.1
    IL_004e:  ldc.i4.s   97
    IL_0050:  callvirt   instance void WindowsApplication1.Class1::set_Six(char)
    IL_0055:  ldnull
    IL_0056:  stloc.1
    IL_0057:  ret
} // end of method Class3::.ctor

Единственное существенное отличие, которое я могу заметить с первого взгляда, - это использование кода операции ldloc.1 над ldloc.0. Согласно MSDN, разница между этими двумя значениями незначительна: ldloc.0 является эффективным методом использования ldloc для доступа к локальной переменной с индексом 0, а ldloc.1 - тем же, только для индекса 1.

Обратите внимание, что размер кода Class3 равен 88 против 84. Они взяты из релизных / оптимизированных сборок. Встроенный VB Express 2010, клиентский профиль .NET 4.0 Framework.

Мысли?

EDIT:
Хотелось бы добавить тем, кто спотыкается в этой теме, общую суть ответов, насколько я их понимаю.

Разумное использование With ... End With:

With ObjectA.Property1.SubProperty7.SubSubProperty4
    .SubSubSubProperty1 = "Foo"
    .SubSubSubProperty2 = "Bar"
    .SubSubSubProperty3 = "Baz"
    .SubSubSubProperty4 = "Qux"
End With

Неразумное использование With ... End With:

With ObjectB
    .Property1 = "Foo"
    .Property2 = "Bar"
    .Property3 = "Baz"
    .Property4 = "Qux"
End With

Причина в том, что в примере ObjectA вы опускаете несколько членов, и каждое разрешение этого члена требует некоторой работы, поэтому, только разрешая ссылки один раз и вставляя окончательную ссылку во временную переменную (что все With на самом деле), это ускоряет доступ к свойствам / методам, скрытым глубоко в этом объекте.

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

Ответы [ 5 ]

5 голосов
/ 04 ноября 2010

Глядя на код IL, блок With делает в основном:

Friend Class Class3
  Friend Sub New()
    Dim c1 As New Class1
    Dim temp as Class1 = c1
    temp.One = "foobar"
    temp.Two = 23009
    temp.Three = 3987231665
    temp.Four = 2874090071765301873
    temp.Five = 3.1415973801462975
    temp.Six = "a"c
    temp = Nothing
  End Sub
End Class

Но важно то, что компилятор JIT делает из этого.Компилятор языка не выполняет много оптимизаций, в основном он оставлен для JIT-компилятора.Скорее всего, он увидит, что переменная c1 не используется ни для чего, кроме создания другой переменной, и полностью оптимизирует хранилище c1.

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

3 голосов
/ 04 ноября 2010

Это поучительный раздел из класса, в котором используется оператор With:

IL_000b:  stloc.0
IL_000c:  ldloc.0
IL_000d:  stloc.1
IL_000e:  ldloc.1

Инструкции с нулевым индексом появляются в классе, который not использует оператор With какну, и они соответствуют экземпляру c1 в источнике (Dim c1 As New Class1)

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

Вы также видите, что вы видите ldloc.1 вместо ldloc.0 в IL для класса, который использует Withзаявление.Это связано с тем, что используется ссылка на локальную переменную, созданную оператором With (вторая переменная в стеке оценки), в отличие от первой переменной в стеке оценки (создание экземпляра Class1 в качестве переменной c1).

3 голосов
/ 04 ноября 2010

Оператор With фактически добавляет больше кода, чтобы обеспечить его семантическую корректность.

Если вы изменили свой код следующим образом:

Dim c1 As New Class1
With c1
    .One = "foobar"
    .Two = 23009
    .Three = 3987231665
    .Four = 2874090071765301873
    .Five = 3.1415973801462975
    c1 = New Class1
    .Six = "a"c
End With

Вы бы, я надеюсь,ожидаем, что свойство .Six по-прежнему присваивается исходному c1, а не второму.

Итак, под капотом компилятор делает это:

Dim c1 As New Class1
Dim VB$t_ref$L0 As Class1 = c1
VB$t_ref$L0.One = "foobar"
VB$t_ref$L0.Two = &H59E1
VB$t_ref$L0.Three = &HEDA853B1
VB$t_ref$L0.Four = &H27E2D1B1540C3A71
VB$t_ref$L0.Five = 3.1415973801462975
VB$t_ref$L0.Six = "a"c
VB$t_ref$L0 = Nothing

Он создаеткопия переменной With, чтобы любые последующие присваивания не изменяли семантику.

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

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

Разница в производительности незначительна.Используйте With только в том случае, если это способствует удобочитаемости.

3 голосов
/ 04 ноября 2010

Я не использую такие вещи, как with, чтобы ускорить мой код. Любой достойный компилятор должен генерировать точно такой же код . Я был бы удивлен, если какой-либо компилятор в настоящее время не имеет общего исключения подвыражения, так что:

                 with a.b.c:
a.b.c.d = 1;         .d = 1;
a.b.c.e = 2;         .e = 2;
a.b.c.f = 3;         .f = 3;
                 end with

были идентичны с точки зрения того, что было сгенерировано под обложками. Это не было бы первым разом, когда Microsoft удивила меня: -)

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


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

Как только будет принято решение JIT для этого куска кода (например, из-за его интенсивного использования), с этого момента я начну применять оптимизацию. Таким образом, ваш компилятор может быть проще в том смысле, что ему не нужно беспокоиться об оптимизации, где это может не понадобиться: YAGNI.

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

0 голосов
/ 04 ноября 2010

Это зависит от того, как вы его используете. Если вы используете:

With someobject.SomeHeavyProperty
   .xxx 
End With

оператор With спасет некоторый вызов получателю свойства В противном случае эффект должен быть аннулирован JIT.

...