Compiler- 循环展开

news2024/11/25 22:57:55

循环展开不仅在编译原理中有涉及到,笔者记得在CSAPP里面也提到了这种优化方法。

话不多说,我们先来看个例子。

int loop(int a)
{
    int result = 0;
    for(int i = 0; i < a; i++){
        result += i;
    }
    return result;
}

int loop1(int a)
{
    int result = 0;
    int len = a/2;
    for(int i = 0; i < len; i++){
        result += 2*i;
        result += 2*i+1;
    }
    if (a % 2) result += a - 1;
    return result;
}

从功能上来讲,这两个函数是等价的,不同之处在于,for循环每次的step不一样,虽然第二个函数len仅仅为a/2,但是它一次加两个,所以只用加一半的次数就可以了。(对于第二个函数,a如果是奇数,要在最后把最后一个数加上)

我们给这个程序加上main函数来跑一下,测一测这两个函数的运行时间。

#include <stdio.h>
#include <time.h>

clock_t to_duration_in_ms(clock_t start, clock_t end) 
{
    return 1000 * (end - start) / CLOCKS_PER_SEC;
}

int loop(int a)
{
    int result = 0;
    for(int i = 0; i < a; i++){
        result += i;
    }
    return result;
}

int loop1(int a)
{
    int result = 0;
    int len = a/2;
    for(int i = 0; i < len; i++){
        result += 2*i;
        result += 2*i+1;
    }
    if (a % 2) result += a - 1;
    return result;
}


int main()
{
    int result,result1;
    clock_t start, end;
    start = clock();
    result = loop(200000000);
    end = clock();

    printf("result is %d,time is %ldms.\n",result,to_duration_in_ms(start,end));

    start = clock();
    result1 = loop1(200000000);
    end = clock();
    printf("result1 is %d,time is %ldms.\n",result1,to_duration_in_ms(start,end));

    return 0;
} 

 可以多运行几次

不难看出使用第二种循环(比较次数少)更节省时间,效率更高。

这就是程序的一种优化方法--循环展开

以上是我们自己主动对for循环进行的优化,那我们要是让编译器来帮我们做优化呢?

下面我们来看看,编译器对这个程序做优化的效果

相比于上次测试的结果,经过O3优化后,两个循环的运行时间都有大幅度的降低

可以看出,对代码进行优化是编译器的一个很重要的工作。

有的读者可能疑惑会不会真有代码这样写(这样写代码真的不会被打死吗hh~)笔者记得上学期在Web信息安全课上,看老师讲述PHP 的DoS攻击的时候(补充:PHP DoS是由于PHP本身对键值对数组的实现而导致的在存储恶意数据的时候造成算法复杂度升高,而导致的DoS)看到过下面的代码。这个是PHP本身对于键值对数组的实现,这里可以看出,PHP尽量避免使用循环,注重效率:

static inline ulong zend_inline_hash_func(const char *arKey, uint nKeyLength)
{
    register ulong hash = 5381;

    /* variant with the hash unrolled eight times */
    for (; nKeyLength >= 8; nKeyLength -= 8) {
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
    }
    switch (nKeyLength) {
        case 7: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 6: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 5: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 4: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 3: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 2: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 1: hash = ((hash << 5) + hash) + *arKey++; break;
        case 0: break;
EMPTY_SWITCH_DEFAULT_CASE()
    }
    return hash;
}

如果这个代码让笔者来写,可能会写成下面这样

static inline ulong zend_inline_hash_func(const char *arKey, uint nKeyLength)
{
    register ulong hash = 5381;

    /* variant with the hash unrolled eight times */
    for (; nKeyLength >= 1; nKeyLength --) {
        hash = ((hash << 5) + hash) + *arKey++;
    }
    return hash;
}

那为什么PHP源码中的实现看起来这么冗长呢?

它在这个for循环中step每次减8,for循环体中的八步操作都是一样的,主要是为了减少循环的次数,即循环进行一次,同时操作8步。

像我们上面的第一个例子中,我们是主动给它除以2,让它的循环次数减少为原来的1/2。

对于PHP中键值对数组实现的这个例子,它的循环此处减少为1/8。

ps:上述PHP源码中的switch也比较有意思,它的作用是:如果nKeyLength不是8的倍数,就需要额外的处理最后这几个数字,源码中还特意在case后面提示了/* fallthrough... */,我们知道case后面一般都会跟上break,不然会把后面的代码一行一行的执行一遍,而PHP源码在此处故意不写break,这样的好处是,如果nKeyLength=7,那么从case 7开始执行7行,如果nKeyLength=5,那么从case 5开始执行5行,这种写法还是比较高效的hh~

由此看出PHP源码写的真挺好的(仰望大佬~)。从上面的例子中,可以看出什么呢?如果我们理解编译器的工作,理解底层的知识,对我们自己的编码水平其实也有帮助。


以上为中科大软件学院《编译工程》课后总结,感谢郭燕老师的倾心教授,老师讲的太好啦(^_^)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/459836.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

虚拟化、容器与Docker基本介绍以及安装部署镜像加速

目录 一.虚拟化概述 1.虚拟化是什么&#xff1f; 2.虚拟化两大组件 3.虚拟化类型 4.虚拟化功能 二.容器概述 1.容器是什么&#xff1f; 2.容器的优点 3.容器的缺点 三.Docker概述 1.Docker是什么&#xff1f; 2.Docker容器与虚拟机的区别 3.容器在内核中支持两种重…

从0开始学习docker-1.mysql安装

从0开始学习docker 环境安装安装mysql备份镜像删除镜像镜像恢复 环境安装 yum update yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install docker-ce systemc…

IOS工程:NSThread sleepForTimeInterval的使用,游戏中途中断(接电话或者点击Home按钮),重新回到游戏音频音效失效问题

IOS工程&#xff1a;NSThread sleepForTimeInterval的使用&#xff0c;游戏中途中断&#xff08;接电话或者点击Home按钮&#xff09;&#xff0c;重新回到游戏音频音效失效问题 设备/引擎&#xff1a;Mac&#xff08;11.7&#xff09;/cocos 开发工具&#xff1a;Xcode 开发…

composer 安装gitlab私有库

开发PHP项目&#xff0c;免不了用composer。最近做一个项目&#xff0c;需要到公司内部开发的核心包&#xff0c;核心包放在内网搭建的gitlab仓库中&#xff0c;于是我用composer进行下载&#xff0c;报错&#xff1a; Cloning into bare repository C:/Users/Administrator/A…

Mac电脑系统管家CleanMyMac X4.13安装下载使用教程

当我们刚刚拿到那闪亮的新Mac时&#xff0c;是多么令人愉悦的一种感觉&#xff01;随着时间的推移&#xff0c;你可能已经注意到它的速度减慢&#xff0c;磁盘空间逐渐减少。不用担心&#xff0c;CleanMyMac会为你的电脑带来焕然一新的体验。这篇文章将向你介绍CleanMyMac的奇妙…

企业oa管理系统是什么

办公自动化&#xff08;Office Automation&#xff0c;简称OA&#xff09;&#xff0c;是将计算机、通信等现代化技术运用到传统办公方式&#xff0c;进而形成的一种新型办公方式。 办公自动化利用现代化设备和信息化技术&#xff0c;代替办公人员传统的部分手动或重复性业务活…

史上最全Python14张思维导图+字节跳动出品《Python背记手册》,高清PDF限时开放!

前言 Python是一种语法简单、功能强大的编程语言&#xff0c;它既适用于传统编程语言擅长的Web开发、移动开发、游戏开发、桌面应用&#xff0c;又适用于当前流行的人工智能、大数据、科学计算、金融分析…… 如果你想要学习一门编程语言Python肯定是一个不错的选择&#xff…

Scala之集合(2)

目录 集合基本函数&#xff1a; &#xff08;1&#xff09;获取集合长度 &#xff08;2&#xff09;获取集合大小 &#xff08;3&#xff09;循环遍历 &#xff08;4&#xff09;迭代器 &#xff08;5&#xff09;生成字符串 &#xff08;6&#xff09;是否包含 衍生集合…

itop-3568 开发板系统编程学习笔记(20)看门狗应用编程

【北京迅为】嵌入式学习之Linux系统编程篇 https://www.bilibili.com/video/BV1zV411e7Cy/ 个人学习笔记 文章目录 看门狗简介看门狗编程命令&#xff08;方法&#xff09;开启和关闭看门狗设置超时时间获取超时时间喂狗 看门狗底层简析看门狗编程实验 看门狗简介 看门狗&#…

MiniGPT-4开源了:看图聊天、教学、创作、搭网站

深度学习系列文章 文章目录 深度学习系列文章前言MiniGPT4效果展示 前言 一个月前&#xff0c;OpenAI 总裁 Greg Brockman 向世人展示了 GPT-4 令人惊讶的多模态能力&#xff0c;如从手写文本直接生成网站和识别图像中的幽默元素等。 尽管目前 OpenAI 暂未对 GPT-4 用户开放这…

农业灌溉以电折水测控终端-开启用水计量新模式

产品概述 农业灌溉以电折水测控终端&#xff08;MGTR-W&#xff09;是一款拥有“最强大脑”的农业水资源计量管理终端&#xff0c;内置以电折水逻辑运算&#xff0c;主要研究耗电量与取水量之间的关系&#xff0c;分析水电折算系数&#xff0c;进而通过计算耗电量与水电折算系数…

如何在个人web项目中使用Servlet监听器?

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 服务器软件&#xff1a;apache-tomcat-8.5.27 目录 一. Servlet监听器是什么&#xff1f;二. Servlet监听器有哪些作用?2.1 监听域对象的创建和销毁2.1.1 ServletContextListener接口…

zabbix自定义监控项脚本

以下脚本具体如何使用可参考以下文章 配置zabbix自定义监控项_Apex Predator的博客-CSDN博客 1.检测url是否存活 vi /opt/zabbix_jb/check_url_status.sh #!/bin/bash acurl -s -o /dev/null -w "%{http_code}" "$1" bcurl -s -o /dev/null -w "%…

heic的照片怎么转化jpg格式,3个工具分享

heic的照片怎么转化jpg格式&#xff1f;当我想要把照片进行人物抠像的话那我们得需要使用专业图片软件PS。因为人物抠像是一种常见的图像处理技术&#xff0c;它在我们职场中有广泛的需求&#xff0c;它可以将人物从照片中提取出来放置到其他地方&#xff0c;使得照片更具专业性…

如何在 Cockpit 中管理虚拟机

Cockpit 是一个很将整个服务器置于一个集中的控制面板中&#xff0c;并对它们进行相当程度的控制。还可以在Cockpit中创建和管理虚拟机。 环境 Centos8 安装Cockpit 要使用 Cockpit 创建和管理虚拟机&#xff0c;必须在运行 Cockpit 的计算机上安装 cockpit-machines 模块&…

【传统方式部署Ruoyi微服务】

IP机器与部署组件 部署思路顺序&#xff1a; 1 安装mysql wget https://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm rpm -ivh https://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm rpm --import https://repo.mysql.com/RPM-GPG-K…

排序 - 快速排序(Quick Sort)

文章目录 快速排序介绍快速排序实现快速排序时间复杂度和稳定性快速排序稳定性快速排序时间复杂度 代码实现核心&总结 快速排序介绍 它的基本思想是: 选择一个基准数&#xff0c;通过一趟排序将要排序的数据分割成独立的两部分&#xff1b;其中一部分的所有数据都比另外一…

使用QToolButton和QStackedWidget的侧边栏(SideBar)的实现与实现原理解析

使用QToolButton和QStackedWidget的侧边栏&#xff08;SideBar&#xff09;的实现与实现原理解析 原文链接&#xff1a;https://blog.csdn.net/qq153471503/article/details/128528072 Demo下载&#xff1a;https://gitee.com/jhuangBTT/QtSideBar 1、简介 侧边栏是一个很常用…

ModStartBlog v7.2.0 暗黑模式,超级搜索,富文本升级

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议。 功能特性 丰富的模块市场&#xff0c;后台一键快速安装会…

Spring Security Ldap 登录认证流程的源码梳理

一、通过请求Controller开始登录认证 通过authenticationManager调用authenticate()方法开始登录认证&#xff0c;因为authenticationManager是通过Bean注入&#xff0c;因为SecurityLdapConfig是继承的WebSecurityConfigurerAdapter类&#xff0c;所以authenticationManager的…