Benchmarkin

7749 ワード

意味のある仕事を完成させるには抽象が重要だが、副作用をもたらす.仕事がうまくいくためには、バッチ処理の具体的な論理を決定するために、いくつかの細部を洞察する必要があります.特定のコンテキストの有用な情報を見つけることは非常に重要であり、挑戦的であり、効率的なプログラミングの核心である.benchmarkingを使用すると、エンジニアは、コード内の実行効率のベールを剥がし、得られた情報を利用して最適化することができる.これは、appをより速く実行したいエンジニア(または自重したいエンジニア)一人一人にとって必須のツールです.
benchmark」という言葉は19世紀にさかのぼることができる.benchmarkは、石を平板に切断するカッターまたは測定用のブラケットである.後にこの語の「物を測る基準」の比喩義が様々な分野に応用された.
プログラミングでは、benchmarkbenchmarkingは少し意味的な違いがあります.benchmarkは、プログラムがハードウェアおよびソフトウェア上の実行効率を明確に測定し、比較するものである.対照的にbenchmarkingは、測定効率のコードである.

Objective-CでのBenchmarkingによる効率測定

Benchmarkは他の認知論と同じ法則に従うべきで、統計量のような科学的方法のように共通の理解がある.
科学的方法は一連の論理ステップをカバーして問題を推論する.
  • 質問
  • 構造仮説
  • 予想結果
  • 検証仮説
  • 分析結果
  • プログラミングに適用すると、一般的に2つの問題が提起されます.
  • このコードの絶対効率はいくらですか?計算力とメモリの上限に達しましたか?異なるサンプルサイズを適用する際のボトルネック操作は何ですか?
  • このコードの相対効率はいくらですか?方法Aと方法Bのどちらが速いですか?

  • オペレーティングシステム自体のすべての基本的な要因は可変性が非常に強いため、性能は大量の試験によって測定されるべきである.ほとんどの用途では、サンプル数は105〜108で直接的に妥当である.

    初回:CFAbsoluteTimeGetCurrent


    ここでは、可変配列に要素を追加する効率を見てみましょう.benchmarkを確立するために、countは、追加する必要がある要素の数を示し、iterationsは、このテストが何回実行されるかを示します.
    static size_t const count = 1000;
    static size_t const iterations = 10000;
    

    申請メモリをテストする時間は必要ありませんので、benchmarkの外部で配列に追加された要素を一度だけ宣言します.
    id object = @"";
    

    このbenchmarkingを作るのは簡単です.コードの実行前に1回の時間を記録し、実行後に1回記録し、時間差を比較します.mach_absolute_timeを包装したCACurrentMediaTime()法を簡単に使用して、秒単位で時間を測定することができます.NSDateまたはCFAbsoluteTimeGetCurrent()のオフセット量とは異なり、mach_absolute_time()およびCACurrentMediaTime()は内蔵クロックに基づいており、より正確により原子化的に測定することができ、外部時間の変化(例えば、時間領域の変化、夏時間制、秒の突然変異など)によって変化することはない.forサイクルは、countおよびiterationsを増加させるために使用される.各サイクルは@autoreleasepoolに包まれ、メモリ消費量を低減するために使用される.
    具体的な手順は次のとおりです.
    CFTimeInterval startTime = CACurrentMediaTime();
    {
        for (size_t i = 0; i < iterations; i++) {
            @autoreleasepool {
                NSMutableArray *mutableArray = [NSMutableArray array];
                for (size_t j = 0; j < count; j++) {
                    [mutableArray addObject:object];
                }
            }
        }
    }
    CFTimeInterval endTime = CACurrentMediaTime();
    NSLog(@"Total Runtime: %g s", endTime - startTime);
    

    この例ではstartTimeとendTimeの間のblockコードは必要ありませんが、可読性を向上させるために、変数を分離して大規模な突然変異が発生するコードをより明確にすることができます.

    2発目:dispatch_benchmark

    dispatch_benchmarklibdispatch(Grand Central Dispatch)の一部である.しかし、厳粛に言えば、この方法は公開されていないので、自分で宣言しなければなりません.
    extern uint64_t dispatch_benchmark(size_t count, void (^block)(void));
    

    公開された関数定義がないため、dispatch_benchmarkXcodeにも公開されていない.幸いなことにmanページがあります.
    あなたは自分ですべてのmanを理解するべきで、完全なmanの内容はここを见ます
    man dispatch_benchmark(3)
    The dispatch_benchmark function executes the given block multiple times according to the count variable and then returns the average number of nanoseconds per execution. This function is for debugging and performance analysis work. For the best results, pass a high count value to dispatch_benchmark. Please look for inflection points with various data sets and keep the following facts in mind:
  • Code bound by computational bandwidth may be inferred by proportional changes in performance as concurrency is increased.
  • Code bound by memory bandwidth may be inferred by negligible changes in performance as concurrency is increased.
  • Code bound by critical sections may be inferred by retrograde changes in performance as concurrency is increased.
  • Intentional: locks, mutexes, and condition variables.
  • Accidental: unrelated and frequently modified data on the same cache-line.


  • これらのドキュメントを省略した場合は、必ずもう一度読んでください.これらのドキュメントは非常に役に立ちます.この関数の使い方をよりよく説明するために、ドキュメントには非常に指導的なガイドラインも書かれています.
    前の例をdispatch_benchmarkで書くとこうなります
    uint64_t t = dispatch_benchmark(iterations, ^{
        @autoreleasepool {
            NSMutableArray *mutableArray = [NSMutableArray array];
            for (size_t i = 0; i < count; i++) {
                [mutableArray addObject:object];
            }
        }
    });
    NSLog(@"[[NSMutableArray array] addObject:] Avg. Runtime: %llu ns", t);
    

    見たでしょう、だいぶよくなったでしょう.以前の秒計時よりもミリ秒が正確であり、dispatch_benchmarkも手動書き込みサイクルのCFAbsoluteTimeGetCurrent()よりも文法構造的に優れているように見える.

    NSMutableArray array対決arrayWithCapacity:!

    Objective-Cで直接benchmarkを実行する方法がわかりました.では、比較速度のテストをしましょう.
    この例では、「capacityパラメータの入力と直接初期化の違い」という問題を依然として考えています.あるいはもっと直接的に「-arrayWithCapacity:を使うか使わないか(これは問題です)」です.
    一緒に見てみましょう.
    uint64_t t_0 = dispatch_benchmark(iterations, ^{
        @autoreleasepool {
            NSMutableArray *mutableArray = [NSMutableArray array];
            for (size_t i = 0; i < count; i++) {
                [mutableArray addObject:object];
            }
        }
    });
    NSLog(@"[[NSMutableArray array] addObject:] Avg. Runtime: %llu ns", t_0);
    
    uint64_t t_1 = dispatch_benchmark(iterations, ^{
        @autoreleasepool {
            NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:count];
            for (size_t i = 0; i < count; i++) {
                [mutableArray addObject:object];
            }
        }
    });
    NSLog(@"[[NSMutableArray arrayWithCapacity] addObject:] Avg. Runtime: %llu ns", t_1);
    

    結果
    iOS 7.1を搭載したiPhoneシミュレータをテストした結果、以下のようになりました.
    [[NSMutableArray array] addObject:]: Avg. Runtime 26119 ns
    [[NSMutableArray arrayWithCapacity] addObject:] Avg. Runtime: 24158 ns
    

    大規模なサンプルのテストを経て、capacityを用いたかどうかは7%の効率差をもたらした.
    結果は論争の余地はないが(私たちのbenchmarkは完璧に働いている)、本当に重要なのはこの結果が発生した原因を説明することだ.誤った考えは,benchmarkパラメータで初期化することが最適であると結論した.正しい考えは、このcapacityの結果は、私たちが質問を続けるべきであることを示しています.
  • これらの効率消費は何を意味しますか?不適切な最適化を避けるために,この効率の違いを大規模なシステムで無視できるかどうか考えてみよう.
  • 配列要素の個数を変えると、何か違う結論が出ますか?capacityパラメータを用いたため,配列要素の増加を回避したと推測できるが,大規模データのn値消費を計算するにはどの程度の大きさがあるのだろうか.
  • 他の集合タイプ、例えばNSMutableSetやNSMutableDictionaryのcapacityパラメータの初期化効率はどうでしょうか.民衆には真実が必要だ!

  • Benchmarking常識ガイド

  • あなたが答えなければならない問題を知っています.私たちは常に不思議な思考の代わりに思考に力を入れていますが、科学的な方法の水没から自分を守らなければなりません.不完全な推理を支持するべきではありません.結論を出す前に、大きな背景の下であなたの結果が何を意味するかを理解するのに時間がかかります.
  • appのコミットコードにbenchmarkingを追加しないでください.benchmarkは、appがApp Storeによって拒否される可能性があります.dispatch_benchmarkコードは、最終的にコミットされた製品に追加されるべきではありません.benchmarkは、個別のプロジェクトブランチまたは独立したテスト例に分離されるべきである.
  • Instrumentsを使用して、より有用な結果を得る.一連の計算プロセスの実行絶対時間は確かに価値があることが分かったが、メモリの使用を減らすための完全な参考にはならないかもしれない.Benchmarkingを使用して、疑問のあるコードのスタック呼び出しとメモリ使用量を分析すると、このコードに何が起こったのかよく理解できます.
  • 実際のデバイス上でbenchmark.他のいかなる効率測定ツールのように、測定は結局本当の機械を走らなければならない.多くの場合、シミュレータと実際の装置の効率測定結果は一致するが、念のためにそうする価値がある.
  • を早期に最適化しないでください.この言葉はいくら強調しても過言ではない.エンジニアの一般的な傾向は、本当の原因を発見する前に、彼らが考えている「遅いコード」に注目しすぎることです.ベテランでも応用のボトルネックを予測しやすい.影を追うのに時間を無駄にしないでください.Instrumentsにあなたのアプリケーションがどこで最も多くの時間を費やしたのかを教えてあげましょう.
  • Instrumentsはかつて物理学の中の粒子化実験を「時計がどのように作られたのか、機械がどのように時計の山を組み合わせて無駄な歯車を取り除いて動いたのか」にたとえたことがある.Richard Feynmanコードの感じはこれのようです.
    これらの計算世界に隠されている抽象層の具体的な実装の詳細を持っており、大規模なコード階層で何がその核心的な役割を果たしているのか、私たちが何を得ることができるのかを理解することができる場合があります.
    科学と基準統計の厳格なプログラムを通じて、開発者はそのコードの性能のお父さんの方面で理由の十分な結論を出すことができます.これらの原則と慣例を自分のプロジェクトに適用して、自分に合った結論を出して、それに基づいて最適化します.