Mono csharp AOT Оптимизации - PullRequest
0 голосов
/ 27 февраля 2020

Я начинаю изучать C#, исходя из фона C ++. Я хотел узнать о модели памяти C# и сравнить ее с моделью C ++. При этом я нашел статью Модель памяти C# в теории и на практике . Пока что ничего удивительного в этом нет, но я попытался воспроизвести оптимизацию компилятора в статье, которая удаляет дополнительное чтение памяти и столкнулась с проблемой. Я использую моно-компилятор csharp и не могу воспроизвести оптимизацию. Вот код C#:

// test.cs

class MainApp {
  static void Main() {
    Foo foo = new Foo();
    foo.bar();
  }
}

class Foo {
  private int _A = 0, _B = 1;
  public bool bar() {
    if (_B == -1) throw new Exception();
    int a = _A;
    int b = _B;
    return a > b;
  }
}

Затем я запускаю следующие команды компиляции:

mcs -optimize+ test.cs
mono --aot -O=all test.exe

Когда я проверяю вывод objdump -d test.exe.so, я вижу следующее (соответствующее ) строки сборки:

0000000000000500 <Foo_bar>:
 500:   48 83 ec 08             sub    $0x8,%rsp
 504:   48 89 3c 24             mov    %rdi,(%rsp)
 508:   48 8b c7                mov    %rdi,%rax
 50b:   48 63 40 14             movslq 0x14(%rax),%rax
 50f:   83 f8 ff                cmp    $0xffffffff,%eax
 512:   74 1b                   je     52f <Foo_bar+0x2f>
 514:   48 8b 0c 24             mov    (%rsp),%rcx
 518:   48 63 41 10             movslq 0x10(%rcx),%rax
 51c:   48 63 49 14             movslq 0x14(%rcx),%rcx
 520:   3b c1                   cmp    %ecx,%eax
 522:   40 0f 9f c0             setg   %al
 526:   48 0f b6 c0             movzbq %al,%rax
 52a:   48 83 c4 08             add    $0x8,%rsp
 52e:   c3                      retq  

 ... # exception stuff

Итак, инструкции 50b, 518 и 51c, кажется, указывают, что чтение все еще повторяется, даже если может быть оптимизирован. У меня вопрос: я делаю что-то не так, это упущенная возможность оптимизации, или здесь есть какая-то другая проблема (какая-то веская причина, почему эта оптимизация не состоялась)? Сейчас у меня нет доступа к Visual Studio, мне было бы интересно узнать, действительно ли он выполняет эту оптимизацию.

В статье утверждается, что я должен получить что-то вроде:

push        eax
mov         edx,dword ptr [ecx+8]
cmp         edx,0FFFFFFFFh
je          00000016
mov         eax,dword ptr [ecx+4]
cmp         eax,edx

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

class Foo {
  int _A{0};
  int _B{1};

  public:
  __attribute__ ((noinline)) bool bar() volatile {
    if (_B == -1) throw 0;
    int a = _A;
    int b = _B;
    return a > b;
  }
};

int main(int argc, char **argv) {
  volatile Foo foo;
  foo.bar();
}

Приводит к следующей (соответствующей) сборке:

00000000000007e4 <_ZNV3Foo3barEv>:
 7e4:   8b 47 04                mov    0x4(%rdi),%eax
 7e7:   83 f8 ff                cmp    $0xffffffff,%eax
 7ea:   74 0b                   je     7f7 <_ZNV3Foo3barEv+0x13>
 7ec:   8b 17                   mov    (%rdi),%edx
 7ee:   8b 47 04                mov    0x4(%rdi),%eax
 7f1:   39 c2                   cmp    %eax,%edx
 7f3:   0f 9f c0                setg   %al
 7f6:   c3                      retq   

 ... # exception stuff

Таким образом, оптимизация здесь также не происходит (хотя, если честно, без ((noinline)) этот код даже не отображается в объектном файле.) Вот то же самое из clang:

0000000000400610 <_ZNV3Foo3barEv>:
  400610:   50                      push   %rax
  400611:   8b 47 04                mov    0x4(%rdi),%eax
  400614:   83 f8 ff                cmp    $0xffffffff,%eax
  400617:   74 0a                   je     400623 <_ZNV3Foo3barEv+0x13>
  400619:   8b 07                   mov    (%rdi),%eax
  40061b:   3b 47 04                cmp    0x4(%rdi),%eax
  40061e:   0f 9f c0                setg   %al
  400621:   59                      pop    %rcx
  400622:   c3                      retq   

 ... # exception stuff

Так что еще есть дополнительное чтение, просто "встроенное" в cmp инструкция.

релевантно --version

Mono C# compiler version 4.6.2.0
g++ (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)

1 Ответ

1 голос
/ 27 февраля 2020

Я бросил ваш код на SharpLab и получил:

    L0000: push rsi
    L0001: sub rsp, 0x20
    L0005: mov eax, [rcx+0xc]
    L0008: cmp eax, 0xffffffff
    L000b: jz L001e
    L000d: mov ecx, [rcx+0x8]
    L0010: cmp ecx, eax
    L0012: setg al
    L0015: movzx eax, al
    L0018: add rsp, 0x20
    L001c: pop rsi
    L001d: ret
    L001e: mov rcx, 0x7ffa2f8d4170
    L0028: call 0x7ffa8f384690
    L002d: mov rsi, rax
    L0030: mov rcx, rsi
    L0033: call System.Exception..ctor()
    L0038: mov rcx, rsi
    L003b: call 0x7ffa8f33a4f0
    L0040: int3

Моя сборка более ржавая, чем ваша, но я могу видеть, что к каждому полю обращаются только один раз?

Обратите внимание, что. NET Core JIT является 2-уровневым, и SharpLab показывает только первый уровень, так что это может быть дополнительно оптимизировано, если выяснится, что он находится на горячем пути.

Поэтому это выглядит как Моно вещь?

...