编译代码性能优化实践:理解循环展开(pragma unroll)

这篇具有很好参考价值的文章主要介绍了编译代码性能优化实践:理解循环展开(pragma unroll)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引言:CUDA的矩阵乘优化经常见到 pragma unroll 的使用,本文通过简单的示例,展示了CPU和CUDA对循环展开前后的性能表现,来通俗理解循环展开的优化策略。

一、什么是循环展开?

        简单理解:将代码中的for循环展开,减少循环次数;循环展开的本质是,利用CPU指令级并行,来降低循环的开销,当然,同时也有利于指令流水线的高效调度

优点

  • 提高缓存命中(cache hit)率,增加循环体内语句并发执行的可能性(需要循环体内语句不相关);
  • 减少分支预测失败的可能性,提高性能

缺点

  • 程序代码膨胀、代码可读性降低
  • 消耗较多寄存器缓存(SM里的寄存器大小是有限的,SM会根据一个块需要消耗的寄存器大小和线程的个数去分配该SM上块的个数,当一个SM连一个块都分配不了时,就会导致内核启动不了)

二、循环展开的使用

        循环展开在CPU和CUDA端都可以使用,但在CPU端可以由程序员手动实现,也可以通过成熟的编译器实现优化。# pragma unroll 是常用在CUDA编程的核函数中对for循环展开的使用方法。

        下面通过计算0-100000个数字累加的和为例,展示CPU和CUDA下的对循环展开使用的理解。

CPU端

1)原始不展开

void test_cpu_1(int count, const char* name)
{  
    int sum = 0;

    auto start = std::chrono::system_clock::now();
    for(int i = 0;i < count;i++){  
        sum += i;
    }
    auto end = std::chrono::system_clock::now();
    auto dura = std::chrono::duration_cast<std::chrono::microseconds> (end - start);
    std::cout << name <<" cost time: "<< dura.count() << " microseconds" << std::endl;
    printf("                                                   sum = %d\n",sum);
}

2)循环展间隔4次

void test_cpu_2(int count, const char* name)
{
    int sum = 0;
    auto start = std::chrono::system_clock::now();
    for(int i=0; i<count; i+=4)
    {
        sum += i;
        sum += i+1;
        sum += i+2;
        sum += i+3;
    }
    auto end = std::chrono::system_clock::now();
    auto dura = std::chrono::duration_cast<std::chrono::microseconds> (end - start);
    std::cout << name <<" cost time: "<< dura.count() << " microseconds" << std::endl;
    printf("                                                   sum = %d\n",sum);

}

3)循环展开间隔4次,优化循环内的数据依赖关系

        上面虽然实现了循环展开,但是循环体内是的4行代码之间共用sum地址, 所以是有先后依赖的,如果我们把他们之间的依赖关系去掉,则能进一步提升代码性能。

void test_cpu_3(int count, const char* name)
{
    int sum = 0;
    int sum1=0,sum2=0,sum3=0, sum4=0;

    auto start = std::chrono::system_clock::now();
    for(int i=0;i < count;i+=4){
        sum1 += i;
        sum2 += i+1;
        sum3 += i+2;
        sum4 += i+3;
    }
    sum = sum1+sum2+sum3+sum4;
    auto end = std::chrono::system_clock::now();
    auto dura = std::chrono::duration_cast<std::chrono::microseconds> (end - start);
    std::cout << name <<" cost time: "<< dura.count() << " microseconds" << std::endl;
    printf("                                                   sum = %d\n",sum);

}

CUDA端

CUDA则主要对比使用# pragma unroll前后的区别。

1)原始不展开

__global__ void progam_kernel1(int* sum, int count)
{
    for(int i = 0;i < count;i++){  
        *sum += i;
    }
    
}

2)使用循环展开

__global__ void progam_kernel2(int* sum, int count)
{
    #pragma unroll
    for(int i = 0;i < count;i++){  
        *sum += i;
    }
}

性能分析与测试接口实现

        上面各种对比的方法测试时间如下,可以看到CPU端循环展开比原始不展开时间减少接近一半,而优化后的循环展开时间又减少将近一半。CUDA端使用pragma unroll后,时间减少三分之二。

cpu origin cost time: 1079 microseconds
                                                   sum = 704982704
cpu pragma unroll cost time: 678 microseconds
                                                   sum = 704982704
cpu pragma unroll_1 cost time: 374 microseconds
                                                   sum = 704982704
cuda origin cost time: 18 microseconds
                                                   sum = 704982704
cuda pragma unroll cost time: 6 microseconds
                                                   sum = 704982704

编译如下,因为把kernel函数写在一起了,所以用.cu为后缀命名。

nvcc -o test test_performance.cu 

下面是总体实现的代码 

// file name: test_performance.cu
#include <stdio.h>
#include <cuda_runtime.h>
#include "cublas_v2.h"
#include <unistd.h>
#include <chrono>
#include <iostream>
#include <string>
using namespace std;

void test_cpu_1(int count, const char* name)
{  
    int sum = 0;

    auto start = std::chrono::system_clock::now();
    for(int i = 0;i < count;i++){  
        sum += i;
    }
    auto end = std::chrono::system_clock::now();
    auto dura = std::chrono::duration_cast<std::chrono::microseconds> (end - start);
    std::cout << name <<" cost time: "<< dura.count() << " microseconds" << std::endl;
    printf("                                                   sum = %d\n",sum);
}


void test_cpu_2(int count, const char* name)
{
    int sum = 0;
    auto start = std::chrono::system_clock::now();
    for(int i=0; i<count; i+=4)
    {
        sum += i;
        sum += i+1;
        sum += i+2;
        sum += i+3;
    }
    auto end = std::chrono::system_clock::now();
    auto dura = std::chrono::duration_cast<std::chrono::microseconds> (end - start);
    std::cout << name <<" cost time: "<< dura.count() << " microseconds" << std::endl;
    printf("                                                   sum = %d\n",sum);

}

void test_cpu_3(int count, const char* name)
{
    int sum = 0;
    int sum1=0,sum2=0,sum3=0, sum4=0;

    auto start = std::chrono::system_clock::now();
    for(int i=0;i < count;i+=4){
        sum1 += i;
        sum2 += i+1;
        sum3 += i+2;
        sum4 += i+3;
    }
    sum = sum1+sum2+sum3+sum4;
    auto end = std::chrono::system_clock::now();
    auto dura = std::chrono::duration_cast<std::chrono::microseconds> (end - start);
    std::cout << name <<" cost time: "<< dura.count() << " microseconds" << std::endl;
    printf("                                                   sum = %d\n",sum);

}

__global__ void progam_kernel1(int* sum, int count)
{
    for(int i = 0;i < count;i++){  
        *sum += i;
    }
    
}

__global__ void progam_kernel2(int* sum, int count)
{
    #pragma unroll
    for(int i = 0;i < count;i++){  
        *sum += i;
    }
}

void test_cuda_1(int count, const char* name)
{
    int sum =0;
    int* g_sum;
    cudaMalloc((void **)&g_sum, sizeof(int) * 1);
    cudaMemcpy(g_sum, &sum, 1 * sizeof(int),cudaMemcpyHostToDevice);

    auto start = std::chrono::system_clock::now();
    progam_kernel1<<<1,1>>>(g_sum, count); //调用核函数
    auto end = std::chrono::system_clock::now();
    auto dura = std::chrono::duration_cast<std::chrono::microseconds> (end - start);
    std::cout << name <<" cost time: "<< dura.count() << " microseconds" << std::endl;

    cudaMemcpy(&sum, g_sum, sizeof(int) * 1, cudaMemcpyDeviceToHost);
    printf("                                                   sum = %d\n",sum);
    cudaFree(g_sum); 

}

void test_cuda_2(int count, const char* name)
{
    int sum =0;
    int* g_sum;
    cudaMalloc((void **)&g_sum, sizeof(int) * 1);
    cudaMemcpy(g_sum, &sum, 1 * sizeof(int),cudaMemcpyHostToDevice);

    auto start = std::chrono::system_clock::now();
    progam_kernel2<<<1,1>>>(g_sum, count); //调用核函数
    auto end = std::chrono::system_clock::now();
    auto dura = std::chrono::duration_cast<std::chrono::microseconds> (end - start);
    std::cout << name <<" cost time: "<< dura.count() << " microseconds" << std::endl;

    cudaMemcpy(&sum, g_sum, sizeof(int) * 1, cudaMemcpyDeviceToHost);
    printf("                                                   sum = %d\n", sum);
    cudaFree(g_sum);  

}

void test_performance()
{
    int count =100000;
    std::string s1 ="cpu origin";
    std::string s2 = "cpu pragma unroll";
    std::string s21 = "cpu pragma unroll_1";
    std::string s3 = "cuda origin";
    std::string s4 = "cuda pragma unroll";

    test_cpu_1(count, s1.c_str());
    test_cpu_2(count, s2.c_str());
    test_cpu_3(count, s21.c_str());
    test_cuda_1(count, s3.c_str());
    test_cuda_2(count, s4.c_str());


}

int main(int argc, char *argv[]) 
{
    test_performance();
    return 0;

}

借助编译器的性能优化

        程序员针对CPU端编写代码时候,可以使用上面的循环展开实现,实际上在c/c++的编译器已经非常成熟,针对这种代码都有对应的优化策略。在实际项目部署时候,可以开启编译器自动优化选项,帮助我们进一步提升代码性能。

        比如,本次测试我写了CMakeLists.txt脚本,添加编译器优化的参数后执行结果如下。CPU端和未开启编译器优化相比,时间性能有了很大的提升。手动增加的循环展开的代码时间也大大降低了。

cpu origin cost time: 31 microseconds
                                                   sum = 704982704
cpu pragma unroll cost time: 0 microseconds
                                                   sum = 704982704
cpu pragma unroll_1 cost time: 0 microseconds
                                                   sum = 704982704
cuda origin cost time: 18 microseconds
                                                   sum = 704982704
cuda pragma unroll cost time: 6 microseconds
                                                   sum = 704982704

上面未开启编译器优化的输出:

cpu origin cost time: 1079 microseconds
                                                   sum = 704982704
cpu pragma unroll cost time: 678 microseconds
                                                   sum = 704982704
cpu pragma unroll_1 cost time: 374 microseconds
                                                   sum = 704982704
cuda origin cost time: 18 microseconds
                                                   sum = 704982704
cuda pragma unroll cost time: 6 microseconds
                                                   sum = 704982704

在CMakeLists.txt添加了如下一行:

set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -O1 -Wall")

参考:

C++性能榨汁机之循环展开 - 知乎

【CMAKE】c++代码编译加速以及优化项_cmake 编译优化-CSDN博客文章来源地址https://www.toymoban.com/news/detail-843868.html

到了这里,关于编译代码性能优化实践:理解循环展开(pragma unroll)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++代码性能优化的好处与缺点?有哪些编译器优化选项?

    性能优化是C++编程中的一个重要方面,它可以带来许多好处,但也有一些潜在的缺点。 以下是C++代码性能优化的一些优缺点: 优点: 提高执行速度 : 优化后的代码可以更快地执行,这对于需要处理大量数据或需要快速响应的应用程序尤其重要。 减少资源消耗 : 优化可以减少

    2024年03月27日
    浏览(45)
  • Linux C++性能优化秘籍:从编译器到代码,探究高性能C++程序的实现之道

    随着大数据、人工智能等技术的飞速发展,程序性能优化的重要性愈发突出。优化性能可以降低资源消耗、提高系统响应速度,从而在有限的硬件资源下,实现更高的吞吐量和处理能力。此外,性能优化也有助于降低能耗、减少散热问题,延长硬件使用寿命。 Linux操作系统具

    2023年04月09日
    浏览(36)
  • 【Java】Java中使用HashMap优化多层for循环嵌套以及for循环之性能优化

    for循环是开发时常用的语法之一,比如对数组,集合的遍历等,但是如果使用不好也会出现很多新能损耗的问题,今天就来讲解一下for循环的常用性能优化问题。 for循环 里面还有 for循环, 然后做一些数据匹配、处理 这种场景。 m层嵌套的n次的for循环的时间复杂度为O(n^m),

    2024年02月16日
    浏览(32)
  • Android复杂UI的性能优化实践 - PTQBookPageView 性能优化记录

    作者:彭泰强 要做性能优化,首先得知道性能怎么度量、怎么表示。因为性能是一个很抽象的词,我们必须把它量化、可视化。那么,因为是UI组件优化,我首先选用了 GPU呈现模式分析 这一工具。 在手机上的开发者模式里可以开启 GPU呈现(渲染)模式分析 这一工具,有的

    2024年02月14日
    浏览(33)
  • 前端需要理解的性能优化知识

    优化的目的是展示更快、交互响应快、页面无卡顿情况。 使用 ChromeDevTool 作为性能分析工具来观察页面性能情况。其中Network观察网络资源加载耗时及顺序,Performace观察页面渲染表现及JS执行情况,Lighthouse对网站进行整体评分,找出可优化项。 DOM的解析受JS加载和执行的影响

    2024年02月11日
    浏览(37)
  • 【Qt 性能优化】 理解与优化Qt信号槽机制 - 提升应用性能的关键策略

    在这个科技日新月异的时代,软件开发不仅仅是编写代码,更是一种艺术。正如著名计算机科学家 Edsger Dijkstra 所说:“计算机科学并不仅仅关于机器,而是更多地关于人的智慧。” Qt框架,作为一个深受广大开发者喜爱的跨平台应用程序和用户界面开发框架,其核心机制之

    2024年02月20日
    浏览(38)
  • Andriod开发性能优化实践

    内存优化 在Android开发中,有一些实践可以帮助进行内存优化,以减少应用程序的内存占用和提高性能。以下是一些常见的内存优化实践: 使用合适的数据结构和集合:选择合适的数据结构和集合来存储和操作数据,以减少内存占用。例如,使用SparseArray代替HashMap,使用Arr

    2024年02月15日
    浏览(32)
  • 线索系统性能优化实践

    在京东家居事业部,线索CRM系统扮演着至关重要的角色,它作为构建家居场景核心解决方案集的首要环节,肩负着获客和拓展业务的重要使命。然而,随着业务的不断扩张和市场需求的日益增长,系统原有的架构开始显露出诸多不适应之处,如架构设计不再清晰,代码存在过

    2024年01月25日
    浏览(35)
  • React组件性能优化实践

    React组件性能优化的核心是减少渲染真实DOM节点的频率,减少 Virtual DOM比对的频率。 在组件中为 window注册的全局事件,以及定时器,在组件卸载前要清理掉,防止组件卸载后继续执行影响应用性能。 需求:开启定时器,然后卸载组件,查看组件中的定时器是否还在运行。 什

    2024年02月14日
    浏览(29)
  • 客户端性能优化实践

    双十一大促时,客户客服那边反馈商品信息加载卡顿,在不断有订单咨询时,甚至出现了商品信息一直处于加载状态的情况,显然,在这种高峰期接待客户时,是没法进行正常的接待工作的。 起初,页面一直处于加载状态,初步认为是后端接口返回太慢导致,后经过后端日志

    2024年02月03日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包