测来测去3:抽象层直接调用实例方法性能提高百分之20

首先吐槽一下hexo标题不能以%结尾 -_-||

抽象层

经常,我们会在相对比较成熟的软件中见到这样一类结构体:

1
2
3
4
5
6
typedef void (*func)(void);

struct abstraction_layer {
func f;

};

内部的成员变量,多以函数指针为主。

这种结构体主要作用是实现一个”抽象层”,用来解耦上层的业务需求和具体的实现。在操作系统、设备驱动等各种场合有很广泛的应用。

采用这种抽象形式,虽然增加了程序的灵活性和拓展性(其实就是OOP中多态的实现方式),但最大的问题就是当真正需要调用某一实例的方法时,只能通过抽象层的函数指针间接调用(indirect call),而这种调用是伤害性能的。

有没有在性能上更好的方式呢?

测试对象

我们构造这样一个抽象层和一组具体的实现:

抽象层struct op需要实现六个操作接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define FUNC_RETURN_TYPE void
#define FUNC_PARAMETER void

#define OPS \
_(open) \
_(close) \
_(write) \
_(read) \
_(add) \
_(delete)

#define _(op) typedef FUNC_RETURN_TYPE (*op)(FUNC_PARAMETER);
OPS // typdef void (*open)(void) and so on...
#undef _

#define _(op) op op##_fp;
struct op {
OPS // open open_fp; and so on...
};
#undef _

而具体实现这些结构体的实例我们用不同的“颜色”表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define LIST \
_(red) \
_(blue)\
_(yellow)\
_(black)\
_(white)

#define FUNC_BODY {int i=0; i++;}

#define _(color) FUNC_RETURN_TYPE open_##color(FUNC_PARAMETER) FUNC_BODY \
FUNC_RETURN_TYPE close_##color(FUNC_PARAMETER) FUNC_BODY \
FUNC_RETURN_TYPE write_##color(FUNC_PARAMETER) FUNC_BODY \
FUNC_RETURN_TYPE read_##color(FUNC_PARAMETER) FUNC_BODY \
FUNC_RETURN_TYPE add_##color(FUNC_PARAMETER) FUNC_BODY \
FUNC_RETURN_TYPE delete_##color(FUNC_PARAMETER) FUNC_BODY

LIST // void open_red(void) {int i=0; i++} and so on...

#undef _

然后我们分别把这几个“颜色”的具体实现与抽象层挂钩:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define OP_TYPE_NUM 5

enum types {
red,
blue,
yellow,
black,
white
};

struct op ops[OP_TYPE_NUM];

#define _(color) ops[color].open_fp = &open_##color; \
ops[color].close_fp = &close_##color; \
ops[color].write_fp = &write_##color; \
ops[color].read_fp = &read_##color; \
ops[color].add_fp = &add_##color; \
ops[color].delete_fp = &delete_##color;

LIST // ops[red].open_fp = &open_red; and so no...

#undef _

准备好了被测对象之后,下面就是对比测试了。

直接调用

对每种实例中的方法,有两种调用方式:

  • 间接调用:

以调用每个实例的open接口为例:

1
ops[idx].open_fp();

这种是最常见的调用方式。因为需要先获取指针,再根据指针去调用,所以称为间接调用。

  • 直接调用:

仍是以调用实例的open接口为例:

1
2
3
4
5
6
switch(idx) {
#define _(color) case color: open_##color(); break;
LIST // case red: open_red(); break; and so on...
#undef _
default: open_red(); break;
}

用一个switch结构先判断该欲调用的方法属于哪个实例,然后直接调用该方法。

看起来直接调用的方式不如间接调用“优雅”,但直接调用是否能带来性能提升呢?

性能对比

1
2
3
4
5
6
7
8
9
10
CPU: Intel(R) Xeon(R) CPU E5-2699 v3 @ 2.30GHz
OS:3.10.0-514.21.1.el7.x86_64
GCC:4.8.5

====RANDOM IDX -O0
call_ops() : 7936.000 cycles per input word (best) 8279.895 cycles per input word (avg)
call_ops_directly() : 6287.000 cycles per input word (best) 6858.248 cycles per input word (avg)
====FIX IDX -O0
call_ops() : 7862.000 cycles per input word (best) 8035.686 cycles per input word (avg)
call_ops_directly() : 6713.000 cycles per input word (best) 7234.337 cycles per input word (avg)

在两种不同的调用方式下(一种是随机选取实例调用,一种是固定调用一个实例),直接调用的方式都比间接调用快(消耗的cycle数少),在随机调用模式下有接近20%((8279-6858)/8279)的性能提升。

完整代码已传到Github:https://github.com/PanZhangg/x86perf/blob/master/indirectcall.c

至于为什么会出现这个结果,会在后续的系列文章中探究。

© 2020 DecodeZ All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero