【C语言最佳实践】为性能编码 |
您所在的位置:网站首页 › atom运行c语言 › 【C语言最佳实践】为性能编码 |
【C语言最佳实践】为性能编码
作者:wallace-lai
时间:2022-08-04
一、什么是性能
性能的两层含义: (1)空间复杂度(越低越好) (2)时间复杂度(越低越好) 优化性能:空间复杂度和时间复杂度的平衡 简单案例:编写函数返回一个字节中位值为一的位的个数 (1)版本一 没有废话,就硬算,然而效率不高。 int count_one_bits(unsigned char byte) { int n = 0; for (int i = 0; i > i) & 0x01) { n++; } } return n; }(2)版本二 空间换时间,一个字节共有256个值,打一个256大小的表,然后直接返回即可。尽管时间复杂度下降为O(1),但是还有两个不足的地方: 一个字节的比特位为1的个数最多不超过8,然而使用了int来存储,大量空间被浪费了 我们真的需要打一个256这么大的表吗,会不会存在好多浪费? static int nr_one_bits[] = { 0, 1, 1, 2, 1, 2, 2, 3, // ... }; static inline int count_one_bits(unsigned char byte) { return nr_one_bits[byte]; }(3)版本三 将一个字节拆分成高四位和低四位分开处理,我们只需要打一张16大小的表即可。内存空间从1K(256* 4B)下降到了16B,运行时间几乎不变。完美! static unsigned char nr_one_bits_half_byte[] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, }; static inline int count_one_bits(unsigned char byte) { return nr_one_bits_half_byte[byte & 0x0F] + nr_one_bits_half_byte[(byte & 0xF0) >> 4]; } 二、提高性能的两个基本原则两个原则 (1)不做无用功:不要有无意义的代码 (2)杀鸡莫用牛刀:如果使用牛刀的收益覆盖不了牛刀的成本,那么就不该用牛刀 常见的无用功 (1)没必要的初始化 (2)多余的函数调用 void foo(void) { char buf[64] = {0}; memset(buf, 0, sizeof(buf)); strcpy(buf, "foo"); }既然本意是做strcpy,为啥还要初始化? 做了初始化还不够,还要调memset做甚? 杀鸡用牛刀 (1)滥用STDIO接口,下面的例子中,往buffer中拷贝字符串完全没有必要使用STDIO这种“重量级”的api,直接使用字符串拷贝就好了。 // use strcpy and strcat instead sprintf(a_buffer, "%s%s", a_string, b_string); // use aoti, aotl, atoll, strtol instead sscanf(a_string, "%d", &i);(2)滥用高级数据结构,比如,总共就10个数据非要用红黑树、哈希表等等,属实是没必要。 三、常见方法和技巧提升软件性能的常用方法和技巧 (1)动态缓冲区分配 版本一:不管怎样,我始终钟情于malloc和free void foo(size_t len) { char *buff = malloc(len); // ... free(buff); }版本二:malloc的时间代价很大,能不用就不用。在绝大多数的情况下能用栈内存就不用堆内存 void foo(size_t len) { char stack_buff[PATH_MAX + 1]; char *buff; if (len > sizeof(stack_buff)) { buff = malloc(len); } else { buff = stack_buff; } // ... if (buff && buff != stack_buff) { free(buff); } }(2)字符串匹配 版本一:逐个匹配即可 int get_locale_category_by_keyword(const char *keyword) { if (strcasecmp(keyword, "ctype") == 0) { return LC_CTYPE; } else if (strcasecmp(keyword, "collate") == 0) { return LC_COLLATE; } else if (strcasecmp(keyword, "numeric") == 0) { return LC_NUMERIC; } else if (strcasecmp(keyword, "monetary") == 0) { return LC_MESSAGES; } else if (strcasecmp(keyword, "time") == 0) { return LC_TIME; } // ... }上面的代码有问题吗?没有问题,但就是性能还可以有一丢丢地提升。什么!还能提升?是的! 版本二:手工哈希一下,妙哉! int get_locale_category_by_keyword(const char *keyword) { const char *head = keyword + 1; switch (keyword[0]) { case 'c': case 'C': if (strcasecmp(head, "ctype" + 1) == 0) { return LC_TYPE; } else if (strcasecmp(head, "collate" + 1) == 0) { return LC_COLLATE; } break; case 'n': case 'N': if (strcasecmp(head, "numeric" + 1) == 0) { return LC_NUMERIC; } else if (strcasecmp(head, "name" + 1) == 0) { return LC_NAME; } // ... } }版本三:牛刀版本 如果字符串的情况比较多,那么干脆就用哈希表吧!牢记不要杀鸡用牛刀的原则。如果用牛刀的收益无法覆盖用牛刀的成本,那么就不要用牛刀。 // 注意定义正确的 SIZEOF_SIZE_T #if SIZEOF_SIZE_T == 8 // 2^40 + 2^8 + 0xb3 = 1099511628211 #define FNV_PRIME ((size_t)0x100000001b3ULL) #define FNV_INIT ((size_t)0xcbf29ce484222325ULL) #else // 2^24 + 2^8 + 0x93 = 16777 #define FNV_PRIME ((size_t)0x01000193) #define FNV_INIT ((size_t)0x811c9dc5) #endif static size_t str2Key(const char *str, size_t length) { const unsigned char *s = (const unsigned char *)str; size_t hval = FNV_INIT; if (str == NULL) { return 0; } if (length == 0) { length = strlen(str); } // FNV-1a hash each octet in the buffer while (*s && length) { // xor the bottom with the current octet hval ^= (size_t)*s++; length--; // multiply by the FNV magic prime #ifdef __GUNC__ #if SIZEOF_SIZE_T == 8 hval += (hval |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |