目前,我編程的k-均值++使用OpenMP和C.並行版本直到現在,我實施質心的初始化。如果您對此過程不熟悉,則其工作原理與follows大致相同。給定一個dataset
(基體)與n
點,k
質心是使用「概率函數」,也被稱爲輪盤賭選擇initilized。OpenMP程序(k均值++)不結垢
假設你有n=4
點,距離下面的數組一些重心:
distances = [2, 4, 6, 8]
dist_sum = 20
從這些,除以distances
每個條目由dist_sum
,並添加以前的結果,就像定義一個累積概率數組這:
probs = [0.1, 0.2, 0.3, 0.4] = [2/20, 4/20, 6/20, 8/20]
acc_probs = [0.1, 0.3, 0.6, 1.0]
然後,執行輪盤賭選擇。給定一個隨機數,說r=0.5
,選擇使用r
和acc_probs
,下一個點迭代acc_probs
直到r < acc_probs[i]
。在這個例子中,所選擇的點是因爲i=2
r < acc_probs[2]
。
問題 在這種情況下,我非常大的矩陣(約點n=16 000 000
)工作。儘管該程序給出了正確的答案(即重心的良好初始化),但它並沒有如預期的那樣成比例。該函數按照此算法計算初始質心。
double **parallel_init_centroids (double **dataset, int n, int d, int k, RngStream randomizer, long int *total_ops) {
double dist=0, error=0, dist_sum=0, r=0, partial_sum=0, mindist=0;
int cn=0, cd=0, ck = 0, cck = 0, idx = 0;
ck = 0;
double probs_sum = 0; // debug
int mink=0, id=0, cp=0;
for (ck = 0; ck < k; ck++) {
if (ck == 0) {
// 1. choose an initial centroid c_0 from dataset randomly
idx = RngStream_RandInt (randomizer, 0, n-1);
}
else {
// 2. choose a successive centroid c_{ck} using roulette selection
r = RngStream_RandU01 (randomizer);
idx = 0;
partial_sum = 0;
for (cn=0; cn<n; cn++) {
partial_sum = partial_sum + distances[cn]/dist_sum;
if (r < partial_sum) {
idx = cn;
break;
}
}
}
// 3. copy centroid from dataset
for (cd=0; cd<d; cd++)
centroids[ck][cd] = dataset[idx][cd];
// reset before parallel region
dist_sum = 0;
// -- parallel region --
# pragma omp parallel shared(distances, clusters, centroids, dataset, chunk, dist_sum_threads, total_ops_threads) private(id, cn, cck, cd, cp, error, dist, mindist, mink)
{
id = omp_get_thread_num();
dist_sum_threads[id] = 0; // each thread reset its entry
// parallel loop
// 4. recompute distances against centroids
# pragma omp for schedule(static,chunk)
for (cn=0; cn<n; cn++) {
mindist = DMAX;
mink = 0;
for (cck=0; cck<=ck; cck++) {
dist = 0;
for (cd=0; cd<d; cd++) {
error = dataset[cn][cd] - centroids[ck][cd];
dist = dist + (error * error); total_ops_threads[id]++;
}
if (dist < mindist) {
mindist = dist;
mink = ck;
}
}
distances[cn] = mindist;
clusters[cn] = mink;
dist_sum_threads[id] += mindist; // each thread contributes before reduction
}
}
// -- parallel region --
// 5. sequential reduction
dist_sum = 0;
for (cp=0; cp<p; cp++)
dist_sum += dist_sum_threads[cp];
}
// stats
*(total_ops) = 0;
for (cp=0; cp<p; cp++)
*(total_ops) += total_ops_threads[cp];
// free it later
return centroids;
}
正如你所看到的,並行區域計算中n
d
維點對k
d
維質心的距離。這項工作在p
線程(最多32個)之間共享。並行區域結束後,兩個陣列被填充:distances
和dist_sum_threads
。第一個數組與前一個示例相同,而第二個數組包含每個線程收集的累積距離。考慮前面的例子,如果p=2
線程是可用的,那麼該陣列被定義如下:
dist_sum_threads[0] = 6 ([2, 4]) # filled by thread 0
dist_sum_threads[1] = 14 ([6, 8]) # filled by thread 1
dist_sum
通過加入的dist_sum_threads
每個條目定義。該函數按預期工作,但是當線程數量增加時,執行時間會增加。這figure顯示了一些性能指標。
出了什麼問題我的執行,特別是使用OpenMP?綜上所述,採用了只有兩個雜注:
# pragma omp parallel ...
{
get thread id
# pragma omp for schedule(static,chunk)
{
compute distances ...
}
fill distances and dist_sum_threads[id]
}
換句話說,我消除了障礙,互斥訪問,以及其他的東西,可能會導致額外的開銷。但是,執行時間隨着線程數量的增加而變得最差。
更新
- 上面的代碼已經被更改爲mcve。 This snippet與我以前的代碼類似。在這種情況下,計算
n=100000
點和k=16
質心之間的距離。 - 執行時間在並行區域之前和之後使用
omp_get_wtime
進行測量。總時間存儲在wtime_spent
中。 - 我包含一個減少計算
dist_sum
。然而,它並不像預期的那樣工作(它在下面被評論爲不好的並行減少)。dist_sum
的正確值是999857108020.0
,但是當使用p
線程來計算它時,結果爲999857108020.0 * p
,這是錯誤的。 - 性能情節updated
這是主要的並行功能,完整的代碼位於here:
double **parallel_compute_distances (double **dataset, int n, int d, int k, long int *total_ops) { double dist=0, error=0, mindist=0; int cn, cd, ck, mink, id, cp; // reset before parallel region dist_sum = 0; // -- start time -- wtime_start = omp_get_wtime(); // parallel loop # pragma omp parallel shared(distances, clusters, centroids, dataset, chunk, dist_sum, dist_sum_threads) private(id, cn, ck, cd, cp, error, dist, mindist, mink) { id = omp_get_thread_num(); dist_sum_threads[id] = 0; // reset // 2. recompute distances against centroids # pragma omp for schedule(static,chunk) for (cn=0; cn<n; cn++) { mindist = DMAX; mink = 0; for (ck=0; ck<k; ck++) { dist = 0; for (cd=0; cd<d; cd++) { error = dataset[cn][cd] - centroids[ck][cd]; dist = dist + (error * error); total_ops_threads[id]++; } if (dist < mindist) { mindist = dist; mink = ck; } } distances[cn] = mindist; clusters[cn] = mink; dist_sum_threads[id] += mindist; } // bad parallel reduction //#pragma omp parallel for reduction(+:dist_sum) //for (cp=0; cp<p; cp++){ // dist_sum += dist_sum_threads[cp]; //} } // -- end time -- wtime_end = omp_get_wtime(); // -- total wall time -- wtime_spent = wtime_end - wtime_start; // sequential reduction for (cp=0; cp<p; cp++) dist_sum += dist_sum_threads[cp]; // stats *(total_ops) = 0; for (cp=0; cp<p; cp++) *(total_ops) += total_ops_threads[cp]; return centroids; }
「即使在Xeon Phi KNC上,我也可以從1到60個線程獲得接近線性的速度(然後使用更多的硬件線程仍然可以提高速度,但不會達到相同的程度)。」 KNC有60個核心,但可以運行4個線程/核心。因此(假設你正在使用分散關係),前60個線程每個都有一個完整的內核,但是60個以上的線程會在同一個內核上有多個HW線程。因此,您不應該期望獲得相同的性能。最好在x和單獨的線上用1T/C,2T/C,4T/C的內核繪製縮放比例。 (KMP_PLACE_THREADS環境可以提供幫助) –