正在阅读:

linux服务器性能调优-gcc __attribute__ 属性分析

658

一. 目的

了解gcc __attribute__ 属性,优化程序

GNU C 的一大特色就是__attribute__ 机制.

__attribute__ 可以设置:

函数属性(Function Attribute )

变量属性(Variable Attribute )

类型属性(Type Attribute )

函数属性(Function Attribute)

  • noreturn
  • noinline
  • always_inline
  • pure
  • const
  • nothrow
  • sentinel
  • format
  • format_arg
  • no_instrument_function
  • section
  • constructor
  • destructor
  • used
  • unused
  • deprecated
  • weak
  • malloc
  • alias
  • warn_unused_result
  • nonnull

类型属性(Type Attributes)

  • aligned
  • packed
  • transparent_union,
  • unused,
  • deprecated
  • may_alias

变量属性(Variable Attribute)

  • aligned
  • packed

Clang特有的

  • availability
  • overloadable

关键字__attribute__ 也可以对结构体(struct )或共用体(union )进行属性设置

大致有六个参数值可以被设定,即:

aligned, packed, transparent_union, unused, deprecated 和 may_alias

二. 总结

1.  __builtin_expect

/* If supported, give compiler hints for branch prediction. */

#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)

#define __builtin_expect(x, expected_value) (x)

#endif

/* linux-2.6.38.8/include/linux/compiler.h */

# define likely(x) __builtin_expect(!!(x), 1)

# define unlikely(x) __builtin_expect(!!(x), 0)

 

内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,

如果参数EXP 的值是常数,函数返回 1,否则返回 0

/* linux-2.6.38.8/include/linux/compiler.h */

# ifndef likely

# define likely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 1))

# endif

# ifndef unlikely

# define unlikely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 0))

# endif

两个函数编译生成的汇编语句所使用到的跳转指令不一样,

__builtin_expect实际上是为了满足在大多数情况不执行跳转指令,

所以__builtin_expect仅仅是告诉编译器优化,并没有改变其对真值的判断.

在存储这种IO路径上,提高CPU命中率对提升带宽和IOPS非常有用。

参考:https://kernelnewbies.org/FAQ/LikelyUnlikely

 

2. __attribute__((format())) 

该__attribute__属性可以给被声明的函数加上类似printf或者scanf的特征,

它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。

该功能十分有用,尤其是处理一些很难发现的bug.

format属性告诉编译器,按照printf, scanf,  strftime或

strfmon的参数表格式规则对该函数参数进行检查.

__attribute__((format(printf,m,n)))

__attribute__((format(scanf,m,n)))

demo:

/* like printf() but to standard error only */

extern void eprintf(const char *format, ...)

__attribute__((format(printf, 1, 2))); /* 1=format 2=params */

/* printf only if debugging is at the desired level */

extern void dprintf(int dlevel, const char *format, ...)

__attribute__((format(printf, 2, 3))); /* 2=format 3=params */

eg: extern void eprintf(const char *format, ...) __attribute__((format(printf, 1, 2)));

void foo() {

eprintf("i=%d\n",6);

eprintf("i=%s\n",6); //error

eprintf("i=%s\n","abc");

eprintf("%s,%d,%d\n",1,2);/error

}

编译:

attribute.c:7: warning: format argument is not a pointer (arg 2)

attribute.c:9: warning: format argument is not a pointer (arg 2)

attribute.c:9: warning: too few arguments for format

如果在函数声明去掉__attribute__((format(printf,1,2))),再重新编译,

再编译运行后,则并不会输出任何警告信息。

3.  __attribute__((nonnull()))  

_attribute__((__nonnull__(1)))

__attribute__((__nonnull__(2)))

__attribute__((__nonnull__(3)))

4.  __attribute__((noreturn))

此方法没有参数,表示这个函数没有返回值也不能有返回值

C库函数中的abort()和exit()的声明格式就采用了这种格式。

extern void exit(int) __attribute__((noreturn));

extern void abort(void) __attribute__((noreturn));

extern void myexit();

int test(int n) {

if ( n > 0 )

{

myexit();/* 程序不可能到达这里*/

}

else

{

return 0;

}

}

编译:

gcc –Wall –c noreturn.c

noreturn.c: In function `test':

noreturn.c:12: warning: control reaches end of non-void function

警告信息也很好理解,因为你定义了一个有返回值的函数test却有可能没有返回值,

程序当然不知道怎么办了!

修改为:

extern void myexit() __attribute__((noreturn));之后,编译不会再出现警告信息

5.  __attribute__((const))

表示一个方法的返回值只由参数决定,如果参数不变的话,

就不再调用此函数,直接返回值。经过我的尝试发现还是调用了,

后又经查资料发现要给gcc加一个-O的参数才可以,是对函数调用的一种优化。

该属性只能用于带有数值类型参数的函数上。

当重复调用带有数值参数的函数时,由于返回值是相同的,

所以此时编译器可以进行优化处理,除第一次需要运算外,

其它只需要返回第一次的结果就可以了,进而可以提高效率。

该属性主要适用于没有静态状态(static state)和副作用的一些函数,

并且返回值仅仅依赖输入的参数.

extern int square(int n) __attribute__     ((const));

for (i = 0; i < 100; i++ )    {

total += square (5) + i;

}

通过添加__attribute__((const))声明,编译器只调用了函数一次,

以后只是直接得到了相同的一个返回值

__attribute__((const)) int get_const_value(int x)

{

return x + 1;

}

//用gcc -o const const.c后运行const, get_const_value(10)就会输出两次。

//用gcc -O -o const const.c后运行const, get_const_value(10)只会输出一次。

get_const_value(10);

get_const_value(10);

6. __attribute__((availability))

7.  __attribute__((unused))

表示函数的返回值必须被检查或使用,否则会警告

__attribute__((__used__))

8.  __attribute__((cleanup()))

可以定义一个变量,在他的作用域结束的时候会自动执行一个指定的方法

//这里传递的参数是变量的地址 void intCleanup(int *num){

printf(@"cleanup------%d",*num);

}

void test{

int a __attribute__((cleanup(intCleanup))) = 10;

}

输出结果为:  cleanup------10

9. __attribute__((always_inline))

这段代码能够保证代码是内联的,因为你如果只定义内联的话,

编译器并不一定会以内联的方式调用,如果代码太多你就算用了内联也不一定会内联,

用了这个的话会强制内联.

static __inline__ __attribute__((always_inline))

将这段代码定义成一个宏,然在函数的前边就能直接强制内联,

如果是频繁调用的函数,这样可以提高一定的效率

10.  __attribute__((__nothrow__))

11.  __attribute__((__sentinel__))

12.  __attribute__((__pure__))

13.  __attribute__((__warn_unused_result__))

14.  __attribute__((__malloc__))

15.  __attribute__((__weak__))

int32 plugin_func_stub(const int8 *func, ...);

#define VA_ARGS(...) , ##__VA_ARGS__

#define FUNCK(func, ...) ((!func)?

(plugin_func_stub(__FUNCTION__ VA_ARGS(__VA_ARGS__))):(func(__VA_ARGS__)))

#ifndef ATTRI_CHECK

#define ATTRIBUTE_WIKE __attribute__((weak))

#else

#define ATTRIBUTE_WIKE

#endif

int ATTRIBUTE_WIKE test(int a, int b);

 

16.  __attribute__((__weak_import__))

17.  __attribute__((__noinline__))

18.  __attribute__((__transparent_union__))

19.  __attribute__((__aligned__((n))))

20. __attribute__((__visibility__("default")))

21. __attribute__((alias))

22. __attribute__((constructor))

1-100的范围是保留的,所以最好从100之后开始用.

(但是实际上,我在项目中测试100以内的,也没有得到警告)

main函数之前的,即constructor的优先级,数值越小,越先调用.

destructor中的数值越大,越先调用.

static __attribute__((constructor(101))) void before1()

{

printf("before1\n");

}

static __attribute__((constructor(102))) void before2()

{

printf("before2\n");

}

21. -finstrument-functions

该参数可以使程序在编译时,在函数的入口和出口处生成instrumentation调用。

恰好在函数入口之后并恰好在函数出口之前,将使用当前函数的地址和调用地址来调用下面的

profiling函数,在一些平台上,__builtin_return_address不能在超过当前函数范围之外正常工作,

所以调用地址信息可能对profiling函数是无效的。

void __cyg_profile_func_enter(void *this_fn, void *call_site);

void __cyg_profile_func_exit(void *this_fn, void *call_site);

21. 多种属性混合

/* send printf-like message to stderr and exit */

extern void die(const char *format, ...)

__attribute__((noreturn))

__attribute__((format(printf, 1, 2)));

/*or*/

extern void die(const char *format, ...) __attribute__((noreturn, format(printf, 1, 2)));

22. 修饰结构体 aligned (alignment)

该属性设定一个指定大小的对齐格式(以字节 为单位),例如:

struct S {

short b[3];

} __attribute__ ((aligned (8)));

typedef int int32_t __attribute__ ((aligned (8)));

该声明将强制编译器确保(尽它所能)变量类 型为

struct S 或者int32_t 的变量在分配空间时采用8 字节对齐方式。

如上所述,你可以手动指定对齐的格式,同 样,你也可以使用默认的对齐方式。

如果aligned 后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况

使用最大最有益的对齐方式。例如:

struct S {

short b[3];

} __attribute__ ((aligned));

这里,如果sizeof (short )的大小为2 (byte ),那么,S 的大小就为6 。

取一个2 的次方值,使得该值大于等于6 ,则该值为8 ,所以编译器将设置S 类型的对齐方式为8 字节。

aligned 属性使被设置的对象占用更多的空间,相反的,使用packed 可以减小对象占用的空间。

需要注意的是,attribute 属性的效力与你的连接器也有关,如果你的连接器最大只支持16 字节对齐,

那么你此时定义32 字节对齐也是无济于事的。

23. 修饰结构体 packed

使用该属性对struct 或者union 类型进行定义,设定其类型的每一个变量的内存约束。

__attrubte__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,

按照实际占用字节数进行对齐, 特别是协议头结构体应该用这个参数。

当用在enum 类型 定义时,暗示了应该使用最小完整的类型

it indicates that the smallest integral type should be used)。

  下面的例子中,packed_struct 类型的变量数组中的值将会紧紧的靠在一起,

但内部的成员变量s 不会被“pack” ,如果希望内部的成员变量也被packed 的话,

unpacked-struct 也需要使用packed 进行相应的约束

struct unpacked_struct

{

      char c;

      int i;

};

struct packed_struct

{

     char c;

     int  i;

     struct unpacked_struct s;

}__attribute__ ((__packed__));

 

#pragma pack(push) //保存对齐状态

#pragma pack(4)// 设定为4字节对齐

struct test

{

int m1;

long long m4;

int m5;

};

#pragma pack(pop)// 恢复对齐状态

 

看下面的案例:

struct p {

int a;

char b;

short c;

} __attribute__((aligned(4))) pp;

struct m {

char a;

int b;

short c;

} __attribute__((aligned(4))) mm;

struct o {

int a;

char b;

short c;

} oo;

struct x {

int a;

char b;

struct p px;

short c;

} __attribute__((aligned(8))) xx;

 

printf("sizeof(int)=%d, sizeof(short)=%d, sizeof(char)=%d\n",

sizeof(int), sizeof(short), sizeof(char));

printf("pp=%d,mm=%d \n", sizeof(pp),sizeof(mm));

 

结果:

sizeof(a)+sizeof(b)+sizeof(c)=4+1+2=6<8 所以sizeof(pp)=8

sizeof(a)+sizeof(b)+sizeof(c)=1+4+2=7

但 a 需要用 3 个字节填充,b 是 4 个字节, 而 c 又要占用 4 个字节,所以 sizeof(mm)=12

sizeof(a)+sizeof(b)+sizeof(c)=4+1+2=7  因为默 认是以4 字节对齐,所以sizeof(oo)=8

sizeof(a)+ sizeof(b)=4+1=5  sizeof(pp)=8;

即xx 是采用8 字节对齐的,所以要在a ,b 后面添3 个空余字节,

然后才能存储px ,4+1+(3)+8+1=17

因为xx 采用的对齐是8 字节对齐,所以xx 的大小必定是8 的整数倍,

即xx 的大小是一个比17 大又是8 的倍数的一个最小值,由此得到17<24 ,所以sizeof(xx)=24

留下脚印,证明你来过。

*

*

流汗坏笑撇嘴大兵流泪发呆抠鼻吓到偷笑得意呲牙亲亲疑问调皮可爱白眼难过愤怒惊讶鼓掌
关闭