const 并不能加快 C 代码的运行速度?

const 对于 C、C++ 而言,到底意味着什么?ymo每天发布大量与生活相关的资讯平台

ymo每天发布大量与生活相关的资讯平台

作者 | Simon Arneaudymo每天发布大量与生活相关的资讯平台

译者 | 弯月,责编 | 屠敏ymo每天发布大量与生活相关的资讯平台

以下为译文:ymo每天发布大量与生活相关的资讯平台

几个月前,我曾在一篇文章中说“const有助于优化C和C++的编译器”只是一个传说。我觉得我应该解释一下,特别是因为以前我自己也一度认为这是不争的事实。在本文中,我将从一些理论和例子着手,然后在一个真正的代码库Sqlite上展开实验和基准测试。ymo每天发布大量与生活相关的资讯平台

简单的测试ymo每天发布大量与生活相关的资讯平台

让我们思考一个最简单的例子,曾经我以为这个例子中的const能够加快C代码运行速度。首先,假设我们有如下两个函数声明:ymo每天发布大量与生活相关的资讯平台

voidfunc(int*x); ymo每天发布大量与生活相关的资讯平台

voidconstFunc(constint*x); ymo每天发布大量与生活相关的资讯平台

然后,假设我们有如下两种写法的代码:ymo每天发布大量与生活相关的资讯平台

voidbyArg(int*x)ymo每天发布大量与生活相关的资讯平台

{ ymo每天发布大量与生活相关的资讯平台

printf( "%dn", *x); ymo每天发布大量与生活相关的资讯平台

func(x); ymo每天发布大量与生活相关的资讯平台

printf( "%dn", *x); ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

voidconstByArg(constint*x)ymo每天发布大量与生活相关的资讯平台

{ ymo每天发布大量与生活相关的资讯平台

printf( "%dn", *x); ymo每天发布大量与生活相关的资讯平台

constFunc(x); ymo每天发布大量与生活相关的资讯平台

printf( "%dn", *x); ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

在执行printf的时候,CPU必须通过指针从内存中获取*x的值。显然,constByArg的速度稍微快一点,因为编译器知道*x是常量,所以它不需要在执行完constFunc后再次加载*x的值。它只需要输出相同的值。对吧?让我们来看看GCC经过优化后生成的汇编代码:ymo每天发布大量与生活相关的资讯平台

$ gcc -S -Wall -O3 test.c ymo每天发布大量与生活相关的资讯平台

$ view test.s ymo每天发布大量与生活相关的资讯平台

如下是byArg完整的汇编代码:ymo每天发布大量与生活相关的资讯平台

byArg: ymo每天发布大量与生活相关的资讯平台

.LFB23: ymo每天发布大量与生活相关的资讯平台

.cfi_startproc ymo每天发布大量与生活相关的资讯平台

pushq %rbx ymo每天发布大量与生活相关的资讯平台

.cfi_def_cfa_offset 16ymo每天发布大量与生活相关的资讯平台

.cfi_offset 3, - 16ymo每天发布大量与生活相关的资讯平台

movl (%rdi), %edx ymo每天发布大量与生活相关的资讯平台

movq %rdi, %rbx ymo每天发布大量与生活相关的资讯平台

leaq .LC 0(%rip), %rsi ymo每天发布大量与生活相关的资讯平台

movl $1, %edi ymo每天发布大量与生活相关的资讯平台

xorl %eax, %eax ymo每天发布大量与生活相关的资讯平台

call __printf_chk@PLT ymo每天发布大量与生活相关的资讯平台

movq %rbx, %rdi ymo每天发布大量与生活相关的资讯平台

call func@PLT # The only instruction that's different in constFooymo每天发布大量与生活相关的资讯平台

movl (%rbx), %edx ymo每天发布大量与生活相关的资讯平台

leaq .LC 0(%rip), %rsi ymo每天发布大量与生活相关的资讯平台

xorl %eax, %eax ymo每天发布大量与生活相关的资讯平台

movl $1, %edi ymo每天发布大量与生活相关的资讯平台

popq %rbx ymo每天发布大量与生活相关的资讯平台

.cfi_def_cfa_offset 8ymo每天发布大量与生活相关的资讯平台

jmp __printf_chk@PLT ymo每天发布大量与生活相关的资讯平台

.cfi_endproc ymo每天发布大量与生活相关的资讯平台

比较byArg和constByArg ,GCC生成的汇编代码中唯一的区别在于,constByArg中的调用是call constFunc@PLT,跟源代码写的一样。const本身实际上没有带来任何差异。ymo每天发布大量与生活相关的资讯平台

那么好,这是GCC的结果。也许我们需要一个更加智能的编译器,那么我们来看看Clang是不是会更好一点。ymo每天发布大量与生活相关的资讯平台

$ clang -S -Wall -O3 -emit-llvm test.c ymo每天发布大量与生活相关的资讯平台

$ view test.ll ymo每天发布大量与生活相关的资讯平台

生成的IR(中间代码表示形式)如下所示,比汇编代码更为紧凑,所以我把两个函数的代码都罗列出来了,你可以看到我说的一点都没错:“除了那个调用之外,二者毫无区别”。ymo每天发布大量与生活相关的资讯平台

; Function Attrs: nounwind uwtable ymo每天发布大量与生活相关的资讯平台

define dso_local void @byArg(i32*) local_unnamed_addr #0 {ymo每天发布大量与生活相关的资讯平台

%2 = load i32, i32* %0, align 4, !tbaa ! 2ymo每天发布大量与生活相关的资讯平台

%3 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([ 4xi8], [ 4xi8]* @.str, i64 0, i64 0), i32 %2) ymo每天发布大量与生活相关的资讯平台

tail call void @func(i32* %0) #4ymo每天发布大量与生活相关的资讯平台

%4 = load i32, i32* %0, align 4, !tbaa ! 2ymo每天发布大量与生活相关的资讯平台

%5 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([ 4xi8], [ 4xi8]* @.str, i64 0, i64 0), i32 %4) ymo每天发布大量与生活相关的资讯平台

ret void ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

; Function Attrs: nounwind uwtable ymo每天发布大量与生活相关的资讯平台

define dso_local void @constByArg(i32*) local_unnamed_addr #0 {ymo每天发布大量与生活相关的资讯平台

%2 = load i32, i32* %0, align 4, !tbaa ! 2ymo每天发布大量与生活相关的资讯平台

%3 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([ 4xi8], [ 4xi8]* @.str, i64 0, i64 0), i32 %2) ymo每天发布大量与生活相关的资讯平台

tail call void @constFunc(i32* %0) #4ymo每天发布大量与生活相关的资讯平台

%4 = load i32, i32* %0, align 4, !tbaa ! 2ymo每天发布大量与生活相关的资讯平台

%5 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([ 4xi8], [ 4xi8]* @.str, i64 0, i64 0), i32 %4) ymo每天发布大量与生活相关的资讯平台

ret void ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

也许能够提高效率的代码ymo每天发布大量与生活相关的资讯平台

如下代码中的const确实能够提高效率:ymo每天发布大量与生活相关的资讯平台

voidlocalVarymo每天发布大量与生活相关的资讯平台

{ ymo每天发布大量与生活相关的资讯平台

intx = 42; ymo每天发布大量与生活相关的资讯平台

printf( "%dn", x); ymo每天发布大量与生活相关的资讯平台

constFunc(&x); ymo每天发布大量与生活相关的资讯平台

printf( "%dn", x); ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

voidconstLocalVarymo每天发布大量与生活相关的资讯平台

{ ymo每天发布大量与生活相关的资讯平台

constintx = 42; // const on the local variableymo每天发布大量与生活相关的资讯平台

printf( "%dn", x); ymo每天发布大量与生活相关的资讯平台

constFunc(&x); ymo每天发布大量与生活相关的资讯平台

printf( "%dn", x); ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

localVar的汇编代码如下所示,经过优化后constLocalVar确实少了两个指令:ymo每天发布大量与生活相关的资讯平台

localVar: ymo每天发布大量与生活相关的资讯平台

.LFB25: ymo每天发布大量与生活相关的资讯平台

.cfi_startproc ymo每天发布大量与生活相关的资讯平台

subq $24, %rsp ymo每天发布大量与生活相关的资讯平台

.cfi_def_cfa_offset 32ymo每天发布大量与生活相关的资讯平台

movl $42, %edx ymo每天发布大量与生活相关的资讯平台

movl $1, %edi ymo每天发布大量与生活相关的资讯平台

movq %fs: 40, %rax ymo每天发布大量与生活相关的资讯平台

movq %rax, 8(%rsp) ymo每天发布大量与生活相关的资讯平台

xorl %eax, %eax ymo每天发布大量与生活相关的资讯平台

leaq .LC 0(%rip), %rsi ymo每天发布大量与生活相关的资讯平台

movl $42, 4(%rsp) ymo每天发布大量与生活相关的资讯平台

call __printf_chk@PLT ymo每天发布大量与生活相关的资讯平台

leaq 4(%rsp), %rdi ymo每天发布大量与生活相关的资讯平台

call constFunc@PLT ymo每天发布大量与生活相关的资讯平台

movl 4(%rsp), %edx # not in constLocalVarymo每天发布大量与生活相关的资讯平台

xorl %eax, %eax ymo每天发布大量与生活相关的资讯平台

movl $1, %edi ymo每天发布大量与生活相关的资讯平台

leaq .LC 0(%rip), %rsi # not in constLocalVarymo每天发布大量与生活相关的资讯平台

call __printf_chk@PLT ymo每天发布大量与生活相关的资讯平台

movq 8(%rsp), %rax ymo每天发布大量与生活相关的资讯平台

xorq %fs: 40, %rax ymo每天发布大量与生活相关的资讯平台

jne .L9 ymo每天发布大量与生活相关的资讯平台

addq $24, %rsp ymo每天发布大量与生活相关的资讯平台

.cfi_remember_state ymo每天发布大量与生活相关的资讯平台

.cfi_def_cfa_offset 8ymo每天发布大量与生活相关的资讯平台

ret ymo每天发布大量与生活相关的资讯平台

.L9: ymo每天发布大量与生活相关的资讯平台

.cfi_restore_state ymo每天发布大量与生活相关的资讯平台

call __stack_chk_fail@PLT ymo每天发布大量与生活相关的资讯平台

.cfi_endproc ymo每天发布大量与生活相关的资讯平台

LLVM IR看起来更清晰。经过优化后,constLocalVar中第二个printf之前的load已经不见了:ymo每天发布大量与生活相关的资讯平台

; Function Attrs: nounwind uwtable ymo每天发布大量与生活相关的资讯平台

define dso_local void @localVar local_unnamed_addr #0 {ymo每天发布大量与生活相关的资讯平台

%1 = alloca i32, align 4ymo每天发布大量与生活相关的资讯平台

%2 = bitcast i32* %1 to i8* ymo每天发布大量与生活相关的资讯平台

call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %2) #4ymo每天发布大量与生活相关的资讯平台

store i32 42, i32* %1, align 4, !tbaa ! 2ymo每天发布大量与生活相关的资讯平台

%3 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([ 4xi8], [ 4xi8]* @.str, i64 0, i64 0), i32 42) ymo每天发布大量与生活相关的资讯平台

call void @constFunc(i32* nonnull %1) #4ymo每天发布大量与生活相关的资讯平台

%4 = load i32, i32* %1, align 4, !tbaa ! 2ymo每天发布大量与生活相关的资讯平台

%5 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([ 4xi8], [ 4xi8]* @.str, i64 0, i64 0), i32 %4) ymo每天发布大量与生活相关的资讯平台

call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %2) #4ymo每天发布大量与生活相关的资讯平台

ret void ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

所以说,constLocalVar成功地省略了重新加载*x的步骤,但是你也产生了一些疑惑:localVar和constLocalVar中调用的都是constFunc。如果编译器可以推断出constLocalVar中的constFunc没有修改*x,那么为什么不能推断出localVar中的同一个函数也不会修改*x呢?ymo每天发布大量与生活相关的资讯平台

为了搞清楚这个问题,我们需要先思考另一个问题的实质:为什么我们不可以把C中的const当作优化工具?实际上,C中的const包含了两重含义:它可以表示打着常量旗号的变量,实际上这个变量可能会改变,也可能不变;也可以表示这个变量是一个真正的常量。如果你利用指针强制转换来去掉const,然后重新给这个指针赋值,则会导致“未定义的行为”。另一方面,指向非常量值的const指针也是可以的。ymo每天发布大量与生活相关的资讯平台

如下constFunc的实现说明了这种情况:ymo每天发布大量与生活相关的资讯平台

// x is just a read-only pointer to something that may or may not be a constantymo每天发布大量与生活相关的资讯平台

voidconstFunc(constint*x)ymo每天发布大量与生活相关的资讯平台

{ ymo每天发布大量与生活相关的资讯平台

// local_var is a true constantymo每天发布大量与生活相关的资讯平台

constintlocal_var = 42; ymo每天发布大量与生活相关的资讯平台

// Definitely undefined behaviour by C rulesymo每天发布大量与生活相关的资讯平台

doubleIt(( int*)&local_var); ymo每天发布大量与生活相关的资讯平台

// Who knows if this is UB?ymo每天发布大量与生活相关的资讯平台

doubleIt(( int*)x); ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

voiddoubleIt(int*x)ymo每天发布大量与生活相关的资讯平台

{ ymo每天发布大量与生活相关的资讯平台

*x *= 2; ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

localVar给了constFunc 一个指向非const变量的const指针。因为变量原本不是const,所以constFunc可能会撒谎,在不触发“未定义的行为”的情况下强制修改它。因此编译器不能假设在constFunc返回后,这个变量的值仍然不变。而constLocalVar中的变量确实是const,所以编译器可以假设它的值不会改变——因为在这种情况下,如果constFunc 去掉const,并重新赋值,那么必然会触发“未定义的行为”。ymo每天发布大量与生活相关的资讯平台

第一个例子中的byArg和constByArg函数不会被优化,因为编译器不知道*x是否真的是const。ymo每天发布大量与生活相关的资讯平台

补充:很多读者指出,对于const int *x来说,这个指针本身算不上const,它只是打着常量旗号的数据,只有写成const int * const extra_const,才能表明指针和数据都是const。但是因为指针本身的常量性质与引用数据的常量性质无关,所以结果是相同的。如果extra_const = 0指向某个定义为const对象的话,那么*(int*const)extra_const = 0依然会触发“未定义的行为”。(实际上*(int*const)extra_const = 0是最糟糕的情况)。说到底,我们只是为了区分真正的const指针,和另一种指针——这个指针本身可能是常量也可能不是常量,而它指向的对象可能是常量也可能不是常量,好拗口(呼~),所以我还是泛泛地称之为“const指针”吧。ymo每天发布大量与生活相关的资讯平台

那么为什么会产生这种不一致呢?如果编译器假设在constLocalVar的调用中,constFunc不会修改它的参数,那么就可以同样优化constFunc调用了,对不对?然而,并不是。因为编译器甚至不能假设constLocalVar有没有运行。如果没有运行(例如,它只是代码生成器或宏生成的一些不会被用到的代码),那么constFunc就有可能会偷偷修改数据,而不会触发“未定义的行为”。ymo每天发布大量与生活相关的资讯平台

你可能需要反复阅读上述说明和示例,即便你感觉这很荒谬也没关系,因为这确实很荒谬。然而,不幸的是,给const变量赋值触发的“未定义的行为”最为糟糕:因为在大多数情况下,编译器甚至不知道这究竟是不是“未定义的行为”。因此,在大多数情况下,当编译器看到const时,它不得不假设某个人会在某个地方去掉这个const,因此编译器无法使用const优化代码。实际上这种情况的确会发生,因为现实世界中许多C代码都本着“我知道自己在干什么”的态度用强制转换去掉const。ymo每天发布大量与生活相关的资讯平台

简而言之,很多时候编译器都无法优化const,包括通过指针从另一个作用域接收数据,或者在堆中分配数据。更有甚至,在大多数情况下,即便你不指定const,编译器也会使用常量。例如,优秀的编译器都知道如下代码中的x是常量,即便x的定义中没有指定const:ymo每天发布大量与生活相关的资讯平台

intx= 42, y= 0; ymo每天发布大量与生活相关的资讯平台

printf( "%d %dn", x, y); ymo每天发布大量与生活相关的资讯平台

y+= x; ymo每天发布大量与生活相关的资讯平台

printf( "%d %dn", x, y); ymo每天发布大量与生活相关的资讯平台

总之,const达不到优化目的的原因主要包括:ymo每天发布大量与生活相关的资讯平台

  1. 除了个别特殊情况外,编译器不得不忽略const,因为其他代码可能会合法地抛弃const。
  2. 在第1条之外的情况下,大多数时候编译器能够自动分辨出某个变量是不是常量。

C++ymo每天发布大量与生活相关的资讯平台

如果你使用C++,那么const可能会通过其他途径影响代码的生成:函数重载。针对同一个函数,你可以使用const和非const两种形式的重载,并且还可以通过优化非const(由程序员而非编译器完成)减少复制或其他工作。ymo每天发布大量与生活相关的资讯平台

voidfoo(int*p)ymo每天发布大量与生活相关的资讯平台

{ ymo每天发布大量与生活相关的资讯平台

// Needs to do more copying of dataymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

voidfoo(constint*p)ymo每天发布大量与生活相关的资讯平台

{ ymo每天发布大量与生活相关的资讯平台

// Doesn't need defensive copiesymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

intmainymo每天发布大量与生活相关的资讯平台

{ ymo每天发布大量与生活相关的资讯平台

constintx = 42; ymo每天发布大量与生活相关的资讯平台

// const-ness affects which overload gets calledymo每天发布大量与生活相关的资讯平台

foo(&x); ymo每天发布大量与生活相关的资讯平台

return0; ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

一方面,我认为实际的C++中不会大量使用上述代码。另一方面,为了观察到真正的差异,程序员必须做出编译器无法做出的假设,因为C++语言没有这方面的保证。ymo每天发布大量与生活相关的资讯平台

Sqlite3的实验ymo每天发布大量与生活相关的资讯平台

上述我们介绍了大量理论和虚构的例子。下面我们来看看const对实际代码库的影响有多大。我将针对Sqlite数据库(版本3.30.0)展开测试,因为ymo每天发布大量与生活相关的资讯平台

  • 它真的用到了const
  • 它的代码库具备一定的规模(超过200K行代码)
  • 作为一个数据库,它包含从字符串处理到算术到日期处理等一系列功能
  • 它可以利用CPU密集的负载展开测试

此外,Sqlite的作者和贡献者们经过多年努力对其性能进行了优化,所以我可以肯定他们没有遗漏明显需要优化的地方。ymo每天发布大量与生活相关的资讯平台

设置ymo每天发布大量与生活相关的资讯平台

我复制了两份源代码,并正常编译了一份。而对于另一份,我使用如下预处理代码片段将const转化成了no-op:ymo每天发布大量与生活相关的资讯平台

#defineconstymo每天发布大量与生活相关的资讯平台

GNU的sed可以将其添加到每个文件的顶部,命令如下:ymo每天发布大量与生活相关的资讯平台

sed -i '1i#define const' *.c *.hymo每天发布大量与生活相关的资讯平台

Sqlite会在构建时使用脚本生成代码,因此情况略显复杂。不过,编译器在遇到const和非const的混合代码时会产生很多噪音,因此很容易检测到两者的混合,而且我可以调整脚本将上述替换const的代码包含进去。ymo每天发布大量与生活相关的资讯平台

直接观察编译结果的意义不大,因为每个细小的改动都会影响整个内存的布局,从而改变整个代码中的指针和函数调用。因此,我利用每条指令的二进制字节数和助记符,从反编译(objdump -d libsqlite3.so.0.8.6)后的代码中提取了特征,例如,下述函数:ymo每天发布大量与生活相关的资讯平台

000000000005d570 <sqlite3_blob_read>: ymo每天发布大量与生活相关的资讯平台

5d570: 4c 8d0559a2 ff ff lea -0x5da7(%rip),%r8 # 577d0 <sqlite3BtreePayloadChecked> ymo每天发布大量与生活相关的资讯平台

5d577: e9 04fe ff ff jmpq 5d380 <blobReadWrite> ymo每天发布大量与生活相关的资讯平台

5d57c: 0f1f4000nopl 0x0(%rax) ymo每天发布大量与生活相关的资讯平台

经过反编译后会变成:ymo每天发布大量与生活相关的资讯平台

sqlite3_blob_read7lea 5jmpq 4nopl ymo每天发布大量与生活相关的资讯平台

在编译的过程中,我没有改动Sqlite的构建设置。ymo每天发布大量与生活相关的资讯平台

分析编译后的代码ymo每天发布大量与生活相关的资讯平台

Ibsqlite3的const版总共包含4,740,704个字节,比非const版(4,736,712个字节)大0.1%左右。两者都输出了1374个函数(不包括PLT等低级的辅助代码),而我总共发现了13个不同之处。ymo每天发布大量与生活相关的资讯平台

有些不同是由于上述我的预处理造成的。例如,如下函数就是其中之一(省略了有些特定于Sqlite的定义):ymo每天发布大量与生活相关的资讯平台

#defineLARGEST_INT64 (0xffffffff|(((int64_t)0x7fffffff)<<32))ymo每天发布大量与生活相关的资讯平台

#defineSMALLEST_INT64 (((int64_t)-1) - LARGEST_INT64)ymo每天发布大量与生活相关的资讯平台

staticint64_t doubleToInt64(doubler){ ymo每天发布大量与生活相关的资讯平台

/*ymo每天发布大量与生活相关的资讯平台

** Many compilers we encounter do not define constants for theymo每天发布大量与生活相关的资讯平台

** minimum and maximum 64-bit integers, or they define themymo每天发布大量与生活相关的资讯平台

** inconsistently. And many do not understand the "LL" notation.ymo每天发布大量与生活相关的资讯平台

** So we define our own static constants here using nothingymo每天发布大量与生活相关的资讯平台

** larger than a 32-bit integer constant.ymo每天发布大量与生活相关的资讯平台

*/ymo每天发布大量与生活相关的资讯平台

staticconstint64_tmaxInt = LARGEST_INT64; ymo每天发布大量与生活相关的资讯平台

staticconstint64_tminInt = SMALLEST_INT64; ymo每天发布大量与生活相关的资讯平台

if( r<=( double)minInt ){ ymo每天发布大量与生活相关的资讯平台

returnminInt; ymo每天发布大量与生活相关的资讯平台

} elseif( r>=( double)maxInt ){ ymo每天发布大量与生活相关的资讯平台

returnmaxInt; ymo每天发布大量与生活相关的资讯平台

} else{ ymo每天发布大量与生活相关的资讯平台

return( int64_t)r; ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

} ymo每天发布大量与生活相关的资讯平台

如果删掉const,那么这些常量会变为static变量。我不太明白为什么不在乎const的人会把这些变量变成static。删掉static和const后,GCC就会把它们当成常量,而且最后我们得到的输出也相同。由于受这类的static const变量的影响,13个函数有3个这类的“假”变动,但我懒得逐一修改了。ymo每天发布大量与生活相关的资讯平台

Sqlite使用了很多全局变量,而这正是大多数const能够实现优化的地方。通常情况下,这些优化包括将比较式中的变量替换为常量,或者逐步展开循环。(我们可以利用Radare工具包方便地找出这些优化处理。)有几个变动令我印象深刻。sqlite3ParseUri 包含487条指令,但const带来的差异只有如下这一处比较:ymo每天发布大量与生活相关的资讯平台

test %al, %al ymo每天发布大量与生活相关的资讯平台

je <sqlite3ParseUri+ 0x717> ymo每天发布大量与生活相关的资讯平台

cmp $0x23, %al ymo每天发布大量与生活相关的资讯平台

je <sqlite3ParseUri+ 0x717> ymo每天发布大量与生活相关的资讯平台

交换了比较二者的顺序:ymo每天发布大量与生活相关的资讯平台

cmp $0x23, %al ymo每天发布大量与生活相关的资讯平台

je <sqlite3ParseUri+ 0x717> ymo每天发布大量与生活相关的资讯平台

test %al, %al ymo每天发布大量与生活相关的资讯平台

je <sqlite3ParseUri+ 0x717> ymo每天发布大量与生活相关的资讯平台

基准测试ymo每天发布大量与生活相关的资讯平台

Sqlite自带一个性能回归测试,因此我针对两个版本的代码各运行了一百次,仍然沿用了默认的Sqlite构建设置。运行结果如下(以秒为单位):ymo每天发布大量与生活相关的资讯平台

ymo每天发布大量与生活相关的资讯平台

从个人的角度来看,我不觉得二者之间有显著的差异。我的意思是说,如果说将程序中的const删掉就能产生差异,那么至少这个差异需要很显眼。但也许你关心任何微小的差异,也许你的工作中性能至关重要。下面让我们来展开一些统计分析。ymo每天发布大量与生活相关的资讯平台

我喜欢利用Mann-Whitney U来做这样的测试。这类似于著名的检测组间差异的t测试,但它更善于处理测量计算机运行时间时所面临的不可预测的复杂性(由于不可预测的上下文切换,页面错误等)。结果如下:ymo每天发布大量与生活相关的资讯平台

ymo每天发布大量与生活相关的资讯平台

上述U测试已经检测到统计上显著的性能差异。但是令人惊讶的是,实际上非const版本的速度更快——快了60毫秒,0.5%。似乎const带来的小幅“优化”根本不值得我们编写额外的代码。看起来const根本没有带来任何重大的优化,比如自动矢量化等。当然,如果你采用不同的编译选项、编译器版本或代码库等等,那么可能会看到不同的结果,但我不得不说如果const确实能够提高C的效率的话,我们应该能够通过上述实验和分析观察到。ymo每天发布大量与生活相关的资讯平台

const有什么用?ymo每天发布大量与生活相关的资讯平台

尽管const有各种缺陷,但C/C++仍然需要它来保障类型安全。特别是,如果你结合C++的move语义和std::unique_pointer,那么const可以明确指针的所有权。当代码库超过~100K行代码时,指针所有权的含混不清会给程序员带来巨大痛苦,所以我个人还是对const充满了感激之情。ymo每天发布大量与生活相关的资讯平台

然而,以前我在保障类型安全之外的很多地方也用到了const。我曾听人说,出于性能考虑,我们应该尽可能使用const。因此,每当性能至关重要时,我都会通过代码重构添加更多的const,即使这种做法降低了代码的可阅读性。当时我认为这种做法很有道理,但现在我明白这并不属实。 ymo每天发布大量与生活相关的资讯平台

原文:https://theartofmachinery.com/2019/08/12/c_const_isnt_for_performance.htmlymo每天发布大量与生活相关的资讯平台

也许你还喜欢

ups不间断电源原理分析

当前在电源市场上,所销售的电源类型较多,不同类型的电源,相应的功能与作用有所不同。在超

空间背景音乐怎么添加图文教程分享

怎样免费添加QQ空间背景音乐其实步骤也不多,很简单,主要有以下几个步骤,详情如下

万能转换器破解版怎么用?具体操作方

万能格式转换器是一款功能强大的格式转换软件,音频、视频、图片、光驱设备都可以通过万

rmvb转mp4格式转换器图文教程

在我们日常娱乐和日常工作中,现在网络上一些高清的视频一般都是rmvb格式的,而一些播放设

支付宝网络系统异常怎么解决

支付宝支付出现网络系统异常是因为IE浏览器设置Internet选项不恰当而导致的,去重新设置

win10蓝牙驱动破解版安装图文教程

win10蓝牙驱动怎么安装?请看下面具体操作方法。

微孔抛光镜面加工计数详解

一般来说,对于小孔微孔进行抛光,传统的加工方法可能会导致孔洞磨损,或者加工效果不理想。

短视频内容管理助手有哪些?怎么好用

随着短视频的普及,越来越多的人开始将大量的时间和精力投入到短视频的制作和分享中。因

模具设计学习知识分享

今天,我想谈谈我在模具设计CAD方面的一些心得体会。作为一个从事这个行业多年的工程师,

seo优化排名软件详细介绍

1. SEMrush SEMrush,一款广受好评的SEO神器,被誉为业内最佳的SEO分析工具。功能包括全