Metal 学习笔记

使用GPU计算的流程

https://developer.apple.com/documentation/metal/basic_tasks_and_concepts/performing_calculations_on_a_gpu

1. 写一个 C语言的GPU函数

void add_arrays(const float* inA,
                const float* inB,
                float* result,
                int length)
{
    for (int index = 0; index < length ; index++)
    {
        result[index] = inA[index] + inB[index];
    }
}

2. 将C语言函数转化成Metal着色语言(MSL)

kernel void add_arrays(device const float* inA,
                       device const float* inB,
                       device float* result,
                       uint index [[thread_position_in_grid]])
{
    // the for-loop is replaced with a collection of threads, each of which
    // calls this function.
    result[index] = inA[index] + inB[index];
}

3. 找一个GPU设备(MTLDevice)

id<MTLDevice> device = MTLCreateSystemDefaultDevice();

4. 初始化Metal实体们

MetalAdder* adder = [[MetalAdder alloc] initWithDevice:device]; //用它来管理需要需Metal通讯的实体

5. 引用Metal函数

Metal函数在 app 的默认 Metal Library 里,所以使用 MTLDevice 获取 MTLLibrary,然后通过 MTLLibrary 或者MTLFunction(Metal 函数)

- (instancetype) initWithDevice: (id<MTLDevice>) device
{
    self = [super init];
    if (self)
    {
        _mDevice = device;
        
        NSError* error = nil;
        
        // Load the shader files with a .metal file extension in the project

        id<MTLLibrary> defaultLibrary = [_mDevice newDefaultLibrary];
        if (defaultLibrary == nil)
        {
            NSLog(@"Failed to find the default library.");
            return nil;
        }

        id<MTLFunction> addFunction = [defaultLibrary newFunctionWithName:@"add_arrays"];
        if (addFunction == nil)
        {
            NSLog(@"Failed to find the adder function.");
            return nil;
        }
//官网就没有结束大括号,也许是这个方法实际还没结束?

6. 准备Metal管道

Metal函数不是真正的可执行代码,Metal管道将函数转化成实际可执行代码。在Metal中,管道表示为pipeline state object (创建管道的时候编译代码)

_mAddFunctionPSO = [_mDevice newComputePipelineStateWithFunction: addFunction error:&error];

7. 创建命令队列

给GPU发送命令,需要一个命令队列

_mCommandQueue = [_mDevice newCommandQueue];

8. 创建Buffer和数据

Metal使用MTLResource管理内存,使用MTLDevice实例创建内存(实际使用MTLBuffer表示创建的buffer,是MTLResource的子类)

_mBufferA = [_mDevice newBufferWithLength:bufferSize options:MTLResourceStorageModeShared];//MTLResourceStorageModeShared可以让CPU和GPU共享
_mBufferB = [_mDevice newBufferWithLength:bufferSize options:MTLResourceStorageModeShared];
_mBufferResult = [_mDevice newBufferWithLength:bufferSize options:MTLResourceStorageModeShared];

[self generateRandomFloatData:_mBufferA];
[self generateRandomFloatData:_mBufferB];
- (void) generateRandomFloatData: (id<MTLBuffer>) buffer
{
    float* dataPtr = buffer.contents;
    
    for (unsigned long index = 0; index < arrayLength; index++)
    {
        dataPtr[index] = (float)rand()/(float)(RAND_MAX);
    }
}

9.创建Command Buffer

id<MTLCommandBuffer> commandBuffer = [_mCommandQueue commandBuffer];

10.创建命令编码器 Command Encoder

为了将命令写入Command Buffer,需要一个命令解码器来传递具体哪种命令,这里使用计算命令编码器。
它编码出一个计算通路,里面有一列命令,每个计算命令都会导致GPU创建一个矩阵表(grid)来执行

id<MTLComputeCommandEncoder> computeEncoder = [commandBuffer computeCommandEncoder];

为了编码一个命令,可以对编码器调用一系列方法,有一些设置状态信息,比如pipeline state object(PSO),或者传递给管道的参数。当作出这些状态改变后,会编码命令来执行管道。编码器把所有的状态改变和命令参数写入Command Buffer

11.设置Pipeline State和参数数据

先设置管道要执行的Pipeline state object,再设置add_arrays函数需要处理的数据,这里的index和add_arrays的参数位置对应。offset是buffer的偏移量。也可以用同一个buffer,不同偏移量,代表不同参数

[computeEncoder setComputePipelineState:_mAddFunctionPSO];
[computeEncoder setBuffer:_mBufferA offset:0 atIndex:0];
[computeEncoder setBuffer:_mBufferB offset:0 atIndex:1];
[computeEncoder setBuffer:_mBufferResult offset:0 atIndex:2];

12.指定线程数和组织方式

Metal可以处理1D,2D和3D数据,本例是1D数据,所以传datasize * 1 * 1作为参数

MTLSize gridSize = MTLSizeMake(arrayLength, 1, 1);

13.指定线程组大小

Metal把整个数据表分割成小的表,叫做线程组,每个线程组独立运行,分发给不同的GPU处理单元,来加速处理。你需要决定线程组有多大

NSUInteger threadGroupSize = _mAddFunctionPSO.maxTotalThreadsPerThreadgroup;//目前可用的最大的线程数量
if (threadGroupSize > arrayLength)
{
    threadGroupSize = arrayLength;
}
MTLSize threadgroupSize = MTLSizeMake(threadGroupSize, 1, 1);

14.编码 计算命令 并执行线程

[computeEncoder dispatchThreads:gridSize
          threadsPerThreadgroup:threadgroupSize];

编码器可以编码多个命令,而无需多余步骤

15.结束计算通路

[computeEncoder endEncoding];

16.提交Command Buffer来执行命令

[commandBuffer commit];

Metal异步执行这些命令,在执行完以后,command buffer会被标记成已完成

17.等待计算完成

[commandBuffer waitUntilCompleted];

这个方法可以同步等待计算完成,也可以对command buffer添加addCompletedHandler(_:),或者检查status属性来获取完成状态

18. 从Buffer中读取结果

例子是读取结果,然后cpu再算一遍,看看gpu算的对不对

- (void) verifyResults
{
    float* a = _mBufferA.contents;
    float* b = _mBufferB.contents;
    float* result = _mBufferResult.contents;

    for (unsigned long index = 0; index < arrayLength; index++)
    {
        if (result[index] != (a[index] + b[index]))
        {
            printf("Compute ERROR: index=%lu result=%g vs %g=a+b\n",
                   index, result[index], a[index] + b[index]);
            assert(result[index] == (a[index] + b[index]));
        }
    }
    printf("Compute results as expected\n");
}

19. 可算完成了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268