47

ライブ データ キャプチャのパーセンタイルを決定するアルゴリズムを探しています。

たとえば、サーバー アプリケーションの開発を考えてみましょう。

サーバーの応答時間は、17 ミリ秒 33 ミリ秒 52 ミリ秒 60 ミリ秒 55 ミリ秒などです。

90 パーセンタイル応答時間、80 パーセンタイル応答時間などをレポートすると便利です。

単純なアルゴリズムは、各応答時間をリストに挿入することです。統計が要求されたら、リストをソートし、適切な位置で値を取得します。

メモリ使用量は、リクエスト数に比例して増加します。

限られたメモリ使用量で「おおよその」パーセンタイル統計を生成するアルゴリズムはありますか? たとえば、何百万ものリクエストを処理する方法でこの問題を解決したいとしますが、パーセンタイルの追跡には 1 キロバイトのメモリしか使用したくないとします (パーセンタイルが想定されているため、古いリクエストの追跡を破棄することはオプションではありませんすべてのリクエストに適用されます)。

また、分布のアプリオリな知識がないことも必要です。たとえば、事前にバケットの範囲を指定したくありません。

4

8 に答える 8

34

より多くのデータを取得するときにメモリ使用量を一定に保ちたい場合は、何らかの方法でそのデータを再サンプリングする必要があります。これは、何らかのリビニングスキームを適用する必要があることを意味します。リビニングを開始する前に、一定量の生の入力を取得するまで待つことはできますが、完全に回避することはできません。

あなたの質問は、「私のデータを動的にビニングする最良の方法は何ですか」という質問ですか? 多くのアプローチがありますが、受け取る可能性のある値の範囲または分布に関する仮定を最小限に抑えたい場合、単純なアプローチは、固定サイズkのバケットを対数的に分布した幅で平均化することです。たとえば、一度に 1000 個の値をメモリに保持したいとします。kのサイズを100 とします。最小解像度を 1ms とします。それで

  • 最初のバケットは、0 ~ 1 ミリ秒 (幅 = 1 ミリ秒) の値を処理します。
  • 2 番目のバケット: 1 ~ 3 ミリ秒 (w=2 ミリ秒)
  • 3 番目のバケット: 3 ~ 7 ミリ秒 (w=4 ミリ秒)
  • 4 番目のバケット: 7 ~ 15 ミリ秒 (w=8 ミリ秒)
  • ...
  • 10 番目のバケット: 511 ~ 1023 ミリ秒 (w=512 ミリ秒)

このタイプの対数スケールのアプローチは、ハッシュ テーブル アルゴリズムで使用されるチャンキング システムに似ており、一部のファイル システムやメモリ割り当てアルゴリズムで使用されます。データのダイナミック レンジが大きい場合に適しています。

新しい値が入ってくると、要件に応じてリサンプリングする方法を選択できます。たとえば、移動平均を追跡したり、先入れ先出し法やその他のより洗練された方法を使用したりできます。1 つのアプローチについては、 Kademliaアルゴリズムを参照してください( Bittorrentで使用されます)。

最終的に、リビニングはいくつかの情報を失うに違いありません。ビニングに関する選択によって、失われる情報の詳細が決まります。別の言い方をすれば、一定サイズのメモリ ストアは、ダイナミック レンジサンプリングの忠実度の間のトレードオフを意味するということです。そのトレードオフをどのように行うかはあなた次第ですが、サンプリングの問題と同様に、この基本的な事実を回避することはできません。

長所と短所に本当に興味がある場合は、このフォーラムの回答で十分とは言えません。サンプリング理論を調べる必要があります。このトピックに関する膨大な量の研究が利用可能です。

価値のあることとしては、サーバー時間の動的範囲が比較的小さいと思われるため、一般的な値のより高いサンプリングを許可するためのより緩やかなスケーリングにより、より正確な結果が得られる可能性があります.

編集:コメントに答えるために、単純なビニングアルゴリズムの例を次に示します。

  • 10 個のビンに 1000 個の値を保存します。したがって、各ビンには 100 個の値が保持されます。各ビンが動的配列 (Perl または Python の用語では「リスト」) として実装されていると仮定します。
  • 新しい値が入ってくると:

    • 選択したビンの制限に基づいて、保管するビンを決定します。
    • ビンがいっぱいでない場合は、値をビン リストに追加します。
    • ビンがいっぱいの場合は、ビン リストの一番上にある値を削除し、新しい値をビン リストの一番下に追加します。これは、古い値が時間の経過とともに破棄されることを意味します。
  • 90 パーセンタイルを見つけるには、ビン 10 を並べ替えます。90 パーセンタイルは、並べ替えられたリストの最初の値です (要素 900/1000)。

古い値を破棄したくない場合は、代わりに使用する別のスキームを実装できます。たとえば、ビンがいっぱいになると (私の例では 100 個の値に達する)、最も古い 50 個の要素 (つまり、リストの最初の 50 個) の平均を取り、それらの要素を破棄してから、新しい平均要素をこれにより、49 個の新しい値を保持するスペースを持つ 51 個の要素のビンが残ります。これはリビニングの簡単な例です。

リビニングのもう 1 つの例は、ダウンサンプリングです。たとえば、ソートされたリストの 5 番目ごとの値を破棄します。

この具体例がお役に立てば幸いです。ここで重要な点は、一定のメモリ エージング アルゴリズムを実現する方法はたくさんあるということです。要件を考慮して満足できるものを決定できるのは、あなただけです。

于 2009-08-08T14:22:00.657 に答える
20

このトピックに関するブログ投稿を公開したことがあります。ブログは現在廃止されていますが、記事は以下に完全に含まれています。

基本的な考え方は、正確な計算の要件を減らして、「応答の 95% パーセントが 500ms ~ 600ms 以下である」 (すべての正確な 500ms ~ 600ms のパーセンタイル) を優先することです。


最近、Web アプリケーションの 1 つの応答時間が遅くなったと感じ始めたので、アプリケーションのパフォーマンスを微調整することに時間を費やすことにしました。最初のステップとして、現在の応答時間を完全に把握したいと考えました。パフォーマンス評価では、最小、最大、または平均の応答時間を使用することはお勧めできません。「『平均』はパフォーマンス最適化の悪であり、多くの場合、『病院内の患者の平均体温』と同じくらい役に立ちます」( MySQL パフォーマンス ブログ)。代わりに、パフォーマンス チューナーはパーセンタイルを確認する必要があります。: 「パーセンタイルは、観測値の特定の割合がそれを下回る変数の値です」(ウィキペディア)。つまり、95 パーセンタイルは、リクエストの 95% が完了した時間です。したがって、パーセンタイルに関連するパフォーマンス目標は、「95 パーセンタイルは 800 ミリ秒未満にする必要がある」のようになります。このようなパフォーマンス目標を設定することは 1 つのことですが、稼働中のシステムでそれらを効率的に追跡することは別のことです。

パーセンタイル計算の既存の実装を探すのにかなりの時間を費やしました (例: ここまたはここ)。それらはすべて、すべてのリクエストの応答時間を保存し、オンデマンドでパーセンタイルを計算するか、新しい応答時間を順番に追加する必要がありました。これは私が欲しかったものではありませんでした。私は、何十万ものリクエストに対してメモリと CPU の効率的なライブ統計を可能にするソリューションを望んでいました。何十万ものリクエストの応答時間を保存し、必要に応じてパーセンタイルを計算することは、CPU 効率もメモリ効率も良くないように思えます。

私が望んでいたような解決策は、単に存在しないようです。考え直して、別のアイデアを思いつきました。私が探していたタイプのパフォーマンス評価では、正確なパーセンタイルを取得する必要はありません。「95 パーセンタイルは 850 ミリ秒から 900 ミリ秒の間です」のようなおおよその答えで十分です。このように要件を下げると、特に可能な結果の上限と下限がわかっている場合は、実装が非常に簡単になります。たとえば、私は数秒を超える応答時間には興味がありません。10 秒であろうと 15 秒であろうと、いずれにしても非常に悪い結果です。

したがって、実装の背後にあるアイデアは次のとおりです。

  1. ランダムな数の応答時間バケットを定義します (例: 0-100ms100-200ms200-400ms、…)400-800ms800-1200ms
  2. 応答数と各バケットの応答数をカウントします (応答時間が 360ms の場合、200ms ~ 400ms のバケットのカウンタをインクリメントします)
  3. 合計が合計の n パーセントを超えるまでバケットのカウンタを合計して、n 番目のパーセンタイルを推定します

それはとても簡単です。そして、ここにコードがあります

いくつかのハイライト:

public void increment(final int millis) {
    final int i = index(millis);
    if (i < _limits.length) {
        _counts[i]++;
    }
    _total++;
}
 
public int estimatePercentile(final double percentile) {
    if (percentile < 0.0 || percentile > 100.0) {
        throw new IllegalArgumentException("percentile must be between 0.0 and 100.0, was " + percentile);
    }
 
    for (final Percentile p : this) {
        if (percentile - p.getPercentage() <= 0.0001) {
            return p.getLimit();
        }
    }
    return Integer.MAX_VALUE;
}

このアプローチでは、バケットごとに 2 つの int 値 (= 8 バイト) しか必要なく、1K のメモリで 128 のバケットを追跡できます。50 ミリ秒の粒度を使用して Web アプリケーションの応答時間を分析するには十分です)。さらに、パフォーマンスのために、一部のインクリメントが失われる可能性があることを認識して、意図的に同期を行わずに (たとえば AtomicIntegers を使用して) これを実装しました。

ちなみに、Google チャートと 60 パーセンタイル カウンターを使用して、収集した 1 時間の応答時間から素敵なグラフを作成できました。

パーセンタイル グラフ

于 2009-11-07T08:33:40.440 に答える
18

(この質問が出されてからかなり時間が経ちましたが、関連する研究論文をいくつか指摘したいと思います)

過去数年間、データ ストリームのおおよそのパーセンタイルに関するかなりの量の研究が行われてきました。完全なアルゴリズム定義を含むいくつかの興味深い論文:

これらの論文はすべて、データ ストリーム上のおおよそのパーセンタイルを計算するための準線形空間複雑度を持つアルゴリズムを提案しています。

于 2011-10-05T10:08:52.183 に答える
17

この問題には多くの優れた近似アルゴリズムがあると思います。適切な最初のアプローチは、単純に固定サイズの配列 (たとえば 1K 相当のデータ) を使用することです。ある確率 p を固定します。リクエストごとに、確率 p で、その応答時間を配列に書き込みます (そこにある最も古い時間を置き換えます)。配列はライブ ストリームのサブサンプリングであり、サブサンプリングは分布を維持するため、その配列で統計を行うと、完全なライブ ストリームの統計の近似値が得られます。

このアプローチにはいくつかの利点があります。アプリオリな情報は必要なく、コーディングも簡単です。特定のサーバーについて、どの時点でバッファーの増加が答えにほとんど影響を与えないかを、すばやく構築して実験的に判断できます。これは、近似が十分に正確であるポイントです。

十分に正確な統計を得るにはあまりにも多くのメモリが必要であることがわかった場合は、さらに掘り下げる必要があります。適切なキーワードは、「ストリーム コンピューティング」、「ストリーム統計」、そしてもちろん「パーセンタイル」です。「怒りと呪い」のアプローチを試すこともできます。

于 2009-08-11T09:02:16.963 に答える
3

大きな整数の動的配列T[]などを使用します。ここで、T[n]は応答時間がnミリ秒であった回数をカウントします。サーバーアプリケーションで実際に統計を実行している場合は、とにかく250ミリ秒の応答時間が絶対的な制限になる可能性があります。したがって、1 KBは、0〜250のミリ秒ごとに1つの32ビット整数を保持し、オーバーフロービン用にいくらかの余裕があります。より多くのビンを持つものが必要な場合は、1000ビンに対して8ビット数を使用し、カウンターがオーバーフローした瞬間(つまり、その応答時間での256番目の要求)に、すべてのビンのビットを1つ下げます(実質的に値を半分にします)すべてのビン)。これは、最も訪問されたビンがキャッチする遅延の1/127未満をキャプチャするすべてのビンを無視することを意味します。

本当に必要な場合は、リクエストの初日を使用して、妥当な固定のビンのセットを作成することをお勧めします。ライブでパフォーマンスに敏感なアプリケーションでは、動的なものはすべて非常に危険です。そのパスを選択した場合は、自分が何をしているのかがよくわかります。または、ある日、統計トラッカーが本番サーバーで突然90%のCPUと75%のメモリを消費している理由を説明するためにベッドから呼び出されます。

追加の統計について:平均と分散については、メモリをほとんど消費しない優れた再帰アルゴリズムがいくつかあります。中心極限定理は、十分に多数の独立変数から生じる分布が、使用できる正規分布(平均と分散によって完全に定義される)に近づくと述べているため、これら2つの統計はそれ自体で多くの分布に十分に役立ちます。最後のN(Nは十分に大きいが、メモリ要件によって制約されている)での正規性検定の1つで、正規性の仮定がまだ成立するかどうかを監視します。

于 2009-08-08T15:08:01.797 に答える
0

次の構造を試すことができます。

入力nを受け取ります。n = 100

[min, max]の範囲の配列をmincountで並べ替えます。

値xの挿入– xの最小範囲のバイナリ検索。見つからない場合は、前の範囲を取ります (ここでmin < x )。値が範囲 ( x <= max )に属する場合、count をインクリメントします。それ以外の場合は、 [min = x, max = x, count = 1] で新しい範囲を挿入します。

範囲の数が2*nに達した場合 –奇数エントリから最小値を、偶数エントリから最大値を取得し、それらのcountを合計して、配列をn (半分)に折りたたむ/マージします。

つまり取得します。p95 walk の終わりからカウントを合計すると、次の加算がしきい値sum >= 95%に達するまで、 p95 = min + (max - min) * partialを取ります。

測定のダイナミックレンジに落ち着きます。nを変更して、精度をメモリと引き換えにすることができます (CPU の程度は低くなります)。値をより離散的にすると、つまり。挿入前に 0.01 に丸めることにより、範囲内でより早く安定します。

各範囲が均一に分散されたエントリを保持すると仮定しないことで、精度を向上させることができます。あなたにavg = sum / countを与える値の合計のような安価なもの、それが位置する範囲からより近い p95 値を読み取るのに役立ちます。

それらを回転させることもできます。m = 1 000 000エントリの後、新しい配列への入力が開始され、配列内のカウントの加重合計として p95 が取得されます (配列 B に A のカウントの 10% がある場合、p95 値に 10% が寄与します)。

于 2021-07-12T04:08:27.463 に答える