Metal 學(xué)習(xí)筆記

使用GPU計(jì)算的流程

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

1. 寫(xiě)一個(gè) C語(yǔ)言的GPU函數(shù)

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語(yǔ)言函數(shù)轉(zhuǎn)化成Metal著色語(yǔ)言(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. 找一個(gè)GPU設(shè)備(MTLDevice)

id<MTLDevice> device = MTLCreateSystemDefaultDevice();

4. 初始化Metal實(shí)體們

MetalAdder* adder = [[MetalAdder alloc] initWithDevice:device]; //用它來(lái)管理需要需Metal通訊的實(shí)體

5. 引用Metal函數(shù)

Metal函數(shù)在 app 的默認(rèn) Metal Library 里涡扼,所以使用 MTLDevice 獲取 MTLLibrary送巡,然后通過(guò) MTLLibrary 或者M(jìn)TLFunction(Metal 函數(shù))

- (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;
        }
//官網(wǎng)就沒(méi)有結(jié)束大括號(hào)湿痢,也許是這個(gè)方法實(shí)際還沒(méi)結(jié)束偎行?

6. 準(zhǔn)備Metal管道

Metal函數(shù)不是真正的可執(zhí)行代碼腔剂,Metal管道將函數(shù)轉(zhuǎn)化成實(shí)際可執(zhí)行代碼辕羽。在Metal中惹挟,管道表示為pipeline state object (創(chuàng)建管道的時(shí)候編譯代碼)

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

7. 創(chuàng)建命令隊(duì)列

給GPU發(fā)送命令拆魏,需要一個(gè)命令隊(duì)列

_mCommandQueue = [_mDevice newCommandQueue];

8. 創(chuàng)建Buffer和數(shù)據(jù)

Metal使用MTLResource管理內(nèi)存熟妓,使用MTLDevice實(shí)例創(chuàng)建內(nèi)存(實(shí)際使用MTLBuffer表示創(chuàng)建的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.創(chuàng)建Command Buffer

id<MTLCommandBuffer> commandBuffer = [_mCommandQueue commandBuffer];

10.創(chuàng)建命令編碼器 Command Encoder

為了將命令寫(xiě)入Command Buffer,需要一個(gè)命令解碼器來(lái)傳遞具體哪種命令起愈,這里使用計(jì)算命令編碼器只恨。
它編碼出一個(gè)計(jì)算通路,里面有一列命令抬虽,每個(gè)計(jì)算命令都會(huì)導(dǎo)致GPU創(chuàng)建一個(gè)矩陣表(grid)來(lái)執(zhí)行

id<MTLComputeCommandEncoder> computeEncoder = [commandBuffer computeCommandEncoder];

為了編碼一個(gè)命令官觅,可以對(duì)編碼器調(diào)用一系列方法,有一些設(shè)置狀態(tài)信息阐污,比如pipeline state object(PSO)休涤,或者傳遞給管道的參數(shù)。當(dāng)作出這些狀態(tài)改變后笛辟,會(huì)編碼命令來(lái)執(zhí)行管道功氨。編碼器把所有的狀態(tài)改變和命令參數(shù)寫(xiě)入Command Buffer

11.設(shè)置Pipeline State和參數(shù)數(shù)據(jù)

先設(shè)置管道要執(zhí)行的Pipeline state object,再設(shè)置add_arrays函數(shù)需要處理的數(shù)據(jù)手幢,這里的index和add_arrays的參數(shù)位置對(duì)應(yīng)捷凄。offset是buffer的偏移量。也可以用同一個(gè)buffer围来,不同偏移量跺涤,代表不同參數(shù)

[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.指定線程數(shù)和組織方式

Metal可以處理1D,2D和3D數(shù)據(jù)监透,本例是1D數(shù)據(jù)桶错,所以傳datasize * 1 * 1作為參數(shù)

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

13.指定線程組大小

Metal把整個(gè)數(shù)據(jù)表分割成小的表,叫做線程組才漆,每個(gè)線程組獨(dú)立運(yùn)行牛曹,分發(fā)給不同的GPU處理單元,來(lái)加速處理醇滥。你需要決定線程組有多大

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

14.編碼 計(jì)算命令 并執(zhí)行線程

[computeEncoder dispatchThreads:gridSize
          threadsPerThreadgroup:threadgroupSize];

編碼器可以編碼多個(gè)命令黎比,而無(wú)需多余步驟

15.結(jié)束計(jì)算通路

[computeEncoder endEncoding];

16.提交Command Buffer來(lái)執(zhí)行命令

[commandBuffer commit];

Metal異步執(zhí)行這些命令超营,在執(zhí)行完以后,command buffer會(huì)被標(biāo)記成已完成

17.等待計(jì)算完成

[commandBuffer waitUntilCompleted];

這個(gè)方法可以同步等待計(jì)算完成阅虫,也可以對(duì)command buffer添加addCompletedHandler(_:)演闭,或者檢查status屬性來(lái)獲取完成狀態(tài)

18. 從Buffer中讀取結(jié)果

例子是讀取結(jié)果,然后cpu再算一遍颓帝,看看gpu算的對(duì)不對(duì)

- (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. 可算完成了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末米碰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子购城,更是在濱河造成了極大的恐慌吕座,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘪板,死亡現(xiàn)場(chǎng)離奇詭異吴趴,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)侮攀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門锣枝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人兰英,你說(shuō)我怎么就攤上這事撇叁。” “怎么了畦贸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵陨闹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我家制,道長(zhǎng)正林,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任颤殴,我火速辦了婚禮觅廓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涵但。我一直安慰自己杈绸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布矮瘟。 她就那樣靜靜地躺著瞳脓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪澈侠。 梳的紋絲不亂的頭發(fā)上劫侧,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼烧栋。 笑死写妥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的审姓。 我是一名探鬼主播珍特,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼魔吐!你這毒婦竟也來(lái)了扎筒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤酬姆,失蹤者是張志新(化名)和其女友劉穎嗜桌,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體辞色,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡症脂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了淫僻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壶唤,死狀恐怖雳灵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闸盔,我是刑警寧澤悯辙,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站迎吵,受9級(jí)特大地震影響躲撰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜击费,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一拢蛋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蔫巩,春花似錦谆棱、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至坪郭,卻和暖如春个从,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工嗦锐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嫌松,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓意推,卻偏偏與公主長(zhǎng)得像豆瘫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子菊值,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345