测试条件与函数调用和简单测试执行大致相同

时间:2010-07-10 01:43:29

标签: c

我正在使用两个版本的函数来过滤链表,一个接收谓词函数作为参数,另一个使用“宏模板”将谓词构建到函数中。我希望第一个运行得更慢,因为它在每次迭代时都会调用函数,但在我的盒子上它们的速度大致相同。有关为什么会发生这种情况的任何线索?任何帮助表示赞赏。

以下是代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

struct list_t {
    int val;
    struct list_t *next;
};
typedef struct list_t list_t;

// Removes elements not matching the predicate.
// NOTE: This is not freeing the removed elements.

list_t *keep(int (*predfun)(int,int), int predarg, list_t *list) {
    list_t *this, *prev;

    for (this=list, prev=NULL; this; prev=this, this=this->next) {
        if (!predfun(this->val, predarg)) {
            if (!prev) {
                list = this->next;
            }
            else {
                prev->next = this->next;
            }
        }
    }

    return list;
}

int less(int a, int b) {
    return a<b;
}

// A "template" macro for the keep function.
// The idea is to embed the predicate into the function, so that we
// don't have to make a function call on each iteration.

#define build_keep(test) {                                           \
    list_t *this, *prev;                                             \
                                                                     \
    for (this=list, prev=NULL; this; prev=this, this=this->next) {   \
        if (!(test)) {                                               \
            if (!prev) {                                             \
                list = this->next;                                   \
            }                                                        \
            else {                                                   \
                prev->next = this->next;                             \
            }                                                        \
        }                                                            \
    }                                                                \
    return list;                                                     \
}


list_t *keep_less(int arg, list_t *list) {
    build_keep(this->val < arg);
}

#define LEN 1000000
#define MOD 1024

// Creates a new list.
list_t *buildlist() {
    int i;
    list_t *list, *last, *t;
    list=NULL, last=NULL;
    srand(0); // Using always the same seed for the benchmark.
    for (i=0; i<LEN; i++) {
        if (!last) {
            last = malloc(sizeof(list_t));
            list = last;
        }
        else {
            last->next = malloc(sizeof(list_t));
            last = last->next;
        }
        last->val = rand() % MOD;
    }
    last->next = NULL;
    return list;
}    

int main() {
    struct timeval t0, t1;
    list_t *list, *t;

    // With macro.
    list = buildlist();
    //for (t=list; t; t=t->next) printf("%d ", t->val); printf("\n");
    gettimeofday(&t0, NULL);
    keep_less(500, list);
    gettimeofday(&t1, NULL);
    printf("keep_less: %lf\n", (1000000 * (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec))/1000000.0);
    //for (t=list; t; t=t->next) printf("%d ", t->val); printf("\n");

    printf("\n");

    // Without macro.
    list = buildlist();
    //for (t=list; t; t=t->next) printf("%d ", t->val); printf("\n");
    gettimeofday(&t0, NULL);
    keep(less, 500, list);
    gettimeofday(&t1, NULL);
    printf("keep: %lf\n", (1000000 * (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec))/1000000.0);
    //for (t=list; t; t=t->next) printf("%d ", t->val); printf("\n");

    return 0;
}

这里的输出是:

keep_less: 0.181019

keep: 0.185590

2 个答案:

答案 0 :(得分:0)

我不会说结果差不多 - 你确实看到4毫秒(~2%)的差异有利于立即版本。

这实际上相当实际 - 只需要保存一个函数调用就可以实现如此高的节省,因为你的测试函数起初做的很少。如果您对此优化有更多的期望,那么您可能偶然发现important lesson ...(就我个人而言,我必须每隔几周重新学习一次:])

答案 1 :(得分:0)

因为Ofek没有真正回答这个问题;-)这是为什么会发生这种情况?。显然,编译器在这里做得非常好。

  • 函数调用本身不是 就像许多人想的那样昂贵 如果通过函数指针。 在a中完成时尤其如此 循环,然后函数在其中 第一个之后的指令缓存 迭代。
  • 如果所有功能代码都存在 一个编译单位任何好处 现在的编译器将能够 inline小功能(如 less)进入来电者。
  • 极端,不变 传播,它甚至可能会逃脱 根本没有动态调用。后 所有less都是常量,此处,在keep被调用的地方。

如果没有进行过度优化以对同一单元中不存在的代码产生类似的影响,我倾向于非常系统地使用inline。这并不能保证函数将被内联,而只是允许在头文件中定义函数体。