Подтвердить что ты не робот

Будет ли мой компилятор игнорировать бесполезный код?

У меня было несколько вопросов по сети по этому вопросу, но я не нашел ответа на мой вопрос, или для другого языка или он не отвечает полностью (мертвый код не бесполезный код), так вот мой вопрос:

Является ли (явным или нет) бесполезным кодом, игнорируемым компилятором?

Например, в этом коде:

double[] TestRunTime = SomeFunctionThatReturnDoubles;
// A bit of code skipped
int i = 0;
for (int j = 0; j < TestRunTime.Length; j++)
{

}
double prevSpec_OilCons = 0;

будет ли цикл for удален?

Я использую и


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

4b9b3361

Ответ 1

Я сделал небольшую форму, чтобы проверить ее в соответствии с несколькими соображениями об использовании long.MaxValue, здесь мой ссылочный код:

public Form1()
{
    InitializeComponent();
    Stopwatch test = new Stopwatch();
    test.Start();
    myTextBox.Text = test.Elapsed.ToString();
}

и вот код с любопытным бесполезным кодом:

public Form1()
{
    InitializeComponent();
    Stopwatch test = new Stopwatch();
    test.Start();
    for (int i = 0; i < int.MaxValue; i++)
    {
    }
    myTextBox.Text = test.Elapsed.ToString();
}

Вы заметите, что я использовал int.MaxValue вместо long.MaxValue, я не хотел тратить на это year на этом.

Как вы можете видеть:

---------------------------------------------------------------------
|                   |   Debug               |   Release             |
---------------------------------------------------------------------
|Ref                |   00:00:00.0000019    |   00:00:00.0000019    |
|Useless code       |   00:00:05.3837568    |   00:00:05.2728447    |
---------------------------------------------------------------------

Код не оптимизирован. Подождите немного, я попробую с помощью int[] проверить int[].Lenght:

public Form1()
{
    InitializeComponent();
    int[] myTab = functionThatReturnInts(1);

    Stopwatch test = new Stopwatch();
    test.Start();
    for (int i = 0; i < myTab.Length; i++)
    {

    }
    myTextBox.Text = test.Elapsed.ToString();
}
public int[] functionThatReturnInts(int desiredSize)
{
    return Enumerable.Repeat(42, desiredSize).ToArray();
}

И вот результаты:

---------------------------------------------
|   Size            |   Release             |
---------------------------------------------
|             1     |   00:00:00.0000015    |
|           100     |   00:00:00            |
|        10 000     |   00:00:00.0000035    |
|     1 000 000     |   00:00:00.0003236    |
|   100 000 000     |   00:00:00.0312673    |
---------------------------------------------

Таким образом, даже с массивами он не оптимизируется вообще.

Ответ 2

Ну, ваши переменные i и prevSpec_OilCons, если они не используются нигде, будут оптимизированы, но не ваш цикл.

Итак, если ваш код выглядит так:

static void Main(string[] args)
{
    int[] TestRunTime = { 1, 2, 3 };
    int i = 0;
    for (int j = 0; j < TestRunTime.Length; j++)
    {

    }
    double prevSpec_OilCons = 0;
    Console.WriteLine("Code end");
}

под ILSpy будет:

private static void Main(string[] args)
{
    int[] TestRunTime = new int[]
    {
        1,
        2,
        3
    };
    for (int i = 0; i < TestRunTime.Length; i++)
    {
    }
    Console.WriteLine("Code end");
}

Поскольку цикл имеет пару операторов, например сравнение и приращение, его можно использовать для выполнения нескольких коротких периодов задержки/ожидания. (хотя это не очень хорошая практика).

Рассмотрим следующий цикл, который представляет собой пустой цикл, но для выполнения выполнения потребуется много времени.

for (long j = 0; j < long.MaxValue; j++)
{

}

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

if (false)
{
    Console.Write("Shouldn't be here");
}

Цикл, даже не будет удален с помощью .NET jitters. Основываясь на этом ответе

Ответ 3

Цикл не может быть удален, код не мертв, например:

  // Just some function, right?
  private static Double[] SomeFunctionThatReturnDoubles() {
    return null;
  }

  ...
  double[] TestRunTime = SomeFunctionThatReturnDoubles();
  ...
  // You'll end up with exception since TestRunTime is null
  for (int j = 0; j < TestRunTime.Length; j++)
  {
  }
  ...

Обычно компилятор просто не может предсказать все возможные результаты SomeFunctionThatReturnDoubles и поэтому он сохраняет цикл

Ответ 4

В вашем цикле есть две операции, неявные в каждой итерации. Инкремент:

j++;

и сравнение

j<TestRunTime.Length;

Итак, цикл не пуст, хотя он выглядит так. В конце что-то выполняется, и, конечно, это не игнорируется компилятором.

Это происходит и в других циклах.

Ответ 5

Он не будет игнорироваться. Однако, когда вы доберетесь до IL, появится инструкция перехода, поэтому for будет работать так, как если бы это была инструкция if. Он также будет запускать код для ++ и length, как упоминал @Fleve. Это будет просто дополнительный код. Для удобства чтения, а также для соблюдения стандартов кода я бы удалил код, если вы его не используете.

Ответ 6

Является ли (явным или нет) бесполезным кодом, игнорируемым компилятором?

Вы не можете легко определить, что это бесполезно, поэтому компилятор тоже не может. Геттер TestRunTime.Length может иметь побочные эффекты, например.

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

Прежде чем реорганизовать фрагмент кода, вы должны проверить, что он делает, чтобы иметь возможность изменить его и сказать, что он по-прежнему имеет тот же результат. Групповые тесты - отличный способ сделать это.

Ответ 7

JIT в основном способен удалять мертвый код. Это не очень тщательно. Мертвые переменные и выражения надежно убиты. Это простая оптимизация в форме SSA.

Не уверен относительно потока управления. Если вы вставляете две петли, только внутренняя будет удалена. Я помню.

Если вы хотите точно узнать, что удалено, а что нет, посмотрите на сгенерированный код x86. Компилятор С# делает очень мало оптимизаций. JIT делает несколько.

4.5 32 и 64-разрядные JIT - это разные кодовые базы и имеют другое поведение. Появляется новый JIT (RyuJIT), который в моем тестировании обычно хуже, иногда лучше.

Ответ 8

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

В вашем случае первая интересная вещь: используется ли переменная j после цикла или нет. Другая интересная вещь - TestRunTime.Length. Компилятор будет смотреть на него и проверять, всегда ли он возвращает тот же результат, и если да, есть ли у него какие-либо побочные эффекты, и если да, то ли его вызов один раз имеет такой же побочный эффект, как и его повторение.

Если TestRunTime.Length не имеет побочного эффекта, а j не используется, цикл удаляется.

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

В противном случае j = max (0, TestRunTime.Length).

Далее, компилятор может определить, требуется ли назначение TestRunTime.Length. Он может быть заменен кодом, который просто определяет, что будет TestRunTime.Length.

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

Ответ 9

По большей части вам не следует беспокоиться о проактивном удалении бесполезного кода. Если вы столкнулись с проблемами производительности, и ваш профилировщик говорит, что какой-то бесполезный код ест ваши циклы тактов, тогда идите на него. Однако, если код действительно ничего не делает и не имеет побочных эффектов, он, вероятно, мало повлияет на время работы.

Тем не менее, большинство компиляторов не обязаны выполнять какие-либо оптимизации, поэтому полагаться на оптимизацию компилятора не всегда является самым умным вариантом. Однако во многих случаях даже бесполезная спиновая петля может выполняться довольно быстро. Базовая спин-блокировка, которая петли миллион раз, будет скомпилирована во что-то вроде mov eax, 0 \ inc eax \ cmp eax, 1000000 \ jnz -8. Даже если мы снизили оптимизацию на процессорах, это всего 3 цикла за цикл (в недавнем чипе стиля RISC), так как нет доступа к памяти, поэтому не будет никакого аннулирования кэша. На процессоре 1 ГГц это только 3 000 000/1 000 000 000 секунд или 3 миллисекунды. Это было бы довольно значительным ударом, если вы попытались запустить его 60 раз в секунду, но во многих случаях это, вероятно, даже не будет заметно.

Петля, подобная той, которую я описал, почти наверняка была бы гладкостью, оптимизированной до mov eax 1000000, даже в среде JIT. Вероятнее всего, это будет оптимизировано, но при отсутствии другого контекста эта оптимизация будет разумной и не вызовет никаких негативных последствий.

tl; dr: Если ваш профайлер говорит, что мертвый/бесполезный код использует заметную сумму ваших ресурсов во время выполнения, удалите его. Не ходите на охоту на ведьм за мертвый код; оставьте это для перезаписи/массивного рефакторинга по линии.

Бонус: если генератор кода знал, что eax не будет считаться для чего-либо, кроме условия цикла, и хочет сохранить спин-блокировку, он может генерировать mov eax, 1000000 \ dec eax \ jnz -3 и уменьшать штраф цикла за счет цикл. Большинство компиляторов просто удалили бы его полностью, хотя.