2぀のバむト配列を比范する5぀の方法。 ベンチマヌク

ストップりォッチ ゜フトりェアをプロファむリングした結果、バッファヌ比范機胜を最適化する必芁があるず結論付けたした。
なぜなら CLRはメモリの2぀の郚分を比范する暙準的な方法を提䟛しないため、関数は独立しお機胜したした機胜する堎合のみ。
「.Netのバむト配列を比范する最良の方法」ずいうフレヌズをグヌグルで探したしたが、ほずんどの堎合、人々はLINQたたはEnumerable.SequenceEqualを䜿甚するこずを提案したした。 StackOverflowでも、これが最も䞀般的な回答でした。 ぀たり 壊滅的に人気のあるフォヌムの劄想

「コンパむラずランタむム環境はルヌプを最適化するので、パフォヌマンスを心配する必芁はありたせん。」

最初にこの投皿を曞くこずを考えさせられたのはそれでした。
Cから利甚可胜なバッファヌを比范する5぀の方法の比范テストを実斜し、テスト結果に基づいお、メ゜ッドの遞択に関する掚奚事項を瀺したした。
さらに、いく぀かの関数を逆コンパむルし、x86構成甚にJITコンパむラヌによっお生成されたコヌドを分析し、同様の目的でJITコンパむラヌによっお生成されたマシンコヌドずCRT関数のマシンコヌドを比范したした。


背景


別のナヌティリティを䜜成しお機胜させた埌、プロファむラを起動し、バむト配列の比范に非垞に時間がかかっおいるこずを確認したした。
CompareByteArrays関数はクリティカルパス䞊にあり、やるべきこずがありたした。
パフォヌマンスの問題に察するアルゎリズムの解決策は芋圓たりたせんでした。アルゎリズムは事前に遞択されおおり、結果ずしお生じる耇雑さを軜枛する方法に぀いおのアむデアはありたせんでした。
World Wide Webでの怜玢結果はあたり期埅できたせんでした。メ゜ッドの速床の比范はありたしたが、これらの数倀はどのデヌタで、どの条件で誰も曞くこずを気にせずに埗られたした。
私は、誰がどのような条件䞋でより速くなるかに぀いお、独自の仮定を持っおいたした。 確認するこずにしたした。
結果は面癜かったので、ここでの断食の考えは぀いに成熟したした。

䜕をテストしたしたか


䞻なもの


コヌドはコンパむルされ、Windows 7 x64マシン䞊で実行されたした。 Microsoft Visual Studion 2010のデフォルト蚭定で、x86構成のみ、぀たり これは32ビットコヌドです。 たず、出䌚ったコヌドのほずんどが32ビットシステム甚に曞かれおいるためです。 次に、この構成では.Netが重芁であるず考えおいたす。 特に、倧量の既存のコンポヌネントが32ビット甚に蚘述されおおり、誰もそれらを曞き換えないため、それらず察話する必芁がありたす。 最終的なアプリケヌションの構成を決定したす。 次に、64ビットを必芁ずするアプリケヌションの数が非垞に少ないため、必芁なビット深床は䞻にメモリ芁件ずタヌゲットプラットフォヌムによっお決たりたす。 最新の64ビットx86互換プロセッサは、ハヌドりェアで32ビットタスクをサポヌトしたす[1] [2]。 Windows x64が32ビットコヌドを盎接実行するこずでこの機胜を䜿甚するのは論理的です[3]。 最初に32ビットアプリケヌションを64ビットでコンパむルするず、コンパむラヌオプションによっおプロセッサヌTLBサむズ、1次キャッシュも倉曎されず、ポむンタヌサむズが2倍になり、必芁なデヌタアラむメント境界もあるため、必芁なメモリが増加し、パフォヌマンスが䜎䞋したす倉曎[4]。

デヌタサむズ


元のタスクでは、サむズが16バむトでサむズが1〜4 * 1024バむトのバむト配列を比范する必芁がありたした。 16バむトはMD5のサむズ、4 KbはWindowsの暙準メモリペヌゞのサむズであるため、I / Oバッファ甚に遞択されたした。
ただし、ハッシュのサむズが16バむトだけでなく、4バむト、さらには2バむトCRC16、CRC32、およびメモリペヌゞが4であるずいう単玔な理由により、1バむトから1/2メガバむトのブロックで比范をテストしたした。キロバむトだけでなく、2 MB [2]、さらには4 MBもありたす。 1/2 MBブロックで瀺された結果は、倧きなブロックでの䜜業に぀いお結論を出すのに十分でした。さらに、私の時間は無制限ではありたせんテスト䞭にコンピュヌタヌに觊れないようにしお、説明されたプロセスから時間がかからないようにしたす。

最初の実行の結果に基づいお、25皮類のサむズのテストデヌタでメ゜ッドを比范するだけで十分であるこずがわかりたした。 これを行うには、最初のテストセットが1バむト配列で構成され、次の各ステップでテスト配列のサむズに同じ係数が乗算されるように、テストサむクルを構築したした。

パラメヌタヌは、コヌド内の定数によっお蚭定されたした。
private const int CountArrays = 128; //    private const int StartArraySize = 1; //    private const int MaxArraySize = 512 * 1024; //    private const int StepsCount = 25; //  private const int MeasurementsCount = 10; //  


メむンのテストサむクルは次のようになりたす。
  double sizeMultiplier = 1; DoInvestigationStep(sizeMultiplier); const int MaxMultiplier = MaxArraySize / StartArraySize; var stepMmltiplier = Math.Pow( MaxMultiplier, 1 / (double)StepsCount ); for (var step = 1; step <= StepsCount; step++) { sizeMultiplier = Math.Pow(stepMmltiplier, (double) step); DoInvestigationStep(sizeMultiplier); } 


各ステップでの配列のサむズは、次のように蚈算されたした。
  var arraySize = (int) (StartArraySize * p_SizeMultiplier); 


テスト枈みのメ゜ッド



System.Collections.IStructuralEquatableむンタヌフェむスの䜿甚


これはCの比范的新しい方法であり、.NET 4でのみ登堎したため、特に興味深いものでした。
  private static bool ByteArrayCompareWithIStructuralEquatable(byte[] p_BytesLeft, byte[] p_BytesRight) { IStructuralEquatable eqa1 = p_BytesLeft; return eqa1.Equals(p_BytesRight, StructuralComparisons.StructuralEqualityComparer); } 


LINQ、たたはむしろEnumerable.SequenceEqual


これは最も簡単な方法の1぀で、実装が最も簡単です。 たた、これは最も䞀般的な方法です。
  private static bool ByteArrayCompareWithSequenceEqual(byte[] p_BytesLeft, byte[] p_BytesRight) { return p_BytesLeft.SequenceEqual(p_BytesRight); } 


PInvoke、CRT memcmp関数甚


理論的には、これが最速の方法であるはずですが、垞にそうであるずは限らないこずが刀明したした。プラットフォヌムずのやり取りのオヌバヌヘッドは非垞に倚くの時間を消費し、堎合によっおはこの方法はその魅力を倱いたす。
ここで私はそれをSOで芋぀けた圢で持っおきたす。 この圢匏でテストしたした。
PInvoke.netでは、少し異なりたす。
  [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] private static extern int memcmp(byte[] p_BytesLeft, byte[] p_BytesRight, long p_Count); private static bool ByteArrayCompareWithPInvoke(byte[] p_BytesLeft, byte[] p_BytesRight) { // Validate buffers are the same length. // This also ensures that the count does not exceed the length of either buffer. return p_BytesLeft.Length == p_BytesRight.Length && memcmp(p_BytesLeft, p_BytesRight, p_BytesLeft.Length) == 0; } 


額に。 ぀たり for;;ルヌプ内のバむトの盎接比范


これは、2぀の配列を比范する最も明癜な方法であり、興味深いのはこのためです。
圓初、この方法は速すぎるずは思われたせんでしたが、倚くの状況ではかなり蚱容できるこずが刀明したした。

ちなみに、この比范方法のバリ゚ヌションの1぀は、CLR補造業者自身の蚘事で 、さらに必芁なテキストで䜿甚されおいたした。
どうやら、ナヌザヌはそのような質問でそれらを終えたした。
  private static bool ByteArrayCompareWithSimplest(byte[] p_BytesLeft, byte[] p_BytesRight) { if (p_BytesLeft.Length != p_BytesRight.Length) return false; var length = p_BytesLeft.Length; for (int i = 0; i < length; i++) { if (p_BytesLeft[i] != p_BytesRight[i]) return false; } return true; } 


Hafor Stefansonの最適化された安党でない方法


メ゜ッドの䜜成者は、この最適化されたメ゜ッドは、配列の先頭がQWORD境界に揃えられおいるず仮定しお、配列の可胜な最倧郚分に぀いお64ビットのブロックで比范を行うず䞻匵しおいたす。 QWORDアラむメントがない堎合でも動䜜したすが、速床は遅くなりたす。
ただし、32ビット環境では、64ビットブロックでの比范はアセンブラヌを䜿甚しおのみ実珟できたす。汎甚レゞスタヌは32ビットであり、コンパむラヌはこれらを䜿甚しおマシンコヌドを生成したす。
したがっお、このメ゜ッドが実際にどのように機胜するかは、コンパむル方法によっお異なりたす。 私の堎合、それは間違いなく64ビットではありたせん。
  // Copyright (c) 2008-2013 Hafthor Stefansson // Distributed under the MIT/X11 software license // Ref: http://www.opensource.org/licenses/mit-license.php. private static unsafe bool UnsafeCompare(byte[] a1, byte[] a2) { if (a1 == null || a2 == null || a1.Length != a2.Length) return false; fixed (byte* p1 = a1, p2 = a2) { byte* x1 = p1, x2 = p2; int l = a1.Length; for (int i = 0; i < l / 8; i++, x1 += 8, x2 += 8) if (*((long*) x1) != *((long*) x2)) return false; if ((l & 4) != 0) { if (*((int*) x1) != *((int*) x2)) return false; x1 += 4; x2 += 4; } if ((l & 2) != 0) { if (*((short*) x1) != *((short*) x2)) return false; x1 += 2; x2 += 2; } if ((l & 1) != 0) if (*((byte*) x1) != *((byte*) x2)) return false; return true; } } 


テスト方法


テストセット


テスト条件を戊闘条件にできるだけ近づけ、プロセッサキャッシュ内のテストデヌタの「劚害」を排陀するため可胜な限りメモリを䜿甚、倚くのテスト配列を生成し、それらを盞互に比范するこずが決定されたした。 それぞれの各配列。

テストセットずしお、「ゞャグ」アレむが遞択されたした元のドキュメントでは「ゞャグアレむ」。 そこいく぀かの遞択肢は、䟋えば、だった、 List<byte[]>ずLinkedList<byte[]>が、.NETの配列がここに茝いおいなくおも代替案は、アむテムのスヌツの時間アクセスがなかった、どのような堎合にはCLRを出力配列境界のむンデックスをチェックしたす、それがれロベヌスの配列であっおも。

テストデヌタは、すべおの配列が異なり、配列の異なる芁玠がちょうど䞭倮にあるような方法で生成されたした。
テストデヌタ生成
  private static void PrepareTestData(int p_ArraySize) { for (int i = 0; i < CountArrays; i++) { var byteArray = new byte[p_ArraySize]; byteArray[p_ArraySize / 2] = (byte) (i & 0x000000ff); s_arrays[i] = byteArray; } } 



比范する


すべおの配列はすべおず比范されるこずになっおいるため、実行時間を枬定したメ゜ッドの䞀般的なビュヌは、セットのすべおの配列に察する2぀のネストされたルヌプであり、テストメ゜ッドはその内郚で呌び出されたす。 䜕が起こっおいるかの本質は、コヌドで衚珟するこずができたす。
  for (int i = 0; i < CountArrays; i++) for (int j = 0; j < CountArrays; j++) Compare( s_arrays[i], s_arrays[j] ); 

ただし、「蚈算」の結果を䜿甚しない堎合、CLRには「蚈算」自䜓を実行しないすべおの暩利がありたす。 C ++を詊しおいたずきに、これに出くわしたした。 「-O3」最適化をオンにするず、䜕も枬定するこずが非垞に困難になりたした。 䜕床もれロであるこずが刀明したした。 したがっお、比范機胜の結果が「蓄積」されお返され、最高レベルで分析されるように、そのようなシナリオを最初から陀倖するこずが決定されたした。
結果を無芖しない比范
  var result = true; for (int i = 0; i < CountArrays; i++) for (int j = 0; j < CountArrays; j++) { var tmp = Compare( s_arrays[i], s_arrays[j] ); result = result && tmp; } return result; 


比范結果の分析
  var stubLocalVar = p_omparisonMethod(); if (stubLocalVar) throw new InvalidOperationException(); 



5぀の比范方法があり、䞊蚘のテンプレヌトは䞀般的であるため、䞀般的な「テンプレヌト」方法を決定し、それに比范方法を枡すこずは論理的に思えたす。 このように
䞍正な関数呌び出しメ゜ッド
  private static bool CompareArraysWithExternalMethod(Func<byte[], byte[], bool> p_Method) { var result = true; for (int i = 0; i < CountArrays; i++) for (int j = 0; j < CountArrays; j++) { var tmp = p_Method( s_arrays[i], s_arrays[j] ); result = result && tmp; } return result; } 


このようなプロセスは、圓然のこずながら、陀倖コピヌ/貌り付け、゚ラヌの確率を枛少させ、それず同時に、特に倚くのその呌び出しを考慮し、枬定の粟床の損倱ぞの䞍芁な間接コヌルず陀倖の取り蟌み方法むンラむン化、順番にリヌドを䜜成したす。 そのため、ほが同じ5぀のメ゜ッドを䜜成する必芁がありたした。
  private static bool CompareArraysWithUnsafeMethod(); private static bool CompareArraysWithSimplestMethod(); private static bool CompareArraysWithSequenceEqualMethod(); private static bool CompareArraysWithPInvokeMethod(); private static bool CompareArraysWithIStructuralEquatableMethod(); 

ただし、レベルが高い堎合は、テンプレヌトメ゜ッドを既に適甚できたす。

以䞋のコヌドは、比范メ゜ッドの実行時間を枬定するMeasureComparisonTime関数が、パラメヌタヌずしおFunc型のデリゲヌトを取るこずを瀺しおいたす。 枬定されるのは圌のランタむムです。

メ゜ッド枬定機胜
  private static long GetMinimalMesuredValue(int p_MeasurementNumber, long p_PreviousValue, long p_MeasuredValue) { var result = p_MeasurementNumber == 0 ? p_MeasuredValue : Math.Min(p_PreviousValue, p_MeasuredValue); return result; } private static long MeasureComparisonTime(Func<bool> p_Method, long p_PreviousTime, int p_MeasurementNumber) { GC.Collect(); GC.Collect(); s_stopwatch.Start(); var stubLocalVar = p_Method(); s_stopwatch.Stop(); if (stubLocalVar) throw new InvalidOperationException(); var result = GetMinimalMesuredValue(p_MeasurementNumber, p_PreviousTime, s_stopwatch.ElapsedTicks); s_stopwatch.Reset(); return result; } 



枬定手順


時間は、 QueryPerformanceCounterに基づく暙準のストップりォッチ クラスSystem.Diagnostics.Stopwatch を䜿甚しお枬定されたした。 圓然、圌らはミリ秒ではなくチックに興味がありたした。小さなアレむの堎合、ミリ秒単䜍の枬定倀はすべおれロになりたす。 たた、RDTSCの基瀎[6]の「自転車」を䜿甚するためのアむデアを持っおいたが、最初にすべおの、最初のいく぀かの実行は、それが手続きの電流粟床のために十分であるこずが瀺されおいる、第二に、䜿甚の譊告ProcessThread.ProcessorAffinityに登堎し、ドキュメントの最近のバヌゞョンでは、このクラスの基瀎は前述のプロセッサクロックカりンタであるこずが瀺唆されおいたす。 すべおの枬定は10回繰り返され、最適な時間が取られたした。 最小の時間でかなり正確な結果が埗られるため、数倀10は実隓的に遞択されたした。
すべおの枬定を行う機胜
  private static MeasurementsResults DoMeasurements() { MeasurementsResults result; result.SimplestTime = 0; result.SequenceEqualTime = 0; result.PInvokeTime = 0; result.IStructuralEquatableTime = 0; result.UnsafeTime = 0; for (int measurementNumber = 0; measurementNumber < MeasurementsCount; measurementNumber++) { Console.WriteLine("   {0}", measurementNumber); result.SimplestTime = MeasureComparisonTime(CompareArraysWithSimplestMethod, result.SimplestTime, measurementNumber); result.SequenceEqualTime = MeasureComparisonTime(CompareArraysWithSequenceEqualMethod, result.SequenceEqualTime, measurementNumber); result.PInvokeTime = MeasureComparisonTime(CompareArraysWithPInvokeMethod, result.PInvokeTime, measurementNumber); result.IStructuralEquatableTime = MeasureComparisonTime(CompareArraysWithIStructuralEquatableMethod, result.IStructuralEquatableTime, measurementNumber); result.UnsafeTime = MeasureComparisonTime(CompareArraysWithUnsafeMethod, result.UnsafeTime, measurementNumber); } return result; } 



枬定結果ず最初の結論


実隓を開始する前に、どの方法が勝者であり、どの方法が郚倖者であるかがわかっおいたしたが、それでも結果は驚きたした。 私の予枬は完党に正しくありたせんでした。
ベストタむムチック配列サむズバむト安党でない額ピンボヌクシヌケンスIStructuralEquatable
276
1
1.7
1
6
10.5
55
276
1
1.7
1
6.3
10.1
55.7
325
2
1.3
1
5.3
11
95.2
374
4
1,1
1
4.8
11,4
121.3
413
8
1
1.3
5
14.1
181.2
412
13
1
1.7
4.7
17.5
252.5
490
23
1
2,3
3,7
22.1
364.8
554
39
1
3.4
3,5
30.1
535.9
691
67
1
4.3
2.9
39.1
727.5
887
114
1
5,6
2,4
50.3
964.1
1212
194
1
4.6
2.1
61.5
1190.9
1875
328
1
4.8
1.8
65.8
1295.8
2897
556
1
5
1,6
71.5
1416.9
4565
942
1
5.3
1.4
76.3
1521.1
7711
1595
1
5.2
1.3
76,2
1521.2
12482
2702
1
5,4
1.3
79,4
1593.6
20692
4576
1
5.5
1,2
81.2
1626.2
34369
7750
1
5,6
1,2
82.8
1657.1
58048
13124
1
5,6
1,2
82.9
1661.9
97511
22226
1
5,6
1,2
83.6
1677.3
173805
37640
1
5,4
1,1
79.5
1585.7
295989
63743
1
5.3
1,1
79.3
1574.9
588797
107949
1
4.6
1,1
67.5
1340.9
1010768
182811
1
4.6
1,1
67
1340.4
1705668
309590
1
4.6
1,1
67.3
1334.1
2885452
524287
1
4.6
1,1
67.3
1329.1


結果は次のずおりです。
最初にCRT関数は単玔に最適化する必芁があり、特定の段階テスト配列のサむズでその速床がプラットフォヌム呌び出しのオヌバヌヘッドをブロックするず蚈算したしたが、これは起こりたせんでした。 埌で、はるかに倧きなアレむでテストを繰り返したしたが、1.5メガバむトでも安党でないコヌドの方が高速であるこずがわかりたした。

2番目IStructuralEquatableは遅くなるず思いたしたが、数字は単に私を驚かせたした私は間違いなくこれを期埅しおいたせんでした。

個々の機胜の蚺断ず分析


等しく深刻倧きいアレむ䞊危険な最も単玔な差がある私はこれ以䞊の2぀の3時間「利点」より予想ネットサポヌタヌは、請求項のように.NETの配列の芁玠を最適化し、控蚎のサむクルはそれほど滑らかではないこずを瀺唆しおいたす。 したがっお、私はJIT'erの仕事の結果を芋るこずの喜びを吊定したせんでした。 盎接生成されたマシンコヌド。

CompareArraysWithSimplestMethod関数の分析


これを行うために、メ゜ッドの最初にThread.Sleep60 * 1000が挿入されたした。 リリヌスビルドを起動した埌、OllyDebugプロセスを遞択したした。 このようなトリッキヌな動きは、CLRの最適化を損なうこずがないようにするために必芁でした。

矢印付きのデバッガヌりィンドりのスクリヌンショット

ntdll.NtDelayExecutionずSleepExからコヌルスタックを䞋っお行くず、私は自分の関数を芋぀け、慎重に研究した埌、再び䞍快に驚きたした。 ここで本圓に奜きではなかったもの
  1. 単䞀のRngChkFailチェックルヌプ党䜓に察しおの代わりに、CLRはむンデックスごずにこのチェックを行いたす!!!
  2. サむクルは、私が曞いた圢のたたでした。コンパむラはそれを「デプロむ」したせんでした。
  3. したがっお、CLRは4バむトレゞスタサむズの比范を行うなど、CLRを最適化するこずはできたしたが、比范はバむト単䜍のたたでした。
  4. 単䞀の゚ピロヌグで単䞀の戻り倀を返しおスタックをクリアする代わりに、CLRはそれらを3回コピヌし、コヌドを「膚匵」させたした。 実際、マシンコヌドはCコヌドをほが1察1で繰り返したす。 これに関しお、疑問が生じたす。圌はコヌドをたったく最適化したしたか 圌はこれを行う方法を知っおいたすか
  5. おそらく前の段萜コヌドの膚匵のおかげで、関数はむンラむン化されたせんでした。


UnsafeCompare関数分析


この関数を逆コンパむルした結果、私の奜奇心が燃え䞊がり、他の関数に぀いお考えるようになりたした。 CRT機胜ず安党でないオプションを比范するこずにしたした。 たず、安党でないオプションを怜蚎するこずにしたした。 これを行うには、最初の関数ず同じ操䜜を行いたしたが、Thread.Sleepは関数自䜓ではなく、呌び出される前に挿入されたした。 これは、䜕が起こっおいるかの本質にほずんど圱響を䞎えるこずはできたせんでしたが、逆コンパむルを幟分単玔化したした。安党でない関数は明らかに最初のものより耇雑です。
挿入スレッド。スリヌプ
  private static bool CompareArraysWithUnsafeMethod() { var result = true; for (int i = 0; i < CountArrays; i++) for (int j = 0; j < CountArrays; j++) { Thread.Sleep( 60 * 1000 ); var tmp = UnsafeCompare(s_arrays[i], s_arrays[j]); 



この関数の前半も重芁です。 UnsafeCompareが呌び出されるたで。 最初の䟋ず同様に、配列の芁玠にアクセスするず、配列の境界内でむンデックスがチェックされたす。 私は、リストにこのこずを匷調したした。
CPU Disasm CompareArraysWithUnsafeMethod
 ;Address Hex dump Command Comments 001B0B88 55 push ebp ;   001B0B89 8BEC mov ebp, esp 001B0B8B 57 push edi 001B0B8C 56 push esi 001B0B8D 53 push ebx 001B0B8E BF 01000000 mov edi, 1 ; result = true; 001B0B93 33DB xor ebx, ebx ; for (int i = 0; 001B0B95 33F6 xor esi, esi ; for (int j = 0; 001B0B97 B9 60EA0000 mov ecx, 0EA60 ; Thread.Sleep( 60 * 1000 ); 001B0B9C E8 0CFBC161 call clr.ThreadNative::Sleep 001B0BA1 8B15 A8337A03 mov edx, [s_arrays] ; EDX <-- s_arrays 001B0BA7 3B5A 04 cmp ebx, [edx+4] ; Compare(s_arrays.Length,  ) (1) !!! 001B0BAA 73 31 jae short stub_CLR.JIT_RngChkFail 001B0BAC 8B4C9A 0C mov ecx, [ebx*4+edx+0C] ; ECX <-- s_arrays[i] 001B0BB0 3B72 04 cmp esi, [edx+4] ; Compare(s_arrays.Length,  ) (2) !!! 001B0BB3 73 28 jae short stub_CLR.JIT_RngChkFail 001B0BB5 8B54B2 0C mov edx, [esi*4+edx+0C] ; EDX <-- s_arrays[j] 001B0BB9 FF15 F0381400 call near UnsafeCompare 



関数の逆コンパむルの結果は非垞に倧きく、1぀の画面に収たらないため、デバッガりィンドりのスクリヌンショットは提䟛したせんでした。 倧芏暡な統合されたリストは読むのが退屈で非生産的であるため、ここでは論理的に関連した断片で説明し、各断片に短いコメントを付けたす。
UnsafeCompare。
 ;a1 ========> ECX ;a2 ========> EDX ;Address Hex dump Command Comments 001B0BF8 55 push ebp ;   001B0BF9 8BEC mov ebp, esp 001B0BFB 57 push edi ;     , , ,  001B0BFC 56 push esi ;     001B0BFD 53 push ebx ; 001B0BFE 83EC 0C sub esp, 0C ;       3*sizeof(DWORD), ..  3  001B0C01 33C0 xor eax, eax ; (!) 001B0C03 8945 F0 mov [ebp-10], eax ; var1 <-- 0 (!) 001B0C06 8945 EC mov [ebp-14], eax ; var2 <-- 0 (!) 001B0C09 85C9 test ecx, ecx ; Compare(a1, null) 001B0C0B 74 0C je short return1 001B0C0D 85D2 test edx, edx ; Compare(a2, null) 001B0C0F 74 08 je short return1 001B0C11 8B41 04 mov eax, [ecx+4] ; eax <-- a1.Length 001B0C14 3B42 04 cmp eax, [edx+4] ; Compare(eax, a2.Length) 001B0C17 74 0A je short argsIsValid return1: 001B0C19 33C0 xor eax, eax ; result <-- 0 001B0C1B 8D65 F4 lea esp, [ebp-0C] 001B0C1E 5B pop ebx 001B0C1F 5E pop esi 001B0C20 5F pop edi 001B0C21 5D pop ebp 001B0C22 C3 ret argsIsValid: ;    



.NET Framework [7] [8]に続くfastcall呌び出し芏玄によるず、最初ず2番目のパラメヌタヌは、巊から右の順にecxおよびedxレゞスタヌにありたす。 䞊蚘のリストでは、入力パラメヌタヌの順次チェックが明確に衚瀺されおおり、Cのコヌドにほが明確に察応しおいたす。
  if (a1 == null || a2 == null || a1.Length != a2.Length) return false; 

ただし、匷調衚瀺されおいる行には特別な泚意が必芁です。その目的は䞍明であり、混乱を招きたす。 それが䜕である必芁があるのか​​を理解するために、別の実隓を行う必芁がありたした。その間に、Cで最も単玔な安党でない関数を䜜成したした。この関数は固定句ずポむンタヌ凊理を䜿甚し、同様のアクションを実行したした。
  private static unsafe int func1(byte[] param1) { fixed (byte* p = param1) { return *p; } } 


最も単玔な安党でない機胜の悲惚さ
 ;param1 ========> ECX Address Hex dump Command Comments $ ==> 55 push ebp ;   $+1 8BEC mov ebp, esp $+3 50 push eax ; (!) $+4 33C0 xor eax, eax ; (!) $+6 8945 FC mov [ebp-4], eax ; var1 <-- 0 (!) $+9 85C9 test ecx, ecx ; Compare(param1, null) $+B 74 06 je short param_is_null $+D 8379 04 00 cmp dword ptr [ecx+4], 0 ; Compare(param1.Length, 0) $+11 75 07 jne short not_zero_len param_is_null: $+13 33D2 xor edx, edx $+15 8955 FC mov [ebp-4], edx ; var1 <-- 0 $+18 EB 0C jmp short ret_1 not_zero_len: $+1A 8379 04 00 cmp dword ptr [ecx+4], 0 $+1E 76 10 jbe short call.JIT_RngChkFail $+20 8D49 08 lea ecx, [ecx+8] ; ecx <-- param1.BufferPointer $+23 894D FC mov [ebp-4], ecx ; var1 <-- ecx ret_1: $+26 8B45 FC mov eax, [ebp-4] ; eax <-- var1 $+29 0FB600 movzx eax, byte ptr [eax] ; eax <-- *eax $+2C 8BE5 mov esp, ebp ;   $+2E 5D pop ebp $+2F C3 ret call.JIT_RngChkFail: $+30 E8 B3BDC861 call clr.JIT_RngChkFail $+35 CC int3 



それをリスト䞊からにかかわらず、汎甚レゞスタの可甚性の、スタック䞊に眮くようにしおください固定提案におけるJITコンパむル倉数の結果ずしおその明確になりたす。 これは、eaxレゞスタヌがスタックに保存され、将来䜿甚されず、したがっお操䜜のために無料でアクセス可胜である関数の開始からオフセット0x26たでが、オフセット倉数スタック倉数が䞀時デヌタを栌玍するために䜿甚されたずいう事実から明らかですebp-4]var1ず呌びたしょう。 倉数は、それが䜿甚されおいたせんが、単玔に䞊曞きされおいるにもかかわらず、すぐに必ずしもれロに初期化されたす。 䟋えば、VAR1れロで0x15のオフセットは既にれロ栌玍され、この時点で存圚するずいう事実にもかかわらず、再び入力されたす。

したがっお、それはリストUnsafeCompare.CPU Disasm.ParametersCheckingで遞択された行に特別な意味はないクマが存圚しないこずが明らかになり、それはコンパむルのちょうど副䜜甚です。 たた、䞊蚘のリストから、配列が3぀の段階で固定匏でチェックされるこずが明らかになりたす最初に、配列のnullがチェックされ、次にその長さがれロず等しいかjneコマンドがZFのみを分析する、れロず負の倀が等しいかがチェックされたす jbeはZFずCFの䞡方をチェックしたす。 私の芖点からは、条件分岐がフラグをリセットしないため、CMP呜什は、2回実行されおいるこずを、すべおのより倚くの奇劙な最埌の2぀の手順が䞀぀に結合されたこずはなく、奇劙である、ず。 それは私が無駄にしようずしおいないこずを意味し、およびアセンブリリストの私の発掘調査は無駄ではなかったので、他のものの䞭で、私は、この行を読んであなたの人々に心から感謝しおいたす。

この実隓は、さらにコヌド分析を倧幅に簡玠化したす。

CompareArraysWithUnsafeMethod関数の固定句
 argsIsValid: 001B0C23 8379 04 00 cmp dword ptr [ecx+4], 0 ; Compare(a1.Length, 0)       001B0C27 75 07 jne short a1Len_NonZero ;   001B0C29 33C0 xor eax, eax 001B0C2B 8945 F0 mov [ebp-10], eax ; var1 <-- 0 001B0C2E EB 10 jmp short a1Len_Zero a1Len_NonZero: 001B0C30 8379 04 00 cmp dword ptr [ecx+4], 0 ; Compare(a1.Length, 0)  ()     001B0C34 0F86 B5000000 jbe call.JIT_RngChkFail ;     001B0C3A 8D41 08 lea eax, [ecx+8] ; eax <-- a1.BufferPointer 001B0C3D 8945 F0 mov [ebp-10], eax ; var1 <-- eax a1Len_Zero: 001B0C40 837A 04 00 cmp dword ptr [edx+4], 0 ; Compare(a2.Length, 0)       001B0C44 75 07 jne short a2Len_NonZero ;   001B0C46 33D2 xor edx, edx 001B0C48 8955 EC mov [ebp-14], edx ; var2 <-- 0 001B0C4B EB 10 jmp short part3 a2Len_NonZero: 001B0C4D 837A 04 00 cmp dword ptr [edx+4], 0 ; Compare(a2.Length, 0)  ()     001B0C51 0F86 98000000 jbe call.JIT_RngChkFail ;     001B0C57 8D52 08 lea edx, [edx+8] ; edx <-- a2.BufferPointer 001B0C5A 8955 EC mov [ebp-14], edx ; var2 <-- edx 



コンパむラはここで驚きを瀺したせんでした。 コンパむルアルゎリズムにより、簡単に予枬可胜な結果が埗られたす。 たずえば、固定句で初期化された倉数は、ずにかくスタックにプッシュされたす。
固定ブロック内の倉数の初期化
 part3: ; ECX <======= a1 ; EDX <======= a2.BufferPointer ; var1 <======= a1.BufferPointer ; var2 <======= a2.BufferPointer 001B0C5D 8B45 F0 mov eax, [ebp-10] ; eax <-- var1 001B0C60 8BF0 mov esi, eax ; esi <-- eax 001B0C62 8B45 EC mov eax, [ebp-14] ; eax <-- var2 001B0C65 8BF8 mov edi, eax ; edi <-- eax 001B0C67 8B41 04 mov eax, [ecx+4] ; eax <-- a1.Length 001B0C6A 8945 E8 mov [ebp-18], eax ; var3 <-- eax 


固定ブロック内の倉数の初期化は、JITコンパむラヌがコヌドを生成する原理をよく瀺しおいるずいう点で興味深いものです。ここでは、スタック倉数からむンデックスレゞスタに倀を盎接送信する代わりに、倀が最初にアキュムレヌタレゞスタに配眮されるこずが明確に瀺されおいたす。たぶん、これはある皮の秘密の魔法の意味ですが、それは䜙分なアクションのように芋えたすeaxに送信。
8バむトのルヌプ
 001B0C6D 33C9 xor ecx, ecx ;   <-- 0 001B0C6F 8BD8 mov ebx, eax ; ebx <-- a1.Length 001B0C71 85DB test ebx, ebx 001B0C73 79 03 jns short ebx_greaterZero 001B0C75 83C3 07 add ebx, 7 ;  ,  7 ebx_greaterZero: 001B0C78 C1FB 03 sar ebx, 3 ; ebx <-- a1.Length / 8 001B0C7B 85DB test ebx, ebx 001B0C7D 7E 1D jle short fourBytesComparing for8_body: 001B0C7F 8B06 mov eax, [esi] 001B0C81 8B56 04 mov edx, [esi+4] 001B0C84 3B57 04 cmp edx, [edi+4] 001B0C87 75 04 jne short setResult_jumpReturn 001B0C89 3B07 cmp eax, [edi] 001B0C8B 74 04 je short for8_increment setResult_jumpReturn: 001B0C8D 33C0 xor eax, eax ; result <-- 0 001B0C8F EB 56 jmp short return2 ; goto return for8_increment: 001B0C91 41 inc ecx 001B0C92 83C6 08 add esi, 8 001B0C95 83C7 08 add edi, 8 for8_expression: 001B0C98 3BD9 cmp ebx, ecx 001B0C9A ^ 7F E3 jg short for8_body 



サむクルの構造は非垞に簡単です。たずえば、サむクルカりンタヌは䌝統的にecxに配眮され、カりンタヌの境界倀はebxに配眮されたすが、これも䌝統的です。ここで泚目すべきは、ルヌプ条件の最初のチェックが初期化盎埌にあり、メむンルヌプ条件のように芋えないこずです。奇跡が起こらないこずも明らかであり、REXプレフィックスは保護モヌドたたは互換モヌドのいずれでも䜿甚できたせん。 QWORDブロックずの比范は䞍可胜であるため、DWORDサむズのブロックが比范されたす。ただし、コヌドは、比范を実行する前に、最初の配列の察応する郚分がeax、edxレゞスタにロヌドされるこずを瀺しおいたす。 JITコンパむラは、゜ヌスコヌドに可胜な限り近いマシンコヌドを生成しようずしたした。

コンパむラがここでCMPSD文字列呜什、぀たり、esiずediアドレスに配眮されたDWORDブロックを比范し、適切なフラグを蚭定する「短い」圢匏を䜿甚しなかったこずは驚くべきこずです。この堎合、マシンコヌドのサむズは数倍小さくなりたすmovコマンドのサむズは2バむトず3バむト、cmpコマンドのサむズは2バむトず3バむト、各CMPSDコマンドのサむズ短い圢匏は1バむトのみです。 。 2チヌムの堎合は2バむトのみ。これは、JITコンパむラヌがコヌドの最適化を望たないこずを瀺唆しおいたす。

䞊蚘のリストから、最初のコマンドがeaxぞの転送であるいく぀かのコマンドが、コンパむラヌが厳密に埓うパタヌンであるこずは明らかです。

そのようなボリュヌムが残っおいる堎合、DWORDサむズのブロックを比范しようずしたす。
 fourBytesComparing: 001B0C9C F745 E8 0400000 test dword ptr [ebp-18], 00000004 ; ZF <-- (var3 & 4) == 0 001B0CA3 74 10 je short length_lowerThenFour 001B0CA5 8B06 mov eax, [esi] 001B0CA7 3B07 cmp eax, [edi] 001B0CA9 74 04 je short dwords_equals ;   ,     001B0CAB 33C0 xor eax, eax 001B0CAD EB 38 jmp short return2 dwords_equals: 001B0CAF 83C6 04 add esi, 4 001B0CB2 83C7 04 add edi, 4 


WORD, , :
 length_lowerThenFour: 001B0CB5 F745 E8 0200000 test dword ptr [ebp-18], 00000002 ; ZF <-- (var3 & 2) == 0 001B0CBC 74 10 je short length_lowerThenTwo 001B0CBE 0FBF06 movsx eax, word ptr [esi] 001B0CC1 66:3B07 cmp ax, [edi] 001B0CC4 74 04 je short words_equals ;   ,     001B0CC6 33C0 xor eax, eax 001B0CC8 EB 1D jmp short return2 words_equals: 001B0CCA 46 inc esi 001B0CCB 46 inc esi 001B0CCC 47 inc edi 001B0CCD 47 inc edi 


BYTE, , :
 length_lowerThenTwo: 001B0CCE F745 E8 0100000 test dword ptr [ebp-18], 00000001 ; ZF <-- (var3 & 1) == 0 001B0CD5 74 0B je short 001B0CE2 001B0CD7 0FB606 movzx eax, byte ptr [esi] 001B0CDA 3A07 cmp al, [edi] 001B0CDC 74 04 je short 001B0CE2 ;   ,     001B0CDE 33C0 xor eax, eax 001B0CE0 EB 05 jmp short return2 001B0CE2 B8 01000000 mov eax, 1 


:
 return2: 001B0CE7 8D65 F4 lea esp, [ebp-0C] 001B0CEA 5B pop ebx 001B0CEB 5E pop esi 001B0CEC 5F pop edi 001B0CED 5D pop ebp 001B0CEE C3 ret JIT_RngChkFail: 001B0CEF E8 C4B1DB61 call clr.JIT_RngChkFail 001B0CF4 CC int3 


CRT memcmp()


, , , , , , .

, 0x76C20000 C:\Windows\SysWOW64\msvcrt.dll 7.0.7601.17744.



, , .. , , .

: -, , , , -, . «» , switch 32 case' .

:
 76C37975 . 8BFF mov edi, edi ; <--(!)    INT msvcrt.memcmp(buf1,buf2,count) 76C37977 . 55 push ebp 76C37978 . 8BEC mov ebp, esp 


, , .. , nop, 2 . , , . . nop.
It's a hot-patch point.
The MOV EDI, EDI instruction is a two-byte NOP, which is just enough space to patch in a jump instruction so that the function can be updated on the fly. The intention is that the MOV EDI, EDI instruction will be replaced with a two-byte JMP $-5 instruction to redirect control to five bytes of patch space that comes immediately before the start of the function. Five bytes is enough for a full jump instruction, which can send control to the replacement function installed somewhere else in the address space.
blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx


switch
 Address Hex dump Command Comments 75A9797C . 8B7D 10 mov edi, [ebp+10] ; edi <-- length 75A9797F . 8BC7 mov eax, edi 75A97981 . 83E8 00 sub eax, 0 ; 75A97984 . 0F84 E7070100 je msvcrt.zeroResult_GoReturn ; (length == 0)=> {result <-- 0, goto return;} (!)      ;    ;    75A979BC . 83FF 1F cmp edi, 1F ; Switch (cases 1..1F, 32. exits) 75A979BF . 77 5B ja short msvcrt.75A97A1C 75A979C1 . FF24BD 1F8AA975 jmp near [edi*4+msvcrt.75A98A1F] ; (!)       (!)      ;    ;    75A98A1F . 1C7AA975 dd msvcrt.75A97A1C ; (00)   ,  jump     75A98A23 . E88AA975 dd msvcrt.75A98AE8 ; (01) 75A98A27 . CA8AA975 dd msvcrt.75A98ACA ; (02) 75A98A2B . 8C8BA975 dd msvcrt.75A98B8C ; (03) 75A98A2F . 0A7AA975 dd msvcrt.75A97A0A ; (04) 75A98A33 . 088FA975 dd msvcrt.75A98F08 ; (05) 75A98A37 . B88AA975 dd msvcrt.75A98AB8 ; (06) 75A98A3B . 758BA975 dd msvcrt.75A98B75 ; (07) 75A98A3F . F479A975 dd msvcrt.75A979F4 ; (08) 75A98A43 . 238FA975 dd msvcrt.75A98F23 ; (09) 75A98A47 . 9F8AA975 dd msvcrt.75A98A9F ; (0A) 75A98A4B . A18BA975 dd msvcrt.75A98BA1 ; (0B) 75A98A4F . DE79A975 dd msvcrt.75A979DE ; (0C) 75A98A53 . 3A8FA975 dd msvcrt.75A98F3A ; (0D) 75A98A57 . FD8AA975 dd msvcrt.75A98AFD ; (0E) 75A98A5B . ED8EA975 dd msvcrt.75A98EED ; (0F) 75A98A5F . C879A975 dd msvcrt.75A979C8 ; (10) 75A98A63 . 518FA975 dd msvcrt.75A98F51 ; (11) 75A98A67 . BA8EA975 dd msvcrt.75A98EBA ; (12) 75A98A6B . 6A98A975 dd msvcrt.75A9986A ; (13) 75A98A6F . 8990A975 dd msvcrt.75A99089 ; (14) 75A98A73 . CD98A975 dd msvcrt.75A998CD ; (15) 75A98A77 . D58EA975 dd msvcrt.75A98ED5 ; (16) 75A98A7B . 8598A975 dd msvcrt.75A99885 ; (17) 75A98A7F . 1899A975 dd msvcrt.75A99918 ; (18) 75A98A83 . E898A975 dd msvcrt.75A998E8 ; (19) 75A98A87 . 698FA975 dd msvcrt.75A98F69 ; (1A) 75A98A8B . 9D98A975 dd msvcrt.75A9989D ; (1B) 75A98A8F . 3399A975 dd msvcrt.75A99933 ; (1C) 75A98A93 . 0099A975 dd msvcrt.75A99900 ; (1D) 75A98A97 . 848FA975 dd msvcrt.75A98F84 ; (1E) 75A98A9B . B598A975 dd msvcrt.75A998B5 ; (1F)    



, :




関数を呌び出すオヌバヌヘッドを掚定するために、マネヌゞコヌドから関数自䜓の開始たでコヌドをトレヌスするこずにしたした。これを行うために、関数の先頭にブレヌクポむントが蚭定され、Thread.Sleepから戻った埌、トレヌスが開始されたした。ただし、最初のトレヌス詊行の結果は倱敗したした。トレヌスログが倧きすぎお玄10䞇行、DllMainが実行されたこずを瀺しおいる可胜性があり、䞀郚のCLRコヌドがキャプチャされた可胜性もありたす。 JITコンパむラコヌド。そこで䜕が正確に行われたのか、私は発芋し始めたせんでした。このような開始コヌドは1回だけ実行され、党䜓像にはほずんど圱響したせん。このコヌドを陀倖するために、Thread.Sleepを呌び出しお再床トレヌスする前に、memcmpぞの別の呌び出しを挿入したした。その結果、100行を少し超えたした。



トレヌスログの䞀郚
 main 00480AEA call 0031C19C ESP=0016F368 ;    main clr.628C3B5F call near [eax+14] ESP=0016F248 ; (1) ;    main 00480B87 mov eax, [ebp-1C] EAX=00313880 main 00480B8A mov eax, [eax+14] EAX=0031391C main 00480B8D mov ecx, [eax] ECX=75A97975 ; (2) main 00480B8F push dword ptr [ebp+0C] ESP=0016F328 main 00480B92 push dword ptr [ebp+8] ESP=0016F324 main 00480B95 push edi ESP=0016F320 main 00480B96 push dword ptr [ebp-10] ESP=0016F31C main 00480B99 mov dword ptr [ebp-2C], 0 main 00480BA0 mov [ebp-28], esp main 00480BA3 mov dword ptr [ebp-24], 480BB0 main 00480BAA mov byte ptr [ebx+8], 0 main 00480BAE call ecx ESP=0016F318 ; (3) main msvcrt.memcmp mov edi, edi -------- Logging stopped 

䞊蚘のログから、たず、関数ぞの途䞭で少なくずも1぀の間接呌び出しが発生し、次に、CLRが䜕らかのデヌタ構造から最終関数のアドレスを抜出するこずがわかりたす。課題にはかなりの間接性があり、移行予枬ナニットにその䜿呜を果たす垌望を残したせん。これにより、䞀床に倧量のデヌタを凊理せず、倚くの蚈算時間を必芁ずしない堎合、マネヌゞコヌドの範囲倖でそのような関数を䜿甚するのは無意味になりたす。

メむン機胜コヌドの評䟡

, , . . . , .

, , . , -, , , , -, , , , .
倉曎された配列比范関数
  private static bool CompareArraysWithPInvokeMethod() { var result = true; for (int i = CountArrays - 1; i >= 0; i--) //    for (int j = 0; j < CountArrays; j++) { var tmp = ByteArrayCompareWithPInvoke(s_arrays[i], s_arrays[j]); result = result && tmp; } return result; } 



最初のトレヌスベストケヌス
 main msvcrt.memcmp mov edi, edi main msvcrt.75A97977 push ebp ESP=0041EC74 main msvcrt.75A97978 mov ebp, esp EBP=0041EC74 main msvcrt.75A9797A push esi ESP=0041EC70 main msvcrt.75A9797B push edi ESP=0041EC6C main msvcrt.75A9797C mov edi, [ebp+10] EDI=00080000 ; edi <-- count main msvcrt.75A9797F mov eax, edi EAX=00080000 ; eax <-- edi main msvcrt.75A97981 sub eax, 0 ; if (eax == 0) {result <-- 0; return;} main msvcrt.75A97984 je msvcrt.zeroResult_GoReturn main msvcrt.75A9798A dec eax EAX=0007FFFF main msvcrt.75A9798B je msvcrt.75A98C10 main msvcrt.75A97991 dec eax EAX=0007FFFE main msvcrt.75A97992 je msvcrt.75A9E610 main msvcrt.75A97998 dec eax EAX=0007FFFD main msvcrt.75A97999 je msvcrt.75A9E5DF main msvcrt.75A9799F dec eax EAX=0007FFFC main msvcrt.75A979A0 je msvcrt.75A98BD2 main msvcrt.75A979A6 mov ecx, [ebp+0C] ECX=034C53B8 ; ecx <-- buf1 main msvcrt.75A979A9 mov eax, [ebp+8] EAX=05C41038 ; eax <-- buf2 main msvcrt.75A979AC push ebx ESP=0041EC68 main msvcrt.75A979AD push 20 ESP=0041EC64 ;        main msvcrt.75A979AF pop edx EDX=00000020, ESP=0041EC68 ;--------------------------------   main msvcrt.75A979B0 cmp edi, edx main msvcrt.75A979B2 jae msvcrt.75A993A7 main msvcrt.75A993A7 mov esi, [eax] ESI=4241403F main msvcrt.75A993A9 cmp esi, [ecx] main msvcrt.75A993AB jne msvcrt.75AA80E7 ;   main msvcrt.75AA80E7 movzx esi, byte ptr [eax] ESI=0000003F main msvcrt.75AA80EA movzx ebx, byte ptr [ecx] EBX=00000001 main msvcrt.75AA80ED sub esi, ebx ESI=0000003E ;      DWORD' main msvcrt.75AA80EF jne msvcrt.75AA8178 main msvcrt.75AA8178 xor ebx, ebx EBX=00000000 ;     ebx main msvcrt.75AA817A test esi, esi main msvcrt.75AA817C setg bl EBX=00000001 main msvcrt.75AA817F lea ebx, [ebx+ebx-1] main msvcrt.75AA8183 mov esi, ebx ESI=00000001 main msvcrt.75AA8185 test esi, esi main msvcrt.75AA8187 jne msvcrt.75A98AB1 main msvcrt.75A98AB1 mov eax, esi EAX=00000001 main msvcrt.75A98AB3 jmp msvcrt.75A97A1E main msvcrt.75A97A1E pop ebx EBX=00852AE0, ESP=0041EC6C main msvcrt.return1 pop edi ESP=0041EC70, EDI=034C53B8 main msvcrt.75A97A20 pop esi ESP=0041EC74, ESI=034C53B0 main msvcrt.75A97A21 pop ebp ESP=0041EC78, EBP=0041ECC4 main msvcrt.75A97A22 ret ESP=0041EC7C 



ここで最初に目を匕くのは、特殊なケヌスの凊理です。パラメヌタカりントが[0..4]内にある堎合、非垞にたれになりたす。これがswitch句のコンパむルの結果なのか、ロヌカル倉数が実際にセットアップされたのかを掚枬するこずしかできたせん。ロヌカル倉数はチェックの各ステップで枛少したした。ただし、デバッグ情報は、それがただスむッチであるず䞻匵しおいたす。最適化の芳点から、これは非垞に合理的なアクションです。ルヌプの初期化は実行されたせん。

すぐに私の目を匕いた2番目のこずは、スタックを介しお番号0x20をedxレゞスタに入力する非垞に珍しい方法でした。これは、ある皮のコンパむルアヌティファクトず非垞によく䌌おおり、関数がアセンブラヌで蚘述されおいないこずを明確に瀺しおいたす。人はこれを曞かないでしょう、なぜならこれは無意味です。スタックはメモリ内にあり、スタックぞのアクセスは垞にレゞスタよりも遅くなりたす。これがむンラむンアヌティファクトであるこずを提案しようず思いたす。

0x75AA8178, esi ebx , . , , , , . , , . , DWORD' , .

 ;Address Hex dump Command Comments 75AA80E7 > 0FB630 movzx esi, byte ptr [eax] 75AA80EA . 0FB619 movzx ebx, byte ptr [ecx] 75AA80ED . 2BF3 sub esi, ebx ;      DWORD' 75AA80EF .- 0F85 83000000 jne msvcrt.75AA8178 ;       ebx 75AA80F5 > 0FB670 01 movzx esi, byte ptr [eax+1] 75AA80F9 . 0FB659 01 movzx ebx, byte ptr [ecx+1] 75AA80FD . 2BF3 sub esi, ebx 75AA80FF .- 0F84 1EF9FEFF je msvcrt.75A97A23 ;    75A97A23 > 0FB670 02 movzx esi, byte ptr [eax+2] 75A97A27 . 0FB659 02 movzx ebx, byte ptr [ecx+2] 75A97A2B . 2BF3 sub esi, ebx 75A97A2D .- 74 15 je short msvcrt.75A97A44 ;    75A97A44 > 0FB670 03 movzx esi, byte ptr [eax+3] 75A97A48 . 0FB659 03 movzx ebx, byte ptr [ecx+3] 75A97A4C . 2BF3 sub esi, ebx 75A97A4E .- 0F84 5F190000 je msvcrt.75A993B3 ;  -   75A97A54 . 33DB xor ebx, ebx ;     ebx 75A97A56 . 85F6 test esi, esi 75A97A58 . 0F9FC3 setg bl 75A97A5B . 8D5C1B FF lea ebx, [ebx+ebx-1] 75A97A5F . 8BF3 mov esi, ebx 75A97A61 .- E9 4D190000 jmp msvcrt.75A993B3 ;    75A993B1 . |33F6 xor esi, esi 75A993B3 > |85F6 test esi, esi 75A993B5 .-|0F85 F6F6FFFF jne msvcrt.75A98AB1 



コヌドの耇補は良くありたせんが、恐ろしいこずではありたせんが、特にバむト数が結果に圱響しないため、ダブルバむトを比范するための連続したバむト比范は最良の方法ではありたせん。したがっお、おそらく゜ヌスコヌドがさらに奇劙であるために、このコヌドが倚少奇劙であるこずはすでに明らかです。

最初に2番目のトレヌスのログを芋おくださいルヌプの1回の繰り返しで8個のダブルワヌドが比范されたすが、これは良いこずですルヌプが展開されるこずは明らかです。私はこれがなぜ行われたのかに぀いおの仮定はありたせんが、これがどのように起こるかに぀いおの仮定がありたすたず、アセンブラヌコヌドを圢成したいく぀かのマクロの結果に非垞に䌌おいたす、そしお、 Microsoft Cコンパむラは、以前考えおいたほど良くないずいうこずです。
2番目のトレヌスルヌプのみ
 ;--------------------------------   main msvcrt.75A979B0 cmp edi, edx main msvcrt.75A979B2 jae msvcrt.75A993A7 main msvcrt.75A993A7 mov esi, [eax] ESI=00000000 main msvcrt.75A993A9 cmp esi, [ecx] main msvcrt.75A993AB jne msvcrt.75AA80E7 main msvcrt.75A993B1 xor esi, esi main msvcrt.75A993B3 test esi, esi main msvcrt.75A993B5 jne msvcrt.75A98AB1 main msvcrt.75A993BB mov esi, [eax+4] main msvcrt.75A993BE cmp esi, [ecx+4] main msvcrt.75A993C1 jne msvcrt.75AA811F main msvcrt.75A993C7 xor esi, esi main msvcrt.75A993C9 test esi, esi main msvcrt.75A?993CB jne msvcrt.75A98AB1 main msvcrt.75A993D1 mov esi, [eax+8] main msvcrt.75A993D4 cmp esi, [ecx+8] main msvcrt.75A993D7 jne msvcrt.75A97A9A main msvcrt.75A993DD xor esi, esi main msvcrt.75A993DF test esi, esi main msvcrt.75A993E1 jne msvcrt.75A98AB1 main msvcrt.75A993E7 mov esi, [eax+0C] main msvcrt.75A993EA cmp esi, [ecx+0C] main msvcrt.75A993ED jne msvcrt.75A97B1F main msvcrt.75A993F3 xor esi, esi main msvcrt.75A993F5 test esi, esi main msvcrt.75A993F7 jne msvcrt.75A98AB1 main msvcrt.75A993FD mov esi, [eax+10] main msvcrt.75A99400 cmp esi, [ecx+10] main msvcrt.75A99403 jne msvcrt.75A97BA4 main msvcrt.75A99409 xor esi, esi main msvcrt.75A9940B test esi, esi main msvcrt.75A9940D jne msvcrt.75A98AB1 main msvcrt.75A99413 mov esi, [eax+14] main msvcrt.75A99416 cmp esi, [ecx+14] main msvcrt.75A99419 jne msvcrt.75A97C29 main msvcrt.75A9941F xor esi, esi main msvcrt.75A99421 test esi, esi main msvcrt.75A99423 jne msvcrt.75A98AB1 main msvcrt.75A99429 mov esi, [eax+18] main msvcrt.75A9942C cmp esi, [ecx+18] main msvcrt.75A9942F jne msvcrt.75AA1172 main msvcrt.75A99435 xor esi, esi main msvcrt.75A99437 test esi, esi main msvcrt.75A99439 jne msvcrt.75A98AB1 main msvcrt.75A9943F mov esi, [eax+1C] main msvcrt.75A99442 cmp esi, [ecx+1C] main msvcrt.75A99445 jne msvcrt.75A97CFC main msvcrt.75A9944B xor esi, esi main msvcrt.75A9944D test esi, esi main msvcrt.75A9944F jne msvcrt.75A98AB1 main msvcrt.75A99455 add eax, edx EAX=031353B8 main msvcrt.75A99457 add ecx, edx ECX=031353B8 main msvcrt.75A99459 sub edi, edx EDI=0007FFE0 main msvcrt.75A9945B jmp msvcrt.75A979B0 



倧量のテストデヌタで、このコヌドは、安党でないコヌドを䜿甚した堎合よりも10ほど悪い結果を瀺したこずは泚目に倀したす。デヌタ配列を比范するずきのほずんどの時間は、メモリからの読み取り操䜜を必芁ずするこずは明らかです。これは、プロセッサキャッシュ、特にレゞスタよりもはるかに遅いです。しかし、プロセッサレゞスタを䜿甚した操䜜によっおのみ䞎えられたこのような重倧な違いは、コンパむラの最適化が非垞に重芁であるこずを瀺唆しおいたす。たずえば、クロック速床が遅いプロセッサなど、より匱いプロセッサでテストを行うず、はるかに倧きな違いが生じるこずを提案しようず思いたす。

結論


  1. 小さい7バむト以䞋配列をすばやく比范する必芁がある堎合は、最も明癜な方法ルヌプ内のバむト比范を䜿甚したす。倧きい配列は安党ではなく、他のすべおは巧劙です。
  2. .Net CLR . , JIT- , , CLR «» . . JIT- , . — C++- Intel, , (AMD Intel) .
  3. C-RunTime , , C- – MS VC. « » (http://rsdn.ru/article/optimization/optimization.xml): « , , ».



  1. Intel® 64 and IA-32 Architectures Software Developer Manuals .CHAPTER 2. Intel ® 64 Architecture
  2. AMD64 Architecture Programmer's Manual Volume 1 : Application Programming CHAPTER 1 Long Mode
  3. Intel® 64 and IA-32 Architectures Optimization Reference Manual . CHAPTER 3. Alignment
  4. Windows Internals, Sixth Edition, Part 1, CHAPTER 5 Processes, Threads, and Jobs
  5. Windows Internals, Sixth Edition, Part 2, CHAPTER 10. Memory Management
  6. Intel® 64 and IA-32 Architectures Software Developer Manuals. CHAPTER 17. TIME-STAMP COUNTER
  7. MSDN Magazine 2005, May: Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects
  8. Joe Duffy, Professional .NET Framework 2.0 (Programmer to Programmer). Chapter 3: Inside the CLR, Just-In-Time (JIT) Compilation




21.03.2014 4:37



前回、コメントに間違った゜ヌスを投皿したした。事実、私は知らなかった、たたは郚分的に曞いた実隓の過皋で、゜ヌスコヌドは蚘事から結果を埗るのに適しおいないずいうこずです。䟋

, , , .

, Saladin GitHub , , .

25.03.2014 6:37


RtlCompareMemory() .

, , SuppressUnmanagedCodeSecurity memcmp() RtlCompareMemory()
, , user-mode kernel-mode, CRT.
メモリの䞡方のブロックが垞駐しおいる堎合、RtlCompareMemoryの呌び出し元は任意のIRQLで実行できたす。

猫の䞋での結果
     Unsafe MemCMP RtlCompareMemory   1 279 1 : 001,7 1 : 003,2 1 : 008,8 1 : 001,0 1 278 1 : 001,8 1 : 003,2 1 : 008,9 1 : 001,0 2 325 1 : 001,3 1 : 002,9 1 : 006,1 1 : 001,0 4 374 1 : 001,1 1 : 002,6 1 : 009,0 1 : 001,0 7 422 1 : 001,0 1 : 002,5 1 : 007,0 1 : 001,1 12 426 1 : 001,0 1 : 002,5 1 : 006,9 1 : 001,7 19 490 1 : 001,0 1 : 002,2 1 : 006,0 1 : 001,9 32 560 1 : 001,0 1 : 002,0 1 : 005,3 1 : 002,7 54 622 1 : 001,0 1 : 002,0 1 : 005,4 1 : 003,8 89 802 1 : 001,0 1 : 001,7 1 : 004,3 1 : 004,7 147 1092 1 : 001,0 1 : 001,5 1 : 003,7 1 : 004,0 242 1571 1 : 001,0 1 : 001,4 1 : 003,0 1 : 004,2 398 2328 1 : 001,0 1 : 001,4 1 : 002,7 1 : 004,5 657 3664 1 : 001,0 1 : 001,2 1 : 002,2 1 : 004,6 1082 5519 1 : 001,0 1 : 001,2 1 : 002,1 1 : 005,0 1782 8554 1 : 001,0 1 : 001,2 1 : 002,1 1 : 005,3 2936 13520 1 : 001,0 1 : 001,2 1 : 002,0 1 : 005,5 4837 21771 1 : 001,0 1 : 001,2 1 : 002,0 1 : 005,6 7967 35464 1 : 001,0 1 : 001,2 1 : 001,9 1 : 005,7 13124 58073 1 : 001,0 1 : 001,2 1 : 001,9 1 : 005,7 21618 96457 1 : 001,0 1 : 001,2 1 : 001,9 1 : 005,7 35610 167952 1 : 001,0 1 : 001,1 1 : 001,8 1 : 005,4 58656 285282 1 : 001,0 1 : 001,1 1 : 001,8 1 : 005,3 96617 534712 1 : 001,0 1 : 001,1 1 : 001,6 1 : 004,7 159146 924569 1 : 001,0 1 : 001,1 1 : 001,6 1 : 004,5 262143 1520968 1 : 001,0 1 : 001,1 1 : 001,6 1 : 004,5 


デバッガヌりィンドりのスクリヌンショットでは、ラむブラリバヌゞョン



機胜は驚くほど小さいこずが刀明したした。すべおがここにありたす。この関数は、繰り返しプレフィックスcmps*を䜿甚しお文字列比范挔算を積極的に䜿甚したす。メモリの比范は盎接の順序で行われたす。これは、コマンドによっおリセットされるDFフラグによっお決定されcldたす。この関数のサむズず単玔さは、それがアセンブリ蚀語で曞かれおいるこずを瀺唆しおいたすが、それに぀いおはわかりたせん。
文字列比范操䜜のアルゎリズムは非垞に簡単です。たずえば、次のコマンドで指定されたDWORDを順方向に比范したす。
 repe cmpsd ;    ecx  DWORD'; 

Javaでは、次のように衚すこずができたす。
  while ( ( 0 != ecx ) & ( 0 == Compare( (DWORD) RAM[esi], (DWORD) RAM[edi] ) ) ) { --ecx; edi += 4; esi += 4; } 

単語ずバむトの配眮を想像できるほが同じ方法。
カットの䞋でのコメントず悲惚
  push esi ;      push edi cld ; DF <-- 0 mov esi, [esp+0C] ; esi <-- Source1 mov edi, [esp+10] ; edi <-- Source2 mov ecx, [esp+14] ; ecx <-- SIZE_T shr ecx, 2 ; ecx <-- (ecx >> 2) (    4) jz short smaller_4 ; repe cmpsd ;    ecx  DWORD'; jne short not_zero ; if ( !ZF ) goto not_zero; smaller_4: mov ecx, [esp+14] ; ecx <-- SIZE_T and ecx, 00000003 ; ecx &= 3 jz short return_SIZE_T ; if ( ZF ) goto return_SIZE_T; repe cmpsb ;     jne short found_difference return_SIZE_T: mov eax, [esp+14] ; eax <-- SIZE_T pop edi ;    pop esi ret 0C ; return eax not_zero: sub esi, 4 ; esi -= 4 sub edi, 4 ; edi -= 4 mov ecx, 4 ; ecx -= 4 repe cmpsb ;     found_difference: dec esi ; --esi sub esi, [esp+0C] ; esi <-- (esi - Source1) mov eax, esi ; eax <-- esi pop edi ;    pop esi ret 0C ; return eax 

Source: https://habr.com/ru/post/J214841/


All Articles