类似 C 的回调处理:哪种算法执行速度更快?

发布于 2024-11-30 13:09:06 字数 1124 浏览 1 评论 0原文

我有一系列像这样的回调 void (*callbacks[n])(void* sender) 我想知道其中哪一个代码执行速度更快:

//Method A
void nullcallback(void* sender){};

void callbacka(void* sender) 
{
    printf("Hello ");
}

void callbackb(void* sender)
{
    printf("world\n");
}

int main()
{
    void (*callbacks[5])(void* sender);
    unsigned i;
    for (i=0;i<5;++i)
        callbacks[i] = nullcallback;
    callbacks[2] = callbacka;
    callbacks[4] = callbackb;
    for (i=0;i<5;++i)
        callbacks[i](NULL);
};

//Method B
void callbacka(void* sender) 
{
    printf("Hello ");
}

void callbackb(void* sender)
{
    printf("world\n");
}

int main()
{
    void (*callbacks[5])(void* sender);
    unsigned i;
    for (i=0;i<5;++i)
        callbacks[i] = NULL;
    callbacks[2] = callbacka;
    callbacks[4] = callbackb;
    for (i=0;i<5;++i)
        if (callbacks[i])
            callbacks[i](NULL);
};

某些条件:

  • 这很重要我是否知道我的大部分回调是否有效?
  • 如果我使用 C 或 C++ 编译器编译代码,会有什么不同吗?
  • 目标平台(Windows、Linux、Mac、iOS、Android)是否会改变结果? (这个回调数组的全部目的是为了管理游戏中的回调)

I have an array of call backs like this void (*callbacks[n])(void* sender) and I'm wondering which one of these codes will preform faster :

//Method A
void nullcallback(void* sender){};

void callbacka(void* sender) 
{
    printf("Hello ");
}

void callbackb(void* sender)
{
    printf("world\n");
}

int main()
{
    void (*callbacks[5])(void* sender);
    unsigned i;
    for (i=0;i<5;++i)
        callbacks[i] = nullcallback;
    callbacks[2] = callbacka;
    callbacks[4] = callbackb;
    for (i=0;i<5;++i)
        callbacks[i](NULL);
};

or

//Method B
void callbacka(void* sender) 
{
    printf("Hello ");
}

void callbackb(void* sender)
{
    printf("world\n");
}

int main()
{
    void (*callbacks[5])(void* sender);
    unsigned i;
    for (i=0;i<5;++i)
        callbacks[i] = NULL;
    callbacks[2] = callbacka;
    callbacks[4] = callbackb;
    for (i=0;i<5;++i)
        if (callbacks[i])
            callbacks[i](NULL);
};

some conditions:

  • Does it matter if I know most of my callbacks are valid or not?
  • Does it make a difference if I'm compiling my code using C or C++ compiler?
  • Does the target platform (windows, linux, mac, iOS, android) change any thing in the results? (the whole reason for this callback array is to manage callbacks in a game)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

彻夜缠绵 2024-12-07 13:09:06

您必须为此查看汇编代码。在我的平台(gcc,32 位)上,我发现编译器无法优化对 nullcallback 的调用。但是,如果我将您的方法 A 改进为以下内容,

int main(void) {
  static void (*const callbacks[5])(void* sender) = {
    [0] = nullcallback,
    [1] = nullcallback,
    [2] = callbacka,
    [3] = nullcallback,
    [4] = callbackb,
  };
  for (unsigned i=0;i<5;++i)
        callbacks[i](0);
};

编译器就能够展开循环并优化调用,结果就是

    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    movl    $0, (%esp)
    call    callbacka
    movl    $0, (%esp)
    call    callbackb
    xorl    %eax, %eax
    leave
    ret
    .size   main, .-main

You'd have to look into the assembler code for that. On my platform (gcc, 32bit) I found that the compiler is not able to optimize the call to nullcallback out. But if I improve your method A to the following

int main(void) {
  static void (*const callbacks[5])(void* sender) = {
    [0] = nullcallback,
    [1] = nullcallback,
    [2] = callbacka,
    [3] = nullcallback,
    [4] = callbackb,
  };
  for (unsigned i=0;i<5;++i)
        callbacks[i](0);
};

the compiler is able to unroll the loop and optimize the calls the result is just

    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    movl    $0, (%esp)
    call    callbacka
    movl    $0, (%esp)
    call    callbackb
    xorl    %eax, %eax
    leave
    ret
    .size   main, .-main
沦落红尘 2024-12-07 13:09:06

这完全取决于你的实际情况。如果可能的话,我更喜欢方法 A,因为它更容易阅读和生成更清晰的代码,特别是如果您的函数有返回值:

ret = callbacks[UPDATE_EVENT](sender);
// is nicer then
if (callbacks[UPDATE_EVENT])
    ret = callbacks[UPDATE_EVENT](sender);
else
    ret = 0;

当然,当您不仅有一个函数签名,而且有 100 个不同的签名时,方法 A 就会变得 tedouis 。对于每个你都必须编写一个空函数。

出于性能考虑,这取决于 nullcallback() 是否是罕见情况。如果很少见,方法A显然更快。如果不是,方法 B 可能会稍微快一些,但这取决于很多因素:您使用哪个平台,您的函数有多少参数等。但无论如何,如果您的回调正在做“真正的工作”,即。不仅仅是一些简单的计算,根本不重要。

当您不仅为一个发送者调用回调,而且为许多发送者调用回调时,您的方法 B 确实可以更快:

extern void *senders[SENDERS_COUNT]; // SENDERS_COUNT is a large number

if (callbacks[UPDATE_EVENT])
{
    for (int i = 0; i < SENDERS_COUNT; i++)
        callbacks[UPDATE_EVENT](senders[i]);
} 

这里,当没有有效的回调时,将跳过整个循环。如果 nullcallback() 地址已知,也可以使用方法 A 来完成此调整,即仅在某些模块中未定义。

This totally depends on your actual situation. If possible I would prefer methode A, because it is simply easier to read and produce cleaner code, in particular if your function has a return value:

ret = callbacks[UPDATE_EVENT](sender);
// is nicer then
if (callbacks[UPDATE_EVENT])
    ret = callbacks[UPDATE_EVENT](sender);
else
    ret = 0;

Of course methode A becomes tedouis when you have not only one function signature but let's say 100 different signature. And for each you have to write a null function.

For the performance consideration it depends if the nullcallback() is a rare case or not. If it is rare, methode A is obviously faster. If not methode B could be slightly faster, but that depends on many factors: which platform you use, how many arguments your functions have, etc. But in any case if your callbacks are doing "real work", ie. not only some simple calculations, it shouldn't matter at all.

Where your methode B could really be faster is when you not only call the callback for one sender but for very many:

extern void *senders[SENDERS_COUNT]; // SENDERS_COUNT is a large number

if (callbacks[UPDATE_EVENT])
{
    for (int i = 0; i < SENDERS_COUNT; i++)
        callbacks[UPDATE_EVENT](senders[i]);
} 

Here the entire loop is skipped when there is no valid callback. This tweak can also be done with methode A if the nullcallback() address is known, ie. not defined in some module only.

不念旧人 2024-12-07 13:09:06

您可以通过简单地对数组进行零初始化来进一步优化您的代码,如下所示:

void (*callbacks[5])(void* sender) = { 0 };

然后您就完全消除了 for 循环将每个指针设置为 NULL 的需要。您现在只需为 callbackacallbackb 进行分配。

You could optimize your code further by simply zero-initializing the array to start with like:

void (*callbacks[5])(void* sender) = { 0 };

Then you've completely eliminated the need for your for-loop to set each pointer to NULL. You now just have to make assignments for callbacka and callbackb.

吖咩 2024-12-07 13:09:06

对于一般情况,方法 B 是首选,但对于函数指针 LUT(当 NULL 为例外时)比方法 A 微观上更快。

主要示例是 Linux 系统调用表,NULL 调用只应在运行在较新系统上构建的二进制文件或程序员错误的极少数情况下发生。系统调用发生的频率足够高,以至于纳秒甚至皮秒的改进可以有所帮助。

其他可能证明它有价值的实例是仿真器(例如 MAME)内的操作码 LUT。

For the general case method B is preferred, but for function pointer LUTs when NULL is the exception than method A is microscopically faster.

The primary example is Linux system call table, NULL calls should only occur in rare circumstances when running binaries built on newer systems, or programmer error. Systems calls occur often enough that nanosecond or even picosecond improvements can help.

Other instances it may prove worthy is for opcode LUTs inside emulators such as MAME.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文