0

キーとレコード番号の間にインデックスがあるキー レコード ルックアップを作成しています。これはキーでソートされます。速度の最適化のために私が持っているものよりもこれをうまく行う方法はありますか?

typedef struct
{
    char key[MAX_KEYLEN];
    int  rec;
} KeyRecPair;

typedef struct
{
    KeyRecPair *map;
    int         numRecs;
} KeyRecMap;

int GetRecFromKey(char *key, KeyRecMap *theMap)
{
    int cmpValue, bottom = 0;
    int half = theMap->numRecs / 2;
    int top = theMap->numRecs - 1;

    while (bottom != top)
    {
        cmpValue = strncmp(key, theMap->map[half].key, MAX_KEY_LEN); 

        if (cmpValue > 0)
        {
            /*top stays*/
            bottom = half + 1;
            half  = bottom + (top - bottom) / 2;
            continue;
        }
        if (cmpValue < 0)
        {
            /*bottom stays*/
            top = half - 1;
            half  = bottom + (top - bottom) / 2;
            continue;
        }
        return theMap->map[half].rec;
    }

    if (0 == strncmp(key, theMap->map[half].key, MAX_KEY_LEN))
        return theMap->map[half].rec;
    return 0;
}
4

9 に答える 9

4

ライブラリ関数は、bsearch実装する適切な比較関数を指定して、配列に対してバイナリ検索を実行します。ライブラリ関数であるため、十分に最適化されており、(できれば) バグがありません。

于 2008-12-10T17:45:30.753 に答える
4

あなたの時間のかなりの部分が strncmp に費やされます。

関数呼び出しがオーバーヘッドになるのを避けるために、強制的にinlined にするか、インラインで書き直すことをお勧めします。

勇気があれば、ループを 1 回か 2 回アンロールして、パフォーマンスの向上を確認することもできます。

文字列が実際に char の配列の固定長である場合、長さを 4 の倍数にして、一度に 1 バイトではなく、一度に 4 バイトを unsigned int 比較と比較することができます。

プロファイラーを持っていない場合は、取得する必要があります。プロファイラーを使用すると、さまざまな実装の相対的なコストを簡単に確認できます。

別のオプションは、データを整理する別の方法を選択することです。インスピレーションについては、 AVL ツリーを参照してください。言及されている他のものと同様に、ある種のハッシュ関数を選択することは、実行可能なオプションかもしれません

于 2008-12-11T19:35:18.380 に答える
2

バイナリ検索を使用してアイテムを見つける代わりに、O(1) ルックアップ特性を持つハッシュ マップの方が適している場合があります。ただし、素朴なアプローチでは衝突が多くなると遅くなる可能性があります。ただし、このペーパーでは、O(log(n) / log(32)) のアクセス時間を持つツリーのようなハッシュマップを作成する方法について説明します。これは通常、通常のハッシュマップの実装よりも優れています。(固定配列 + リンク リストの実装)。

于 2008-12-11T11:20:19.237 に答える
1

文字列ではないキーを使用できる可能性はありますか? または少なくとも可能な限り短い文字列ですか?(MAX_KEYLEN の値とは) ループのすべての反復で strcmp が実行される可能性が高いのは、検索の遅い部分の 1 つです。

于 2008-12-11T19:37:40.793 に答える
1

これを最適化したい理由はありますか?プロファイラーを使用してプログラムを実行し、ルックアップが総実行時間のかなりの部分を占めていると判断しましたか? どれだけ早く取得できるかだけに興味がありますか? (私の意見では、どちらも正当な理由です。) ランダムに最適化するだけなら、しないでください。

また、ベンチマークすることを忘れないでください。関数の 2 つのバージョンのどちらが最新のシステムで高速かを判断するのは困難です (私の Z80 では簡単でした)。キャッシュ ミスの数は、誤って予測された分岐の数よりも重要である場合もあれば、そうでない場合もあります。

于 2008-12-11T19:52:41.793 に答える
0

私が考えることができる唯一の潜在的な最適化はhalf、残りのサブセットを同じ数の要素を持つ2つの半分に分割する代わりに、計算に黄金比に似たものを使用することです。

        if (cmpValue > 0)
        {
                /*top stays*/
                bottom = half + 1;
                half = bottom + (top - bottom) * 3 / 5;
                continue;
        }
        if (cmpValue < 0)
        {
                /*bottom stays*/
                top = half - 1;
                half = bottom + (top - bottom) * 2 / 5;
                continue;
        }
于 2008-12-11T10:26:44.307 に答える
0

2 U で除算する代わりに、ビット シフト演算子を利用できます。

=> for /2 u を使用できます >> 1

于 2008-12-11T10:59:00.607 に答える
0

ループごとに 1 回計算する必要があるためhalf、使用する直前に 1 回だけ実行しないのはなぜですか? これにより、複雑に見える (少なくとも比較的言えば) 2 行のコードが削減されます。

于 2008-12-11T11:13:27.817 に答える
0

まともなオプティマイザーがこれを行うことを期待していますが、アクセスごとに逆参照するのではなく、レジスターに入る可能性が半分になるように、 theMap->map をローカルに配置します。繰り返しますが、オプティマイザーがこれを行うことを期待しているので、アセンブリの出力も確認することをお勧めします。

Visual Studio 2008 のリリースの出力を見たところ、コードはかなりうまく機能しています。たとえば、比較コードは次のようになります。

; 30   :         if (cmpValue > 0)
test    eax, eax
jle SHORT $LN11@GetRecFrom
; 31   :         {
; omitted inner block for > case.
$LN11@GetRecFrom:
; 37   :         if (cmpValue < 0)
jge SHORT $LN2@GetRecFrom

基本的にcmpValueを再テストせずに分岐する分岐です。いい感じです。

theMap->map をローカルに配置することにはわずかな利点がありますが、小さいものです。MAX_KEY_LEN が適切な 4 の倍数ではなく、構造体がパディングされていない場合は、間違いなく int を構造体の最初に配置する必要があります。

于 2008-12-11T20:36:12.530 に答える