パフォーマンスとチューニング
制御フロー難読化は、わずかな実行時間と引き換えに保護を得るものです。軽量なアルゴリズムは、実質的にコストがかかりません。フラット化アルゴリズムは、呼び出しのたびに実行されるディスパッチャーを追加するため、コストが高くなります。このページでは、アルゴリズムを比較し、「反復回数」の設定を説明して、頻繁に呼び出されるメソッドを高速に保つ方法を示します。
アルゴリズムの比較
| アルゴリズム | 系統 | 実行時の相対コスト | 検証可能な IL | エディション |
|---|---|---|---|---|
if | 軽量 | ごくわずか | はい | すべて |
goto | 軽量 | ごくわずか | はい | すべて |
switch | フラット化 | 高 | はい | すべて |
case | フラット化(switch と併用) | 高 | はい | すべて |
call | フラット化(switch と併用) | 高 | はい | すべて |
value | フラット化(switch と併用) | 高 | はい | すべて |
chain(チェーン状態) | フラット化 | 高(≈ switch) | はい | Ultimate |
token | — | 低 | いいえ | すべて |
underflow | — | 低 | いいえ | すべて |
表の読み方
ifとgotoは、分岐と条件を追加しますが、ディスパッチャーは追加しないため、実行時のコストはごくわずかです。広く適用できます。- フラット化系(
switch、case、call、value)とchainは、ディスパッチャーを中心にメソッドを再構築します。タイトなループで数千万回呼び出される小さなメソッドでは、フラット化を 1 回適用すると、そのメソッド自体の呼び出し 1 回あたりの時間が、およそ 1 桁増えます。難読化解除がはるかに困難であるにもかかわらず、チェーン状態はswitchとの差が数パーセント以内に収まります。追加の保護は、フラット化そのものと比べれば、実質的にコストがかかりません。 tokenとunderflowは、実行時のコストは低いものの、検証不可能な IL を生成するため、Babel は .NET と .NET Core ではこれらを無効にします。
これらは、フラット化された小さなメソッド 1 つをループで呼び出すだけのマイクロベンチマークによる、最悪の場合の数値です。ほとんどのメソッドは、このようなホットループでは呼び出されないため、フラット化の実際の影響は、通常は小さなものです。それでも、フラット化は重要なメソッドに限って使用し、ホットパスには適用しないことをお勧めします。
制御フローの反復回数
制御フローアルゴリズムによる難読化の効果は、主に、メソッドに適用する「反復回数」によって決まります。回数が多いほど、結果は複雑になります。フラット化アルゴリズムの場合は、適用のたびにディスパッチの層が 1 つ増えるため、実行時のコストも高くなります。
Babel では、--iterations <n>(MSBuild では ILIterations)で反復回数を設定できます。0 に設定すると、制御フロー難読化は無効になります。メソッドのフローをそれ以上変更できなくなる上限があり、これはメソッドの構造によって決まります。そのため、回数を増やしても、命令が増えるとは限りません。
制御フロー難読化の限定
フラット化は、対象となるすべてのメソッドに分岐を追加するため、見境なく適用すると、パフォーマンスが重要なコードが遅くなることがあります。XML ルールを使用して、制御フロー難読化を必要なメソッドに限定し、残りを除外してください。たとえば、コンパイラーが生成したメソッドは、保護に値する知的財産を含むことがほとんどないため、除外できます。
<Rules>
<Rule name="rule" feature="control flow" exclude="true" applyToMembers="true">
<Target>Classes</Target>
<Pattern>*</Pattern>
<HasAttribute onEnclosingType="false">System.Runtime.CompilerServices.CompilerGeneratedAttribute</HasAttribute>
</Rule>
</Rules>このルールは、exclude="true" で control flow 機能を対象にし、applyToMembers="true" で、一致するクラスのすべてのメンバーに適用範囲を広げます。<Target> 要素は要素の種類(ここでは Classes)を選択し、<Pattern> はその名前に一致させ(* はすべて)、<HasAttribute> は、CompilerGeneratedAttribute を持つクラスだけに一致を絞り込みます。
同じ方法で、パフォーマンスが重要だとわかっているメソッドを除外できるため、保護と速度のバランスを取ることができます。機密性の高いコードはフラット化し、ホットパスには手を加えないでおきます。