Могут ли оптимизированные сборки и JIT-компиляция создавать проблемы при изменении переменной через Span <T>? - PullRequest
11 голосов
/ 08 марта 2019

Предположим, я использую MemoryMarshal.CreateSpan для доступа к байтам локального типа значения, например, следующего (не очень полезного) кода:

using System;
using System.Runtime.InteropServices;

// namespace and class boilerplate go here

private static void Main()
{
    int value = 0;
    Span<byte> valueBytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1));

    var random = new Random();
    while (value >= 0) // the check in question
    {
        random.NextBytes(valueBytes);
        Console.WriteLine(value);
    }
}

Хотя этот код работает должным образом, гарантируется ли указанная проверка на выживание при компиляции в IL и JIT без оптимизации до true, учитывая, что переменная value не изменяется в цикле, кроме как косвенно через valueBytes промежуток? Могу ли я рассчитывать на чтение value, дающее мне то, что написано записью в valueBytes, или это может быть уязвимо для переупорядочения? Или я просто параноик, потому что в последнее время немного увлекаюсь C ++?

(Обратите внимание, что я знаю, что есть другие способы достижения ожидаемого эффекта приведенного выше кода, речь не идет о том, как получить 32-разрядное случайное целое число с полным диапазоном, или о проблеме XY в более крупном приложении. в который я пытаюсь вставить этот код, такого более крупного приложения не существует)

1 Ответ

3 голосов
/ 20 марта 2019

Я думаю, что единственный определенный ответ могут дать люди, которые реализуют оптимизацию компилятора, как на стороне Roslyn, так и на стороне RyuJIT.

Поскольку вы используете .NET Core, вы, конечно, можетеПогрузитесь в исходный код и найдите ответ самостоятельно.Это будет ответом для конкретной версии компилятора.

Посмотрите на сгенерированный код IL для вашего фрагмента:

// int value = 0;
ldc.i4.0
stloc.0

// MemoryMarshal.CreateSpan(ref value, 1)
ldloca.s 0
ldc.i4.1
call valuetype System.Span`1<!!0> System.Runtime.InteropServices.MemoryMarshal::CreateSpan<int32>(!!0&, int32)

// the rest is omitted

Обратите внимание, что ldloca.s код операции.Эта операция загружает адрес локальной переменной в стек оценки .

Хотя я не могу предоставить вам официальную ссылку, подтверждающую это, но я уверен, что и C #, и JITкомпиляторы не будут оптимизировать эту локальную переменную - только потому, что был использован ее адрес, поэтому есть вероятность, что эта локальная переменная будет видоизменена через ее адрес.

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

// int value = 0;
xor         ecx,ecx  
mov         dword ptr [rsp+3Ch],ecx 

WHILE_LOOP_START:
// ... do stuff

// effectively: if (value >= 0) goto WHILE_LOOP_START
cmp         dword ptr [rsp+3Ch],0  
jge         WHILE_LOOP_START  

Попробуйте написать код, который не выдает код операции ldloca.s (например, просто ++valueв цикле), переменная value, скорее всего, станет переменной только для регистров.

Если вы измените свой код таким образом, что value никогда не записывается (кроме инициализации), JITкомпилятор фактически полностью исключит проверку и саму переменную:

LOOP:

// Console.WriteLine(0)
xor         ecx,ecx  
call        CONSOLE_WRITE_LINE

// while (true)
jmp         LOOP

Интересно, что компилятор C # не будет делать эту оптимизациюn:

// int value = 0;
ldc.i4.0
stloc.0

br.s WHILE_CHECK

LOOP_START:
// Console.WriteLine(value)
ldloc.0
call void System.Console::WriteLine(int32)

WHILE_CHECK:
// effectively: if (value >= 0) goto LOOP_START
ldloc.0
ldc.i4.0
bge.s LOOP_START

Опять же, IL и код сборки в моем ответе зависят от платформы и компилятора (даже от CLR).Я не могу предоставить вам подтверждающие документы.Но я уверен, что ни один компилятор не оптимизирует локальную переменную, адрес которой был получен и, более того, использован в качестве аргумента при вызове методов / функций.

Может быть, кто-то из команд Roslyn и RyuJIT мог бы дать вамлучший ответ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...