日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Intel TBB 开发指南 3 parallel_reduce

發(fā)布時(shí)間:2024/1/8 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Intel TBB 开发指南 3 parallel_reduce 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文

循環(huán)可以進(jìn)行歸約,如以下求和:

float SerialSumFoo( float a[], size_t n ) {float sum = 0;for( size_t i=0; i!=n; ++i )sum += Foo(a[i]);return sum; }

如果迭代是獨(dú)立的,你可以使用模板類 parallel_reduce 并行化此循環(huán),如下所示:

float ParallelSumFoo( const float a[], size_t n ) {SumFoo sf(a);parallel_reduce( blocked_range<size_t>(0,n), sf );return sf.my_sum; }

SumFoo 類指定了歸約的細(xì)節(jié),例如如何累加和組合它們。 這是類 SumFoo 的定義:

class SumFoo {float* my_a; public:float my_sum;void operator()( const blocked_range<size_t>& r ) {float *a = my_a;float sum = my_sum;size_t end = r.end();for( size_t i=r.begin(); i!=end; ++i )sum += Foo(a[i]);my_sum = sum;}SumFoo( SumFoo& x, split ) : my_a(x.my_a), my_sum(0) {}void join( const SumFoo& y ) {my_sum+=y.my_sum;}SumFoo(float a[] ) :my_a(a), my_sum(0){} };

請(qǐng)注意與來(lái)自 parallel_for 的類 ApplyFoo 的區(qū)別。 首先,operator() 不是 const。 這是因?yàn)樗仨毟?SumFoo::my_sum。 其次,SumFoo 有一個(gè)拆分構(gòu)造函數(shù)和一個(gè)方法 join,必須存在才能使 parallel_reduce 工作。 拆分構(gòu)造函數(shù)將原始對(duì)象的引用和類型為 split 的偽參數(shù)作為參數(shù),該參數(shù)由庫(kù)定義。 虛擬參數(shù)將拆分構(gòu)造函數(shù)與復(fù)制構(gòu)造函數(shù)區(qū)分開(kāi)來(lái)。

在示例中,operator() 的定義將局部臨時(shí)變量(a、sum、end)用于循環(huán)內(nèi)訪問(wèn)的標(biāo)量值。 這種技術(shù)可以通過(guò)讓編譯器清楚地知道這些值可以保存在寄存器而不是內(nèi)存中來(lái)提高性能。 如果這些值太大而無(wú)法放入寄存器中,或者它們的地址以編譯器無(wú)法跟蹤的方式獲取,則該技術(shù)可能無(wú)濟(jì)于事。 對(duì)于典型的優(yōu)化編譯器,僅對(duì)寫入的變量(例如示例中的 sum)使用局部臨時(shí)變量就足夠了,因?yàn)檫@樣編譯器就可以推斷出循環(huán)不會(huì)寫入任何其他位置,并將其他讀取提升到外部 循環(huán)。

當(dāng)工作線程可用時(shí),由任務(wù)調(diào)度程序決定,parallel_reduce 調(diào)用拆分構(gòu)造函數(shù)為工作線程創(chuàng)建子任務(wù)。 當(dāng)子任務(wù)完成時(shí),parallel_reduce 使用方法 join 來(lái)累積子任務(wù)的結(jié)果。 下圖頂部的圖形顯示了當(dāng)工作程序可用時(shí)發(fā)生的拆分連接序列:


上圖中的箭頭表示時(shí)間順序。 拆分構(gòu)造函數(shù)可能會(huì)在對(duì)象 x 用于縮減的前半部分時(shí)并發(fā)運(yùn)行。 因此,創(chuàng)建 y 的拆分構(gòu)造函數(shù)的所有操作都必須相對(duì)于 x 成為線程安全的。 因此,如果拆分構(gòu)造函數(shù)需要遞增與其他對(duì)象共享的引用計(jì)數(shù),則應(yīng)使用原子遞增。
如果 worker 不可用,則使用減少前半部分的相同主體對(duì)象減少迭代的后半部分。 那就是下半場(chǎng)的減持開(kāi)始,上半場(chǎng)的減持結(jié)束。

由于如果 worker 不可用,則不使用 split/join,parallel_reduce 不一定會(huì)進(jìn)行遞歸拆分。
由于同一個(gè)主體可能用于累積多個(gè)子范圍,因此 operator() 不丟棄較早的累積是至關(guān)重要的。 下面的代碼顯示了 SumFoo::operator() 的錯(cuò)誤定義。

class SumFoo {... public:float my_sum;void operator()( const blocked_range<size_t>& r ) {...float sum = 0; // WRONG – should be 'sum = my_sum"....for( ... )sum += Foo(a[i]);my_sum = sum;}... };

由于錯(cuò)誤,主體返回最后一個(gè)子范圍的部分和,而不是 parallel_reduce 應(yīng)用它的所有子范圍。
parallel_reduce 的分區(qū)器和粒度規(guī)則與 parallel_for 相同。
parallel_reduce 推廣到任何關(guān)聯(lián)操作。 通常,拆分構(gòu)造函數(shù)會(huì)做兩件事:

  • 復(fù)制運(yùn)行循環(huán)體所需的只讀信息。
  • 將歸約變量初始化為操作的標(biāo)識(shí)元素。
    join 方法應(yīng)該進(jìn)行相應(yīng)的合并。 你可以同時(shí)進(jìn)行多次縮減:可以使用單個(gè) parallel_reduce 收集最小值和最大值。

歸約運(yùn)算可以是非交換的。 如果浮點(diǎn)加法被字符串連接替換,該示例仍然有效。

Advanced Example

更高級(jí)的關(guān)聯(lián)操作的一個(gè)例子是找到 Foo(i) 最小化的索引。 串行版本可能如下所示:

long SerialMinIndexFoo( const float a[], size_t n ) {float value_of_min = FLT_MAX; // FLT_MAX from <climits>long index_of_min = -1;for( size_t i=0; i<n; ++i ) {float value = Foo(a[i]);if( value<value_of_min ) {value_of_min = value;index_of_min = i;}}return index_of_min; }

該循環(huán)記錄迄今為止找到的最小值以及該值的索引。 這是循環(huán)迭代之間攜帶的唯一信息。 要將循環(huán)轉(zhuǎn)換為使用 parallel_reduce,函數(shù)對(duì)象必須跟蹤攜帶的信息,以及當(dāng)?shù)植荚诙鄠€(gè)線程上時(shí)如何合并這些信息。 此外,函數(shù)對(duì)象必須記錄指向 a 的指針以提供上下文。
以下代碼顯示了完整的函數(shù)對(duì)象。

class MinIndexFoo {const float *const my_a; public:float value_of_min;long index_of_min;void operator()( const blocked_range<size_t>& r ) {const float *a = my_a;for( size_t i=r.begin(); i!=r.end(); ++i ) {float value = Foo(a[i]);if( value<value_of_min ) {value_of_min = value;index_of_min = i;}}}MinIndexFoo( MinIndexFoo& x, split ) :my_a(x.my_a),value_of_min(FLT_MAX), // FLT_MAX from <climits>index_of_min(-1){}void join( const SumFoo& y ) {if( y.value_of_min<value_of_min ) {value_of_min = y.value_of_min;index_of_min = y.index_of_min;}}MinIndexFoo( const float a[] ) :my_a(a),value_of_min(FLT_MAX), // FLT_MAX from <climits>index_of_min(-1),{} };

現(xiàn)在 SerialMinIndex 可以使用 parallel_reduce 重寫,如下所示:

long ParallelMinIndexFoo( float a[], size_t n ) {MinIndexFoo mif(a);parallel_reduce(blocked_range<size_t>(0,n), mif );return mif.index_of_min; }

Advanced Topic: Other Kinds of Iteration Spaces

到目前為止的示例都使用了類 blocks_range<T> 來(lái)指定范圍。 這個(gè)類在許多情況下都很有用,但它并不適合所有情況。 你可以使用 oneTBB 來(lái)定義自己的迭代空間對(duì)象。 該對(duì)象必須通過(guò)提供一個(gè)基本的拆分構(gòu)造函數(shù)、一個(gè)可選的比例拆分構(gòu)造函數(shù)(伴隨著啟用其使用的特征值)和兩個(gè)謂詞方法來(lái)指定如何將其拆分為子空間。 如果你的類稱為 R,則方法和構(gòu)造函數(shù)應(yīng)如下所示:

class R {// True if range is emptybool empty() const;// True if range can be split into non-empty subrangesbool is_divisible() const;// Splits r into subranges r and *thisR( R& r, split );// Splits r into subranges r and *this in proportion pR( R& r, proportional_split p );// Allows usage of proportional splitting constructorstatic const bool is_splittable_in_proportion = true;... };

如果范圍為空,方法 empty 應(yīng)該返回 true。如果范圍可以拆分為兩個(gè)非空子空間,則方法 is_divisible 應(yīng)該返回 true,這樣的拆分值得開(kāi)銷。基本的拆分構(gòu)造函數(shù)應(yīng)該有兩個(gè)參數(shù):

  • 第一個(gè) R 類型
  • 第二個(gè) oneapi::tbb::split 類型

第二個(gè)參數(shù)沒(méi)有被實(shí)際使用;它僅用于將構(gòu)造函數(shù)與普通的復(fù)制構(gòu)造函數(shù)區(qū)分開(kāi)來(lái)。基本的拆分構(gòu)造函數(shù)應(yīng)該嘗試將 r 大致拆分為兩半,并將 r 更新為前半部分,并將構(gòu)造的對(duì)象設(shè)置為后半部分。
與基本拆分構(gòu)造函數(shù)不同,比例拆分構(gòu)造函數(shù)是可選的,并采用 oneapi::tbb::proportional_split 類型的第二個(gè)參數(shù)。該類型具有返回比例值的 left 和 right 方法。應(yīng)該使用這些值來(lái)相應(yīng)地拆分 r,使更新后的 r 對(duì)應(yīng)于比例的左側(cè)部分,而構(gòu)造的對(duì)象對(duì)應(yīng)于右側(cè)部分。只有在類中定義了靜態(tài)常量 is_splittable_in_proportion 并賦值為 true 時(shí),才會(huì)使用比例拆分構(gòu)造函數(shù)。
兩個(gè)拆分構(gòu)造函數(shù)都應(yīng)該保證更新后的 r 部分和構(gòu)造的對(duì)象不為空。只有當(dāng) r.is_divisible 為真時(shí),并行算法模板才會(huì)調(diào)用 r 上的拆分構(gòu)造函數(shù)。
迭代空間不必是線性的。查看 oneapi/tbb/blocked_range2d.h 以獲得二維范圍的示例。它的拆分構(gòu)造函數(shù)嘗試沿其最長(zhǎng)軸拆分范圍。當(dāng)與parallel_for 一起使用時(shí),它會(huì)導(dǎo)致循環(huán)以提高緩存使用率的方式“遞歸阻塞”。這種良好的緩存行為意味著,在blocked_range2d<T> 上使用parallel_for 可以使循環(huán)比順序等效的循環(huán)運(yùn)行得更快,即使在單個(gè)處理器上也是如此。

總結(jié)

以上是生活随笔為你收集整理的Intel TBB 开发指南 3 parallel_reduce的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。