私はこの主題についてネットワークを介していくつかの質問をしましたが、私の質問に対する答えが見つかりませんでした、またはそれは 別の言語の場合 またはそうではありません 完全に答えます (デッドコードはnot役に立たないコードです)だからここに私の質問があります:
(明示的かどうかにかかわらず)役に立たないコードはコンパイラーによって無視されますか?
たとえば、次のコードでは次のようになります。
double[] TestRunTime = SomeFunctionThatReturnDoubles;
// A bit of code skipped
int i = 0;
for (int j = 0; j < TestRunTime.Length; j++)
{
}
double prevSpec_OilCons = 0;
forループは削除されますか?
背景は私がたくさんのコードを維持しているということです (私が書いていなかった) そして、役に立たないコードをターゲットにするべきなのか、それともコンパイラに処理させることができるのか、疑問に思っていました。
long.MaxValue
の使用に関するいくつかの回答者のアイデアに従って、テスト用の小さなフォームを作成しました。これが私の参照コードです。
public Form1()
{
InitializeComponent();
Stopwatch test = new Stopwatch();
test.Start();
myTextBox.Text = test.Elapsed.ToString();
}
そして、これがkinda役に立たないコードのコードです:
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 |
---------------------------------------------
したがって、配列を使用しても、最適化はまったく行われません。
まあ、あなたの変数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
のすべての可能な結果であり、それがループを保持する理由です
ループには、各反復で暗黙的に2つの操作があります。増分:
j++;
と比較
j<TestRunTime.Length;
したがって、ループは空ではないように見えますが、空ではありません。最後に何かが実行されており、これはもちろんコンパイラによって無視されません。
これは他のループでも発生します。
それは無視されません。ただし、ILに到達すると、jumpステートメントがあるため、forはifステートメントであるかのように実行されます。 @Fleveが述べたように、++と長さのコードも実行します。余分なコードになります。読みやすくするため、またコード標準を維持するために、使用しない場合はコードを削除します。
明らかに、コンパイラはignore役に立たないコードではありませんが、慎重に分析してから、最適化を実行する場合は削除しようとします。
あなたの場合、最初に興味深いのは、変数jがループの後に使用されるかどうかです。もう1つの興味深い点は、TestRunTime.Lengthです。コンパイラーはそれを調べて、常に同じ結果が返されるかどうかを確認します。はいの場合、副作用があるかどうかを確認します。はいの場合、一度呼び出すと、繰り返し呼び出すのと同じ副作用があります。
TestRunTime.Lengthに副作用がなく、jが使用されていない場合、ループは削除されます。
それ以外の場合、TestRunTime.Lengthを繰り返し呼び出すと、1回呼び出すよりも多くの副作用がある場合、または繰り返し呼び出すと異なる値が返される場合は、ループを実行する必要があります。
それ以外の場合、j = max(0、TestRunTime.Length)。
次に、コンパイラーは、TestRunTime.Lengthの割り当てが必要かどうかを判別できます。 TestRunTime.Lengthを決定するだけのコードに置き換えることができます。
そしてもちろん、コンパイラが派手な最適化を試みないかもしれませんし、言語規則がこれらのことを決定できないようになっていて、行き詰まっているかもしれません。
(明示的かどうかにかかわらず)役に立たないコードはコンパイラーによって無視されますか?
役に立たないと簡単に判断できないので、コンパイラも判断できません。たとえば、TestRunTime.Length
のゲッターには副作用があります。
背景は、私が(私が書かなかった)多くのコードを維持していることであり、役に立たないコードをターゲットにするべきかどうか疑問に思っていました
コードの一部をリファクタリングする前に、コードを変更できるようにするためにコードが何をするかを確認し、後で同じ結果が得られると言う必要があります。ユニットテストはこれを行うための優れた方法です。
JITは基本的にデッドコードを削除することができます。それはあまり徹底的ではありません。死んだ変数と式は確実に殺されます。これは、SSA形式での簡単な最適化です。
制御フローについてはよくわかりません。 2つのループをネストすると、内側のループだけが削除されることを覚えています。
何が削除され、何が生成されたx86コードを見ないのかを確実に知りたい場合。 C#コンパイラは、最適化をほとんど行いません。 JITはいくつかのことを行います。
4.5 32ビットと64ビットのJITは異なるコードベースであり、動作も異なります。新しいJIT(RyuJIT)が間もなく登場しますが、私のテストでは一般的に悪化し、時には改善します。
ほとんどの場合、無駄なコードを積極的に削除することを心配する必要はありません。パフォーマンスの問題が発生し、プロファイラーが役に立たないコードがクロックサイクルを食いつぶしていると言った場合は、それを核にします。ただし、コードが実際に何も実行せず、副作用がない場合は、実行時間にほとんど影響を与えない可能性があります。
とはいえ、ほとんどのコンパイラは最適化を実行する必要がないため、コンパイラの最適化に依存することが常に最も賢いオプションであるとは限りません。ただし、多くの場合、役に立たないスピンループでもかなり高速に実行できます。 100万回ループする基本的なスピンロックは、mov eax, 0 \ inc eax \ cmp eax, 1000000 \ jnz -8
のようなものにコンパイルされます。 CPU上の最適化を割り引いたとしても、メモリアクセスがないため、ループあたり3サイクル(最近のRISCスタイルのチップ)であり、キャッシュが無効になることはありません。 1GHzのCPUでは、これはわずか3,000,000/1,000,000,000秒、つまり3ミリ秒です。 1秒間に60回実行しようとすると、かなり大きなヒットになりますが、多くの場合、目立たないでしょう。
私が説明したようなループは、JIT環境であっても、ほぼ間違いなくmov eax 1000000
に最適化されたのぞき穴になります。それ以上に最適化される可能性がありますが、他のコンテキストが与えられない場合、その最適化は合理的であり、悪影響はありません。
tl; dr:プロファイラーがデッド/役に立たないコードがかなりの量のランタイムリソースを使用していると言った場合は、それを削除します。ただし、デッドコードを探して魔女狩りをしないでください。これは、リライト/大規模なリファクタリングのために残してください。
ボーナス:コードジェネレーターがeax
がループ条件以外で読み取られないことを知っていて、スピンロックを保持したい場合、mov eax, 1000000 \ dec eax \ jnz -3
を生成し、ループのペナルティを減らすことができます。サイクルによって。ただし、ほとんどのコンパイラはそれを完全に削除します。