Amethyst Studio
705 words
4 minutes
近距离观察C++20`[[likely]]`和`[[unlikely]]`属性
2023-04-22

C++20提供了两个属性:[[likely]][[unlikely]],用在为编译器提供分支的信息。在更有可能触发的分支上使用[[likely]]关键字可以提升一定的程序运行效率。

注:尽管这两个属性在C++20中被添加,但是如果使用std=c++11的选项,在最新版的C编译器中也是起作用的。

这一节来近距离观察编译的效果。

Sample Code#

int foo(int a, int b);

int bar(int a, int b, int c);

int main(int argc, char *argv[]) {
  if (argc < 5) {
    foo(1, 2);
  } else {
    bar(41, 96, 83);
  }
  return 0;
}

这里的main函数,我们从标准输入中获取参数个数,如果参数个数小于5,调用foo这个函数,否则调用bar这个函数。

然后,我们在两个分支上,分别添加[[liekly]][[unlikely]]属性。

First Case#

第一次尝试,我们把[[likely]]添加到一个分支上:

int main(int argc, char *argv[]) {
  if (argc < 5) [[likely]] {
    foo(1, 2);
  } else [[unlikely]]  {
    bar(41, 96, 83);
  }
  return 0;
}

接着使用clang++编译,注意这里使用-O1-O0编译器不会使用优化。打印得到的x86汇编代码,为了节省篇幅,这里把编译出来的冗余信息进行了删除。

main:
	pushq	%rax
	cmpl	$4, %edi
	jg	.LBB0_2
	movl	$1, %edi
	movl	$2, %esi
	callq	_Z26fooii@PLT
	popq	%rcx
	retq
.LBB0_2:
	movl	$41, %edi
	movl	$96, %esi
	movl	$83, %edx
	callq	_Z29bariii@PLT
	popq	%rcx
	retq

Second Case#

第二次尝试,我们把[[likely]]加到第二个分支上:

int main(int argc, char *argv[]) {
  if (argc < 5) [[unlikely]] {
    foo(1, 2);
  } else [[likely]] {
    bar(41, 96, 83);
  }
  return 0;
}

然后再进行编译:

main:
	pushq	%rax
	cmpl	$4, %edi
	jle	.LBB0_1
	movl	$41, %edi
	movl	$96, %esi
	movl	$83, %edx
	callq	_Z29bariii@PLT
	popq	%rcx
	retq
.LBB0_1:
	movl	$1, %edi
	movl	$2, %esi
	callq	_Z26fooii@PLT
	popq	%rcx
	retq

Compare two assembly#

比较一下这两块代码:

			
main:
    pushq	%rax
    cmpl	$4, %edi
    jg	.LBB0_2
    movl	$1, %edi
    movl	$2, %esi
    callq	_Z26fooii@PLT
    popq	%rcx
    retq
.LBB0_2:
    movl	$41, %edi
    movl	$96, %esi
    movl	$83, %edx
    callq	_Z29bariii@PLT
    popq	%rcx
    retq
			
		
			
main:
    pushq	%rax
    cmpl	$4, %edi
    jle	.LBB0_1
    movl	$41, %edi
    movl	$96, %esi
    movl	$83, %edx
    callq	_Z29barii@PLT
    popq	%rcx
    retq
.LBB0_1:
    movl	$1, %edi
    movl	$2, %esi
    callq	_Z26fooii@PLT
    popq	%rcx
    retq
			
		

从这里可以看到,第一种情况,如果为argc < 5的情形标注[[likely]],那么函数的跳转会把调用bar的代码安排在“稍微远一点”的地方。如果反过来为argc < 5的情形标注[[unlikely]],那么编译器会把调用foo的代码往后安排。把更可能的分支安排在接近cmp指令的地方,从而避免频繁地跳转对性能的干扰。

近距离观察C++20`[[likely]]`和`[[unlikely]]`属性
https://ziyue.cafe/posts/likely-and-unlikely-in-cpp/
Author
Kaida Amethyst
Published at
2023-04-22