C++实战之OpenCL矩阵相乘优化(二)

OpenCL Reduction操作与group组内同步(barrier)操作的理解 OpenCL Reduction操作与group同步 先解释一下什么是reduction操作和barrier操作 Reduction操作:规约操作就是由多个数生成一个数,如求最大值、最小值、向量点积、求和等操作,都属于这一类操作。 group同步:OpenCL只提供了工作组内的各线程之间的同步机制,并没有提供所有线程的同步。提供组内item-work同步的方法: void barrier (cl_mem_fence_flags flags) 参数说明:   cl_mem_fence_flags 可以取C 阅读详情

前言

上一篇文章,分析了简单的矩阵相乘在opencl里面的优化kernel代码,每个work-item只负责计算结果矩阵的一个元素。下一步准备每次计算出结果矩阵的块元素,看看计算时间是如何。

这个矩阵系列参考国外一个大神的教程:
https://cnugteren.github.io/tutorial/pages/page4.html
有每个kernel的详解工程,还有github代码工程。

具体分析

这里引入opencl内存的概念:
比较常见的有:
全局内存 __global 修饰符,通常修饰指向一个数据类型的地址,
本地内存 __local 修饰符。local 定义的变量在一个work-group中是共享的,也就是说一个work-group中的所有work-item都可以通过本地内存来进行通信,

私有内存,private 每个work-item里的内部变量
常量内存, constant

下面是opencl的内存模型:
这里写图片描述

我们分析一下之前的矩阵相乘的一些性能:

__kernel void hello_kernel(__global const int *a,
                           __global const int *b,
                           __global int *result_matrix,int  result_matrix_row,
                           int  result_matrix_col,int  compute_size)
{
    int row = get_global_id(0);
    int col = get_global_id(1);


    int sum = 0;
    for(int i=0;i<compute_size;i++)
    {
        sum += a[row*compute_size+i] * b[i*result_matrix_col+col];
    }

    result_matrix[row*result_matrix_col+col] = sum;
}

首先在运行时总共有M*N个work-item同时执行,每个work-item中执行一个size为k(computesize)的for循环,循环里面每次分别load 数组a和b中的一个元素,所以综合起来一个kernel会有 M*N*K*2 个加载global内存的操作,乘以2是因为a,b两个数组。

其次每个work-item计算出结果矩阵的一个元素并保存,所以有M*N个对global内存的 store 的操作。

由上图的内存模型可知这种访问并不是最优的,再同一个work-group中我们可以定义local内存,来减少这种操作。

下面摘自国外博客的配图说明一下这次优化的原理:
这里写图片描述

其实就是把之前row*col的方式变成了 多个row和col相乘,究其本质还是对应元素相乘再相加。

这边的中心思想是引入work-group分块计算再相加,work-item的大小还是没变为M*N,不同的是在同一个work-group中把global数组A和B的对应值保存在local内存中,之后每个work-item在这个group中访问这个local变量速度会相对访问global较快,后面的大小为k的循环访问的也是local内存,所以在这个点上是被优化了。先看一下代码实现:


__kernel void hello_kernel(const __global int* A,
                     const __global int* B,
                     __global int* C, int M,  int N,  int K) {

    // Thread identifiers
    const int row = get_local_id(0); // Local row ID (max: TS)
    const int col = get_local_id(1); // Local col ID (max: TS)
    const int globalRow = TS*get_group_id(0) + row; // Row ID of C (0..M)
    const int globalCol = TS*get_group_id(1) + col; // Col ID of C (0..N)



    // Local memory to fit a tile of TS*TS elements of A and B
    __local int Asub[TS][TS];
    __local int Bsub[TS][TS];

    // Initialise the accumulation register
    int acc = 0;

    // Loop over all tiles
    const int numTiles = K/TS;
    for (int t=0; t<numTiles; t++) {

        // Load one tile of A and B into local memory
        const int tiledRow = TS*t + row;
        const int tiledCol = TS*t + col;
        Asub[col][row] = A[tiledCol*M + globalRow];
        Bsub[col][row] = B[globalCol*K + tiledRow];
        printf("Asub[%d][%d]=A[%d]=%d\t",col,row,tiledCol*M + globalRow,A[tiledCol*M + globalRow]);
        // Synchronise to make sure the tile is loaded
        barrier(CLK_LOCAL_MEM_FENCE);

        // Perform the computation for a single tile
        for (int k=0; k<TS; k++) {
            acc += Asub[k][row] * Bsub[col][k];
            //printf("acc[%d][%d]=%d\n",k,row,Asub[k][row]);
        }
        printf("acc = %d\n",acc);
        // Synchronise before loading the next tile
        barrier(CLK_LOCAL_MEM_FENCE);
    }

    // Store the final result in C
    C[globalCol*M + globalRow] = acc;
}

下面具体分析一下这个kernel在运行时的的运行情况:
线看一下cpu端的配置:

#define TS 16
size_t globalWorkSize[2];
    globalWorkSize[0]= heightA;
    globalWorkSize[1]=widthB;
    size_t localWorkSize[2] ;
    localWorkSize[0]= TS;
    localWorkSize[1]= TS;
        errNum = clEnqueueNDRangeKernel(commandQueue, kernel, 2, NULL,
                                        globalWorkSize, localWorkSize,
                                        0, NULL, NULL);

这边新加了localworksize的参数,并且设置大小为16,这里设置大小是有讲究的:
首先TS 必须为2的幂次方
也就是
localWorkSize[0]*localWorkSize[1] <= CL_DEVICE_MAX_WORK_GROUP_SIZE 要怎么知道自己机器的这个size呢?可以通过

size_t      maxWorkItemPerGroup;
    clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE,sizeof(maxWorkItemPerGroup), &maxWorkItemPerGroup, NULL);
    printf("maxWorkItemPerGroup: %zd\n", maxWorkItemPerGroup);

我这边打印的结果是256,也就是说我这边group的size最大只能设置到16.(16*16=256)

接下来看kernel的实现细节:

const int row = get_local_id(0); // Local row ID (max: TS)
const int col = get_local_id(1); // Local col ID (max: TS)

get_local_id 这一组操作主要是获取work-group中当前work-item所在的2d索引。

const int globalRow = TS*get_group_id(0) + row; // Row ID of C (0..M)
const int globalCol = TS*get_group_id(1) + col; // Col ID of C (0..N)

这个是通过当前work-item所在的group-id和自己在此group中的索引计算出,当前work-item在全局的索引。get_group_id是获取当前work-item所在work-group的id。

    __local int Asub[TS][TS];
    __local int Bsub[TS][TS];

定义local 内存,在同一个work-group对所有work-item可

C++ 实战OpenCL环境搭建(一) 前言:接触opencl并行计算变成之前,在我的认知观中,所谓的并行应该就是应用多线程技术达到,比如openMP,openMPI等多线程技术。不过这些都是在cpu上运行,原理都是更好的利用多核处理器的硬件特性,让程序最大程度的利用了多核的优势。 接触opencl之后,认识到了opencl编程技术可以把一些复杂的代码搬运到GPU或其他加速处理器上运行,而gpu又比cpu更适应与计算比如加法,乘法等。第 阅读详情

相关推荐

全网第一个c++版本的opencl编程详解(gpu编程)

本文主要讲解opencl在windows下,使用cpp编程的基本过程,使用的IDE是Visual Studio 2017。 下面的代码连在一起就可以运行 step1:新建工程,导入头文件 在VS上新建一个工程,然后配置相关的头文件:视图->其它窗口->属性管理器,然后配置一些项,目的是将opencl的头文件导入这个cpp工程里面。(这里不介绍了,有很多现成的文章) step2:...

brucexiajun的博客 5496

第9章 并行编程模式实践

为了挖掘硬件的性能,并行算法的实践模式还与具体的硬件有关。 模式的意义在于挖掘算法的相似性,以同样的方式解决类似的问题。 9.1 map模式 map实践模式直观的表述是:对每个数据施加同样的运算。 在应用map模式时,需要注意算法的粒度和硬件的粒度。 算法的粒度是指:某些应用在一种粒度上看是map模式,而在另一种粒度上看却不是map模式。例如对300块不同的数据排序,那么对数据块来说,是map模式。对于块内的每一个数据而言,这又不是map模式,因为对每个数据操作可能并不相同。 硬件的粒度是指:主

weixin_47955824的博客 1074

从零实现C++/OpenCL卷积神经网络:底层原理与异构计算优化实践

卷积神经网络(CNN)作为深度学习的基础模型,其核心原理是通过卷积层、池化层等结构自动提取图像特征。在工程实践中,为了追求极致的性能与部署灵活性,开发者常需深入底层实现。本文以异构计算框架OpenCL为切入点,探讨如何用C++从零构建高效CNN,涵盖内存管理、并行计算调度等关键技术。通过剖析im2col优化、工作组大小调优等实践,揭示如何将复杂数学运算转化为高性能GPU代码,为边缘计算、嵌入式部署等场景提供深度集成方案。项目不仅验证了OpenCL的跨平台通用性,也展示了手动实现反向传播、梯度检验对理解深度学

wjxbj的博客 430

No.7_4 OpenCL 同步——工作项同步

OpenCL 中多个工作项会并行执行,当一个工作项对某个存储空间写入数据后,其它的工作项从该地址读取数据时,该如何保证后面工作项读取的数据是前一个工作项写入的数据。这时,就涉及到了工作项之间的同步。

Bob's Blog 4140

c++ 矩阵 转vector_OpenCL示例02.矩阵乘法

“Python是目前较流行的一种科学计算语言。语法简洁,上手快,易于维护的优点。但其运算速度是真心的慢。那我们能否利用Python的简洁+OpenCL的运算能力呢?答案是可以的,那就是PyOpenCL。”预备知识———矩阵相乘的例子,他是把矩阵分成一个维的4X4数组。计算时,16个数位同时计算,每个数位都进行四次乘法和四次加法的运算。其实我们不难发现矩阵乘法其实就是行列式的点乘计算。在...

weixin_39622980的博客 337

OpenCL面向存储结构优化的研究

OpenCL是一种面向多设备、多平台的编程语言,被多个设备生产厂家支持,具有良好的兼容性。但是,由于不同设备之间有很大的差异,在不同设备上针对OpenCL的调优手段也大不相同。这里主要针对两款GPU,分别是ARM公司的Mali-T860(以下简称为T860)和NVIDIA公司的GeForce GTX 950M(以下简称为950M),使用不同的内存优化策略,评估1024*1024规模的矩阵相乘的性能...

softee的专栏 1734

矩阵相乘:从几何直觉到高性能计算的核心原理与应用

矩阵相乘是线性代数中的核心运算,它本质上是线性变换的复合。从几何视角看,矩阵代表了对空间的变换,而矩阵相乘则能将一系列复杂变换压缩封装成一个统一的变换矩阵,这构成了其强大的技术价值。在计算机图形学中,模型、视图、投影变换通过矩阵相乘合并为MVP矩阵,高效完成3D顶点坐标转换;在机器学习领域,神经网络的全连接层前向传播、推荐系统的协同过滤算法都依赖于矩阵相乘实现高效的加权求和与特征组合。理解其行列规则、结合律等核心性质,并掌握循环重排、分块、SIMD等优化策略,对于实现高性能计算至关重要。本文结合矩阵论、混淆

weixin_34342905的博客 373

opencl JAVA编程_OpenCL编程指南 PDF 下载

相关截图:资料简介:OpenCL领域公认的权威著作,由OpenCL核心设计人员亲自执笔,不仅全面而深刻地解读了OpenCL规范和编程模型,而且通过大量案例和代码演示了基于OpenCL编写并行程序和实现各种并行算法的原理、方法、流程和*实践,以及如何对OpenCL进行性能优化,如何对硬件进行探测和调整。 蒙施、Benedict R.Gaster、TimothyG.Mattson、James Fung...

weixin_31076517的博客 635

CUDA矩阵乘法

从CUDA的实验结果可以看到,并行CUDA程序实现的矩阵乘法相比于串行的串行有了很大的速度提升,而且我们观察到,对于不同阶数的矩阵计算来说,较小的矩阵加速比往往比较大矩阵的加速比要低,这可能是因为计算任务小,GPU资源利用率不高,相较于计算来说,可能内核启动、分配内存所占时间的比重更大。t=N7T8。

张小殊的博客 3056

SYCL作业一:并行矩阵乘法

提高矩阵乘法效率的主要核心是并行计算。矩阵分块乘法将我们最后将要得到的矩阵按照一个给定的大小进行分块,每一个块的结果运算都是独立的,只需要从要相乘的两个矩阵中获取相应数据计算即可得出结果,每个块之间不会相互影响结果,所以可以在GPU上并行计算执行这些矩阵块,从而可以实现并行矩阵乘法,提高矩阵乘法的效率,缩减运行时间。

dxf2003的博客 1213

4B模型本地自动化编码实战:GGUF+llama.cpp+LM Studio全栈指南

轻量级大语言模型(如4B参数级)正成为边缘设备与离线环境实现自动化编码的关键技术基础。其核心原理在于通过GGUF格式实现低内存占用与原生量化支持,结合llama.cpp的CPU/GPU协同推理优化,达成高吞吐、低延迟的代码生成能力。该技术路径显著降低AI编程门槛,具备无需GPU、兼容主流开发工具、支持树莓派等嵌入式平台等工程优势,广泛适用于企业内网开发、硬件原型设计及编程教学等场景。本文聚焦Qwen2.5-4B等典型4B模型在LM Studio平台上的端到端部署与调优实践。

weixin_33862993的博客 819

OpenCL Workshop 1 —— 数字音频滤波

Introduction 这两年深度学习大火,Cuda跟着吃红利,OpenCL发展也很快。虽然OpenCL不是事实上的标准,但是作为开放标准,适应性是很强的,除了显卡之外,CPU/FPGA上都可以执行。 第一个OpenCL Workshop的具体目标就是编写一个音频文件升频工具,用来给PCM编码的WAV文件四倍频,把升频结果写到一个新的WAV文件里面。 用于升频的主要方法,数字滤波,可以广泛...

weixin_34405332的博客 378

C++图像处理实战:从OpenCV环境搭建到性能优化全解析

图像处理作为计算机视觉的基础,其核心在于对像素数据的操作与变换。从原理层面看,图像处理算法通常涉及滤波、阈值分割、形态学操作和边缘检测等经典技术,这些技术通过卷积运算、统计分析和几何变换等方法,实现对图像特征的增强、提取与分析。在工程实践中,选择C++进行图像处理开发,主要源于其对性能的极致追求和对底层硬件的直接控制能力,这对于需要实时处理、高吞吐量的应用场景(如工业视觉检测、嵌入式视觉系统)具有不可替代的技术价值。OpenCV作为跨平台的计算机视觉库,提供了丰富的算法实现和高效的数据结构(如cv::Mat

diandingyin9417的博客 343

ARM 嵌入式端算法加速

OpenCL 嵌入式端算法加速1 嵌入式端算法加速架构1.1.1 平台是否支持简单理解,我们使用 OpenMP 就可以非常简单地实现多线程有多简单呢,查看是否支持 OpenMP在使用 OpenMP 完成多线程任务时,首先得查看当前编译器是否支持 OpenMP,我在 Linux 上的 GCC 编译器是默认支持 OpenMP 的,只需在生成可执行文件的命令中加入 -fopenmp 即可下面的代码也可以查看当前编译器是否支持 OpenMP。

qq_36784503的博客 2126

KCF目标跟踪算法:C++实现与工程优化全解析

目标跟踪是计算机视觉中的基础任务,其核心是在视频序列中持续定位特定目标。相关滤波作为经典跟踪方法,通过将空间域的卷积运算转换到频域,利用快速傅里叶变换实现高效计算,大幅提升了跟踪速度。KCF(核化相关滤波)算法在此基础上引入核技巧,增强了非线性处理能力,使其在保持高速的同时提升了鲁棒性。该技术特别适用于对实时性要求苛刻的嵌入式设备和移动平台。本文聚焦于KCF算法的C++高效实现,深入解析其利用循环矩阵和频域加速的核心原理,并详细探讨特征提取、模型更新等工程实践,为开发高性能跟踪系统提供具体方案。

weixin_30735745的博客 359

C++ AMP: Hello GPU

部分知识摘自一个网站的描述,自己通过程序实现进行了测试,得到了一些感性的认识 C++ AMP是微软提供的一套利用GPU并行计算的API。GPU运算不是新概念,用GPU运算比较有名的已有NVIDIA的CUDA,AMD的stream。同时对于OpenCL这个标准大家也一定没见过也听说(AMP同样与近日放出了开放标准)。 导读:C++ AMP是微软提供的一套利用GPU并行计算的API. GPU运算不是新

Augusdi的专栏 2699

C++ OpenCV实现Richardson-Lucy反卷积算法:从原理到工程优化

图像复原是计算机视觉和数字图像处理中的核心问题,旨在从退化的观测图像中恢复原始清晰图像。其基本原理通常基于退化模型,即模糊图像可建模为清晰图像与点扩散函数的卷积结果。反卷积技术通过逆向求解该模型实现图像复原,具有重要的技术价值,广泛应用于天文成像、显微摄影、医学影像和安防监控等领域。Richardson-Lucy算法作为一种经典的迭代反卷积方法,基于泊松噪声统计的贝叶斯框架,通过最大似然估计逐步逼近最优解。该算法在实现时需重点处理数值稳定性、噪声抑制和计算效率等工程挑战。本文以C++和OpenCV为工具,详

wjxbj的博客 431

llama.cpp源码解析:C语言实现的轻量级大模型推理引擎

大语言模型本地化部署的核心挑战在于算力与内存约束,而量化推理引擎正是突破这一瓶颈的基础技术路径。其原理是通过权重压缩、计算图优化与内存亲和调度,在CPU端实现低延迟、低功耗的token级生成。技术价值体现在跨平台兼容性、确定性执行与嵌入式友好性,广泛应用于边缘AI、机器人自然语言理解、NAS知识库及移动端小模型推理等场景。本文深入剖析llama.cpp这一工业级C实现,聚焦GGUF模型加载、llama_context状态管理、llama_batch任务抽象及ggml量化计算图等关键机制,揭示如何在Windo

anjueci1221的博客 357
上一篇: C++实战之OpenCL矩阵相乘
下一篇: C++ 深入理解系列-构造函数的技巧
哇小明
博客等级 码龄14年 165粉丝 64原创
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值