为什么随机额外代码可以提高性能?

时间:2015-07-16 08:50:13

标签: c++ performance pointers trie

Struct Node {
    Node *N[SIZE];
    int value;
};

struct Trie {
    Node *root;

    Node* findNode(Key *key) {
        Node *C = &root;
        char u;
        while (1) {
            u = key->next();
            if (u < 0) return C;
         // if (C->N[0] == C->N[0]); // this line will speed up execution significantly
            C = C->N[u];
            if (C == 0) return 0;
        }
    }
    void addNode(Key *key, int value){...};
};

在Prefix Tree(又名Trie)的这个实现中,我发现90%的findNode()执行时间是由单个操作C=C->N[u];

执行的。

在我尝试加速此代码时,我随机添加了上面剪切的注释行,代码变得快了30%!那是为什么?

更新

这是完整的程序。

#include "stdio.h"
#include "sys/time.h"

long time1000() {
  timeval val;
  gettimeofday(&val, 0);
  val.tv_sec &= 0xffff;
  return val.tv_sec * 1000 + val.tv_usec / 1000;
}

struct BitScanner {
    void *p; 
    int count, pos;
    BitScanner (void *p, int count) {
        this->p = p;
        this->count = count;
        pos = 0;
    }
    int next() {
        int bpos = pos >> 1;
        if (bpos >= count) return -1;
        unsigned char b = ((unsigned char*)p)[bpos];
        if (pos++ & 1) return (b >>= 4);
        return b & 0xf;
    }

};

struct Node {
    Node *N[16];
    __int64_t value;
    Node() : N(), value(-1) { }
};

struct Trie16 {
    Node root;

    bool add(void *key, int count, __int64_t value) {
        Node *C = &root;
        BitScanner B(key, count);
        while (true) {
            int u = B.next();
            if (u < 0) {
                if (C->value == -1) {
                    C->value = value;
                    return true; // value added
                }
                C->value = value;
                return false; // value replaced
            }
            Node *Q = C->N[u];
            if (Q) {
                C = Q;
            } else {
                C = C->N[u] = new Node;
            }
        }
    }

    Node* findNode(void *key, int count) {
        Node *C = &root;
        BitScanner B(key, count);
        while (true) {
            char u = B.next();
            if (u < 0) return C;
//          if (C->N[0] == C->N[1]);
            C = C->N[0+u];
            if (C == 0) return 0;
        }
    }
};

int main() {
    int T = time1000();
    Trie16 trie;
    __int64_t STEPS = 100000, STEP = 500000000, key;
    key = 0;
    for (int i = 0; i < STEPS; i++) {
        key += STEP;
        bool ok = trie.add(&key, 8, key+222);
    }
    printf("insert time:%i\n",time1000() - T); T = time1000();
    int err = 0;
    key = 0;
    for (int i = 0; i < STEPS; i++) {
        key += STEP;
        Node *N = trie.findNode(&key, 8);
        if (N==0 || N->value != key+222) err++;
    }
    printf("find time:%i\n",time1000() - T); T = time1000();
    printf("errors:%i\n", err);
}

3 个答案:

答案 0 :(得分:6)

这在很大程度上是一种猜测,但从我读到的有关CPU数据预取器的内容来看,只有在看到对同一内存位置的多次访问并且该访问与预取触发器匹配时才会预取,例如看起来像扫描。在您的情况下,如果只有C->N的单一访问权限,则预取器不会感兴趣,但是如果有多个并且它可以预测后来的访问进一步进入相同的内存位,可以使其预取更多内容比一个缓存行。

如果发生了上述情况,那么C->N[u]就不必等待内存从RAM到达,因此会更快。

答案 1 :(得分:1)

看起来你正在做的是通过延迟执行代码直到数据在本地可用来防止处理器停顿。

以这种方式这样做非常容易出错,不可能继续一致地工作。更好的方法是让编译器执行此操作。默认情况下,大多数编译器会为通用处理器系列生成代码。 但是如果查看可用的标记,通常可以找到用于指定特定处理器的标志,以便生成更具体的代码(如预取和停止代码)。

请参阅:GCC: how is march different from mtune?第二个答案详细介绍:https://stackoverflow.com/a/23267520/14065

答案 2 :(得分:-2)

由于每次写入操作都比读取操作昂贵。 在这里,如果你看到,  C = C-> N [u];它意味着CPU在变量C的每次迭代中执行写入。 但是当你执行if(C-> N [0] == C-> N [1])dummy ++时;只有当C-> N [0] == C-> N [1]时才执行写入伪。因此,您可以使用if条件保存许多CPU的写入指令。