一行代码的神奇力量:C语言的极致优化艺术

在嵌入式底层开发过程中,字节对齐是非常重要的,比如通常的四字节处理,这个在芯片性能上带来显著提升,减少了内存访问次数和循环控制开销。来看一个简单的例子,普通的内存拷贝(一个字节一个字节的拷贝):

void MemoryCopy(char *dest, char *src, unsigned int size)
{
   if (!dest || !src || !size) return;

   for (int i = 0; i < size; i++)
   {
    //逐字节拷贝
    dest[i] = src[i];
   }
}

优化版:四字节拷贝

void MemoryCopy_s(char *dest, char *src, unsigned int size)
{
   if (!dest || !src || !size) return;

   unsigned int i = 0;
   unsigned int count = size / 4;

   for (; i < count * 4; i += 4)
   {
      //一次拷贝四个字节
      *(unsigned int *)(dest + i) = *(const unsigned int *)(src + i);
   }

   //处理剩余的0~3个字节
   for (; i < size; i++)
   {
     dest[i] = src[i];
   }
}

别小看这个简单的改动,这个优化在拷贝大量数据时,比如在32位处理器上,这个性能提升是非常显著的,内存拷贝速度可轻松提高百分之几百以上,它大量地减少了循环迭代次数和内存总线事务数量,更好地利用了cpu缓存。写一个测试用例,用来测试对比上面的逐字节内存拷贝MemoryCopy和优化后的四字节拷贝MemoryCopy_s之间的性能差异。

看看到底有多大的差距,如下:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "time.h"

#define MEM_SIZE (20 * 1024 * 1024) //20M内存
#define CYCLE_NUMS 256 //循环次数

void TestPerformance()
{
   char *src = (char *)malloc(MEM_SIZE);
   char *dest1 = (char *)malloc(MEM_SIZE);
   char *dest2 = (char *)malloc(MEM_SIZE);

   //初始化原始内存块数据
   for (int i = 0; i < MEM_SIZE - 1; i++)
   {
      src[i] = (char)(rand() % 256);
   }
      src[MEM_SIZE - 1] = '\0';

    //测试逐字节拷贝性能
    clock_t start = clock();
   for (int i = 0; i < CYCLE_NUMS; i++)
   {
      MemoryCopy(dest1, src, MEM_SIZE);
   }
    clock_t end = clock();
    double elapsedTime1 = (double)(end - start) / CLOCKS_PER_SEC;
    printf("MemoryCopy: %.6fs\n", elapsedTime1);

   //测试四字节拷贝性能
    start = clock();
    for (int i = 0; i < CYCLE_NUMS; i++)
    {
     MemoryCopy_s(dest2, src, MEM_SIZE);
    }
   end = clock();
   double elapsedTime2 = (double)(end - start) / CLOCKS_PER_SEC;
   printf("MemoryCopy_s: %.6fs\n", elapsedTime2);

   //验证拷贝结果是否正确
   printf("copy result is %s\n", (!memcmp(dest1, dest2, MEM_SIZE)) ? "correct" : "incorrect");

   double enhancement = elapsedTime1 / elapsedTime2;
   printf("Performance Enhancement is %.2f(%.2f%%)\n", enhancement, (enhancement - 1) * 100);

   //释放内存
   free(src);
   free(dest1);
   free(dest2);
   }

  int main()
   {
srand((unsigned int)time(NULL));
TestPerformance();
return 0;
}

【运行结果】测试用例模拟一个20M的内存块,对其反复拷贝256次,普通的逐字节拷贝耗时11.86秒,而四字节拷贝仅耗时2.7秒,相差4.37倍,性能提升了336.71%

在这里插入图片描述

注意:测试数据与实际环境有关,仅供参考。另外,示例代码只是简单演示,并没有全面考虑架构限制、完整的四字节对齐等场景,并非完整和健壮的实现。比如,非X86环境下(某些架构Arm、MIPS),未对齐访问很有可能导致硬件异常。现代编译器的memcpy通常已经包含了这些优化。

再来看一个例子,这个写法也是大大减少了循环开销和数据相关性,允许cpu更好地利用指令级并行。

for (int i = 0; i < size; i++)
{
buf[i] = HandleData(buf[i]);
}

for (int i = 0; i < size; i += 4)
{
//循环展开,交错执行
buf[i] = HandleData(buf[i]);
buf[i + 1] = HandleData(buf[i + 1]);
buf[i + 2] = HandleData(buf[i + 2]);
buf[i + 3] = HandleData(buf[i + 3]);
}

通过展开循环的优化,外场实测可获得2倍左右的加速(数据结果与实际环境有关)。注意,这是伪代码,实际编码过程中还要注意数组buf是否可能越界,再根据实际业务适配修改。读者可根据上面的测试用例,依样画葫芦,多写几个用例来进行性能测试对比。

除此之外,还有很多,比如两个for循环要把小循环放在外层(减少跨切次数)、尽量提取和减少循环里的if条件判断、使用restrict关键字的优化、利用硬件特性(GCC的__built in_popcount内在函数)、数据预取优化__builtin_prefetch等等。

最后,再来看一个平时很常见的条件判断:

//普通
if (0 <= value && 1024 > value)
{
//do_something...
}

//优化
if ((unsigned int)value < 1024)
{
//do_something...
}

这种简单的类型转换,直接将两个比较合并为一个,因为负数转换为无符号整数会变成很大的正数,自动超出范围。这种技巧在性能关键的循环中也能带来一定的提升。当然,所有技巧还要结合实际业务来进行适配和决策。

调试环境:windows eclipse

优秀的代码不是一蹴而就的,而是多年经验和良好习惯的累积。多读,多写,多调试,多踩坑,多思考,才能厚积薄发。

文章来源公众呺 C语言算法,如有侵权,请私我联系删除。

题外话

黑客&网络安全如何学习

如果你也对网路安全技术感兴趣,但是又没有合适的学习资源,我可以把私藏的网安学习资料免费共享给你们,来看看有哪些东西。

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我们和网安大厂360共同研发的的网安视频教程,内容涵盖了入门必备的操作系统、计算机网络和编程语言等初级知识,而且包含了中级的各种渗透技术,并且还有后期的CTF对抗、区块链安全等高阶技术。总共200多节视频,100多本网安电子书,最新学习路线图和工具安装包都有,不用担心学不全。
在这里插入图片描述

🐵这些东西我都可以免费分享给大家,需要的可以点这里自取👉:网安入门到进阶资源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值