通过OpenCL内核代码猜测设备寄存器个数

news/2024/6/16 21:27:12 标签: linux, c++

在OpenCL标准中,没有给出查看计算设备一共有多少寄存器,至少能分配给每个work-item多少寄存器使用的特征查询。而由于一个段内核代码是否因寄存器紧缺而导致性能严重下降也是一个比较重要的因素,因此我这边提供一个比较基本的方法来猜测当前计算设备至少能为每个work-item分配多少可用的寄存器。

这个方法的思路是,先定义四个临时变量,然后在一个大规模循环里面做一定规模的计算。然后把时间统计出来。随后,再定义八个临时变量,仍然,在与前者相同次数的循环里做一定规模的计算,再把时间统计出来。一般,如果寄存器不爆,或者由于Cache的缘故,性能影响不大的话,两者消耗时间一般在2倍左右。如果后者比前者超了2.2倍以上,那么我们即可认为寄存器爆了~

这个方法对于一般的GPU更有用些。由于CPU往往拥有L1 Data Cache,当寄存器不够用的时候,编译器会将不太常用的数据放到栈中,而栈在此时往往能获得高命中率的Cache访问,因此性能不会过受影响。而GPU端当寄存器不够用时,编译器往往会采取将不常用数据直接存放到VRAM中,而对外部VRAM的访问往往是比较慢的,因此,如果临时变量太多,使得频繁访问外部存储器,会使得整体计算性能大幅下降。当然,现在不少GPU也有了L1 Cache,但是空间也十分有限。因此,这里用“猜”这个词~🙂

下面先提供四个临时变量的kernel代码:

kernel void QueryRegisterCount(__global int *pInOut)
{
    int index = get_global_id(0);

    int i0 = pInOut[(index * 4 + 0) * 4];
    int i1 = pInOut[(index * 4 + 1) * 4];
    int i2 = pInOut[(index * 4 + 2) * 4];
    int i3 = pInOut[(index * 4 + 3) * 4];
    
    for(int i = 0; i < 100000; i++)
    {
        i1 += i0 << 1;
        i2 += i1 << 1;
        i3 += i2 << 1;
        i0 += i3 << 1;
        
        i1 += i0 >> 1;
        i2 += i1 >> 1;
        i3 += i2 >> 1;
        i0 += i3 >> 1;
        
        i1 += i0 >> 2;
        i2 += i1 >> 2;
        i3 += i2 >> 2;
        i0 += i3 >> 2;
        
        i1 += i0 >> 3;
        i2 += i1 >> 3;
        i3 += i2 >> 3;
        i0 += i3 >> 3;
    }
    
    pInOut[(index * 4 + 0) * 4] = i0;
    pInOut[(index * 4 + 1) * 4] = i1;
    pInOut[(index * 4 + 2) * 4] = i2;
    pInOut[(index * 4 + 3) * 4] = i3;
}

再提供八个临时变量的kernel代码:

kernel void QueryRegisterCount(__global int *pInOut)
{
    int index = get_global_id(0);

    int i0 = pInOut[(index * 8 + 0) * 4];
    int i1 = pInOut[(index * 8 + 1) * 4];
    int i2 = pInOut[(index * 8 + 2) * 4];
    int i3 = pInOut[(index * 8 + 3) * 4];
    int i4 = pInOut[(index * 8 + 4) * 4];
    int i5 = pInOut[(index * 8 + 5) * 4];
    int i6 = pInOut[(index * 8 + 6) * 4];
    int i7 = pInOut[(index * 8 + 7) * 4];
    
    for(int i = 0; i < 100000; i++)
    {
        i1 += i0 << 1;
        i2 += i1 << 1;
        i3 += i2 << 1;
        i4 += i3 << 1;
        i5 += i4 << 1;
        i6 += i5 << 1;
        i7 += i6 << 1;
        i0 += i7 << 1;
        
        i1 += i0 >> 1;
        i2 += i1 >> 1;
        i3 += i2 >> 1;
        i4 += i3 >> 1;
        i5 += i4 >> 1;
        i6 += i5 >> 1;
        i7 += i6 >> 1;
        i0 += i7 >> 1;
        
        i1 += i0 >> 2;
        i2 += i1 >> 2;
        i3 += i2 >> 2;
        i4 += i3 >> 2;
        i5 += i4 >> 2;
        i6 += i5 >> 2;
        i7 += i6 >> 2;
        i0 += i7 >> 2;
        
        i1 += i0 >> 3;
        i2 += i1 >> 3;
        i3 += i2 >> 3;
        i4 += i3 >> 3;
        i5 += i4 >> 3;
        i6 += i5 >> 3;
        i7 += i6 >> 3;
        i0 += i7 >> 3;
    }
    
    pInOut[(index * 8 + 0) * 4] = i0;
    pInOut[(index * 8 + 1) * 4] = i1;
    pInOut[(index * 8 + 2) * 4] = i2;
    pInOut[(index * 8 + 3) * 4] = i3;
    pInOut[(index * 8 + 4) * 4] = i4;
    pInOut[(index * 8 + 5) * 4] = i5;
    pInOut[(index * 8 + 6) * 4] = i6;
    pInOut[(index * 8 + 7) * 4] = i7;
}

而像16个、32个临时变量的方法依此类推~

然后,给出主机端代码:

/** Prepare for running an OpenCL kernel program to get register count */
    
    /*Step 4: Creating command queue associate with the context.*/
    commandQueue = clCreateCommandQueue(context, device, CL_QUEUE_PROFILING_ENABLE, NULL);
    
    /*Step 5: Create program object */
    // Read the kernel code to the buffer
    kernelPath = [[NSBundle mainBundle] pathForResource:@"reg" ofType:@"ocl"];
    aSource = [[NSString stringWithContentsOfFile:kernelPath encoding:NSUTF8StringEncoding error:nil] UTF8String];
    kernelLength = strlen(aSource);
    program = clCreateProgramWithSource(context, 1, &aSource, &kernelLength, NULL);
    
    /*Step 6: Build program. */
    status = clBuildProgram(program, 1, &device, NULL, NULL, NULL);
    
    /*Step 7: Initial inputs and output for the host and create memory objects for the kernel*/
    const size_t memSize = global_work_size[0] * 1024 * 4 * 4;
    cl_int *orgBufer = (cl_int*)malloc(memSize);
    memset(orgBufer, 1, memSize);
    outputMemObj = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, memSize, orgBufer, NULL);
    
    /*Step 8: Create kernel object */
    kernel = clCreateKernel(program, "QueryRegisterCount", NULL);
    
    /*Step 9: Sets Kernel arguments.*/
    status |= clSetKernelArg(kernel, 0, sizeof(outputMemObj), &outputMemObj);
    
    /*Step 10: Running the kernel.*/
    for(int i = 0; i < 5; i++)
    {
        NSTimeInterval beginTime = [[NSProcessInfo processInfo] systemUptime];
        status |= clEnqueueNDRangeKernel(commandQueue, kernel, 1, NULL, global_work_size, local_work_size, 0, NULL, NULL);
        clFinish(commandQueue);
        NSTimeInterval endTime = [[NSProcessInfo processInfo] systemUptime];
        
        NSLog(@"Time spent: %f", endTime - beginTime);
    }
    
    free(orgBufer);

    if(status != CL_SUCCESS)
    {
        NSLog(@"Program built failed!");
        return;
    }
    
    clReleaseMemObject(outputMemObj);
    clReleaseProgram(program);
    clReleaseKernel(kernel);
    clReleaseCommandQueue(commandQueue);
    
    clReleaseContext(context);

以上由于是在macOS下开发的,因此直接用Objective-C文件读写更方便些。但是大部分都是C代码,很容易读懂。

其中,最后一断代码中,我们做5次循环,统计时间。我们比较的时候往往选出5次执行时间中最小耗费的时间进行比较。

在2013年的MacBook Air中的Intel HD 5000中的测试结果为:

  • 四个临时变量耗费:0.061020秒
  • 八个临时变量耗费:0.121868秒
  • 十六个临时变量耗费:0.243470秒
  • 三十二个临时变量耗费:0.719506秒

很显然,我们可以猜得,Intel HD Graphics 5000至少可以为每个work-item分配16个寄存器。

我们如果要用在实际应用场合,可以通过动态生成kernel字符串依次执行进行检测,直到相邻两段kernel的执行时间超过2.2倍,那么我们即可终止。


http://www.niftyadmin.cn/n/304073.html

相关文章

C++学习记录——이십일 AVL树

文章目录 1、了解AVL树2、模拟实现3、旋转1、左单旋2、右单旋3、双旋&#xff08;先左后右&#xff09;4、双旋&#xff08;先右后左&#xff09; 4、检查平衡5、测试性能&#xff08;随机数&#xff09;6、删除 1、了解AVL树 如果数据有序或接近有序&#xff0c;二叉搜索树将…

Word三线表创建

三线表是论文写作中经常使用到的表格格式 自定义三线表 “插入”-->“表格”&#xff0c;随便插入一个表格&#xff0c;然后将光标移动到表格内 “表设计”-->“其他”-->“新建表格样式” 修改模板名称为“三线表”&#xff0c;方便下次直接套用 首先设置标题行【…

大势智慧软硬件技术答疑第一期

1.重建大师生成的实景三维模型&#xff0c;模型周边的道路植物被压平了&#xff0c;怎么保留植物道路原有形状&#xff1f; 答&#xff1a;这个是单体化生成的地理实体场景&#xff0c;会把地物压平&#xff0c;可以用模方将单体化的模型加入到osgb中。 2.直接导入空三的话这个…

存储迁移到vSAN后将oracle rac的共享虚拟磁盘由“精简置备”转换为“厚置备快速置零”格式

在vSAN 6.5.0环境中&#xff0c;将Oracle RAC虚拟机的存储迁移到vSAN数据存储后&#xff0c;共享磁盘在迁移后全部变成了“精简置备”类型&#xff0c;如下所示&#xff1a; 注&#xff1a;从vSAN 6.7 Patch 01开始&#xff0c;vSAN上的Oracle RAC 不再要求共享的 VMDKs为厚置…

使用PyTorch构建神经网络,并使用thop计算参数和FLOPs

文章目录 使用PyTorch构建神经网络&#xff0c;并使用thop计算参数和FLOPsFLOPs和FLOPS区别使用PyTorch搭建神经网络整体代码1. 导入必要的库2. 定义神经网络模型3. 打印网络结构4. 计算网络FLOPs和参数数量5. 结果如下手动计算params手动计算FLOPs注意 使用PyTorch构建神经网络…

MySQL笔记之文件和日志

一、存储文件 1、存放位置 MySQL数据库会在data目录下&#xff0c;以数据库为名&#xff0c;为每一个数据库建立文件夹&#xff0c;用来存储数据库中的表文件数据。 不同的数据库引擎&#xff0c;每个表的扩展名也不一样 &#xff0c;例如&#xff1a; MyISAM用“.MYD”作为…

把苹果全家桶用于VR全身追踪是什么体验

此前&#xff0c;青亭网曾报道了一项无需摄像头的VR全身追踪方案&#xff1a;Standable: Full Body Estimation&#xff08;简称SFBE&#xff09;&#xff0c;这套方案就是利用了头显双手柄数据来模拟预测全身动作数据&#xff0c;效果还算不错。 近期在CHI2023活动上&#xff…

【数据结构与算法】二分查找

【数据结构与算法】二分查找&#xff08;力扣704、35、34题解&#xff09; 文章目录 【数据结构与算法】二分查找&#xff08;力扣704、35、34题解&#xff09;写在前面一、 相关概念二、 二分查找框架三、力扣相关题3.1 [二分查找(704)](https://leetcode.cn/problems/binary-…