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

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

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

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

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

}
double prevSpec_OilCons = 0;

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

Я использую .net4.5 и vs2013


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

9 ответов


Ну переменные 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. Основываясь на этом ответ


цикл не могут быть удалены код не мертвый, например:

  // 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 и именно поэтому он сохраняет цикл


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

j++;

и сравнение

j<TestRunTime.Length;

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

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


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


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

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

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

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


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

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

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

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


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

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

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

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

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

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

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


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

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

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

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

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


я сделал небольшую форму, чтобы проверить ее в соответствии с несколькими идеями ответчиков об использовании 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, Я не хотел тратить год день на этом.

Как видите:

---------------------------------------------------------------------
|                   |   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    |
---------------------------------------------

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