4

このコードのプロファイルを作成しておらず、クリティカルパスでもないということで、この前置きをさせてください。これは主に私自身の好奇心のためです。

静的intを既知のエラー値に宣言/定義する関数があります。これによりコードが分岐します。ただし、関数が成功した場合、ブランチが二度と取得されないことは確実です。このためのコンパイル時の最適化はありますか?具体的にはGNU/gcc / glibc?

だから私はこれを持っています:

static unsigned long volatile *getReg(unsigned long addr){

    static int fd = -1;

    if (fd < 0){
        if (fd = open("file", O_RDWR | O_SYNC) < 0){
            return NULL;
        }
    }
}

したがって、関数が正常に完了すると(この関数がnullを返す場合は、プログラムを終了します)、fdは今後のすべての呼び出しで有効になり、最初の分岐を取得することはありません。__builtin_expect()マクロがあることを知っているので、次のように書くことができます

if (__builtin_expect((fd<0),0){

しかし、私が理解していることから、これはコンパイラーへのヒントにすぎず、それでも条件チェックを実行する必要があります。また、99.9999%のケースでは十分すぎるため、パフォーマンスの向上はごくわずかです。

初めて実行した後の最初の条件チェック(fd <0)さえも防ぐ方法があるのではないかと思いました。

4

5 に答える 5

4

簡単な答えは「いいえ」です。

つまり、確かに、関数へのポインターやコードのモンキーパッチなどを使ってトリックをプレイすることはできますが、それはテストを行うよりもほぼ確実に遅くなります。

ブランチは、予測が誤っている場合にのみ高価になります。 __builtin_expectこのブランチが最初に誤って予測されるだけであることを保証するように手配します。

ここでは、文字通り1〜2サイクルについて話しているのですが、CPUがこのコードの近くで他に何をしているのかによっては、そうではないかもしれません。

[アップデート]

このようなものが実際に1秒間に数百万回または数十億回呼び出されている場合は、コードを再構築してfd早期に初期化し、テストを気にせずに繰り返し使用することで対処します。たとえば、initGlobalState();main()の先頭近くに呼び出しを追加して、ファイルを開くことができます。(対応destroyGlobalState();するものを再度閉じる必要があります。)

そしてもちろん、ファイル記述子は恐ろしい例です。ファイル記述子に対して行うことは、とにかく1〜2サイクル以上かかるからです。

ちなみに、C ++では、コンストラクタ、デストラクタ、およびRAIIイディオムにより、この種のアプローチは非常に自然になります。

于 2011-06-02T23:20:56.813 に答える
2

関数を2つに分割し、独自のソースファイルで...そして呼び出し元にそれについて心配させます:)

static int fd;

unsigned long volatile *getReg(unsigned long addr) {
  /* do stuff with fd and addr */
  return 0;
}

int getRegSetup(void) {
  fd = open("file", O_RDWR | O_SYNC);
  if (fd < 0) return 1;                /* error */
  /* continue processing */
  return 0;                            /* ok */
}

次に、発信者は

  /* ... */
  if (getRegSetup()) {
    /* error */
  } else {
    do {
      ptr = getReg(42);
    } while (ptr);
  }
  /* ... */
于 2011-06-02T23:20:43.797 に答える
1

これを修正する方法の1つは、関数ポインターを使用してメソッドを呼び出すことです。関数ptrをlong関数に初期化し、最初の呼び出しの最後に、追加の初期化なしでバージョンに設定します。

とは言うものの、それは絶対的なメンテナンスの悪夢のように聞こえ、確かに1つのブランチを回避する価値はありません-しかし、ブランチを取り除きます..(そして、関数の長さに応じて、関数がインライン化される可能性を確実に取り除きますほぼ確実に有害になります)

于 2011-06-03T17:22:05.193 に答える
0

好奇心旺盛な人のために、これは私が思いついたものです。これは、より大規模で長時間実行されるプログラムのモジュールであることに注意してください。また、それはレビューされておらず、とにかく基本的に悪いハックです.

__attribute__((noinline)) static unsigned int volatile *get_mem(unsigned int addr) {
    static void *map = 0 ;
    static unsigned prevPage = -1U ;
    static int fd = -1;
    int poss_err = 0;
    register unsigned page = addr & ~MAP_MASK ;

    if ( unlikely(fd < 0) ) {
        if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
            longjmp(mem_err, errno);
        }
    }
    if ( page != prevPage ) {
        if ( map ) {
            if (unlikely((munmap(map,MAP_SIZE) < 0))) poss_err = 1;
        }
        if (unlikely((map = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, page )) == MAP_FAILED)) longjmp(mem_err, errno);

        prevPage = page ;
    }
    return (unsigned int volatile *)((char *)map+(addr & MAP_MASK));
}

static void set_reg(const struct reg_info * const r, unsigned int val)
{
    unsigned int volatile * const mem = get_mem(r->addr);
    *mem = (*mem & (~(r->mask << r->shift))) | (val << r->shift);
}

// This isn't in the final piece. There are several entry points into this module. Just an example

static int entryPoint(unsigned int value){

    if (setjmp(mem_err)!=0) {
        // Serious error
        return -1;
    }

    for (i=0; i<n; i++) {
        if (strlen(regs[i].name) == strlen(name) &&
                strncmp(regs[i].name, name, strlen (name))==0) {

            set_reg(&regs[i], value);
            return value;
        }
    }
}

すべての呼び出しで条件をチェックするため、これは明らかに質問に対する答えではありません。

于 2011-06-22T18:10:50.767 に答える
0

__builtin_expectヒントだけです。コンパイラがより良いコードを生成するのに役立ちます。たとえば、ジャンプ ラベルを再配置して、メインライン コードがメモリ内で継続的に配置されるようにします。これにより、コード キャッシュ ラインがより使いやすくなり、メイン メモリからのフェッチが容易になります。実行中のプロファイルに基づく最適化はさらに優れています。

コードにロックが見られないため、この関数は複数のスレッドから同時に呼び出されることは想定されていません。この場合fd、二重チェック ロックが適用されないように、関数のスコープの外に移動する必要があります。次に、コードを少し再配置します (これは、GCC が分岐ヒントで行うべきことですが...)。さらに、頻繁にアクセスする場合は、ファイル記述子をメイン メモリ/キャッシュ ラインからレジスタにコピーできます。コードは次のようになります。

static int g_fd = -1;

static unsigned long volatile *getReg(unsigned long addr)
{
    register int fd = g_fd;

    if (__builtin_expect ((fd > 0), 1))
    {
on_success:
        return NULL; // Do important stuff here.
    }

    fd = open("file", O_RDWR | O_SYNC);

    if (__builtin_expect ((fd > 0), 1))
    {
        g_fd = fd;
        goto on_success;
    }

    return NULL;
}

しかし、これを真剣に受け止めないでください。システム コールとファイル I/O は非常に悪いため、このような最適化は意味がありません (いくつかの例外を除きます)。

本当に 1 回だけ呼び出したい場合は、file open を別の関数に移動して、1 回だけ呼び出されるようにすることをお勧めします。はい、GCC のプロファイル フィードバックと LTO をご覧ください。これにより、このようなことにあまり時間を費やすことなく、良い結果を得ることができます。

于 2011-06-02T23:42:08.723 に答える