我正在尝试实现Hogwild! Linear SVM算法,但我遇到了与我的实现存在错误的共享问题。
我的代码如下,但背景是我正在尝试计算哪些样本未通过我的测试并进行更新,这是由该组向量给出的。 Hogwild! (据我所知)只是完全异步地在同一个内存上进行更新。由于更新时间不正确,这会在数学意义上产生“噪音”。
可悲的是,当我尝试执行这些异步更新时,L1缓存无效并且必须重新获取。以下是我的代码。
有没有一种很好的方法来修复这种错误共享而不会丢失异步? (我更像是数学家而不是计算机科学家)。 This提到使用不同的优化级别可以解决此问题。
void update(size_t epoch, const double *X_data, const int *X_indices,
const int *X_indptr, const int *Y, double *W,
double reg, double step_size, size_t nodes,
size_t X_height, size_t X_width) {
size_t i, j;
double step = step_size/(1 + epoch);
double c;
#pragma omp parallel shared(W, X_data, X_indices, X_indptr, Y) private(i, j, c)
{
#pragma for schedule(static)
for (i=0;i<X_height;i++) {
c = 0.0;
for (j=X_indptr[i];j<X_indptr[i+1];j++)
c += X_data[j]*W[X_indices[j]]; // Scaled to discount the MPI scaling
if (Y[i]*c > 1)
continue;
for (j=X_indptr[i];j<X_indptr[i+1];j++)
W[X_indices[j]] += step*Y[i]*X_data[j]/(X_height*nodes);
} // END FOR OMP PARALLELIZED
#pragma for schedule(static) // Might not do much
for (i=0;i<X_width;i++) // (1 - self.reg*step)*self.W/self.nodes +
W[i] *= (1 - reg*step)/nodes;
}
}
答案 0 :(得分:3)
我对你提到的算法了解不多,但它看起来全局比计算边界更多的内存限制。为了说服你,这里是你的代码的快速重写:
void update( size_t epoch, const double *X_data, const int *X_indices,
const int *X_indptr, const int *Y, double *W,
double reg, double step_size, size_t nodes,
size_t X_height, size_t X_width ) {
const double step = step_size / ( 1 + epoch );
const double ratio = step / ( X_height * nodes );
const double tapper = ( 1 - reg * step ) / nodes;
#pragma omp parallel
{
#pragma omp for schedule( static )
for ( size_t i = 0; i < X_height; i++ ) {
double c = 0;
for ( int j = X_indptr[i]; j < X_indptr[i+1]; j++ ) {
c += X_data[j] * W[X_indices[j]]; // Scaled to discount the MPI scaling
}
if ( Y[i] * c <= 1 ) {
double ratioYi = Y[i] * ratio;
for ( int j = X_indptr[i]; j < X_indptr[i+1]; j++ ) {
// ATTENTION: this will collide across threads and have undefined result BY DESIGN
W[X_indices[j]] += ratioYi * X_data[j];
}
}
} // END FOR OMP PARALLELIZED
#pragma omp for schedule( static ) // Might not do much
for ( size_t i = 0; i < X_width; i++ ) { // (1 - self.reg*step)*self.W/self.nodes +
W[i] *= tapper;
}
}
}
如您所见,我做了一些改动。其中大多数都是纯粹的风格(如缩进,间距,变量声明位置等),但有些是非常重要的。例如,通过将ratio
和ratioYi
定义为尽可能浅的循环,我删除(或帮助编译器删除,它是否已完成)代码中的大多数计算。很明显,代码几乎只访问数据并计算得很少。
因此,除非您拥有多插槽(或多内存控制器)共享内存计算机,否则您将无法在此OpenMP并行化中看到更多的加速(如果有)。
此外,&#34;按设计&#34;算法在并行更新W
时接受的竞争条件,即使在你指出的论文中证明是合理的,也要继续困惑我。我仍然不想依赖于计算代码(或任何代码)的未定义行为。
无论如何,假设代码完成你想要的,缩放并且实际上仅受到由于错误共享导致的L1缓存失效的限制(或者实际上是真正的共享,因为你授权数据冲突),一个可能的&#34;解决方案&#34 ;将增加W
数组的大小,例如将其大小加倍,并且每秒仅存储有意义的数据。在你的算法中,这不会改变任何东西。简而言之,您必须乘以2 X_indices
。通过这样做,您甚至可以通过将存储在单个缓存行中的有用数据的数量机械地除以2来更加限制错误共享的可能性。然而,对于内存限制的代码,增加内存消耗可能不是最好的想法...但是因为它是一个非常简单的测试,只需尝试它,看看它是否给你带来任何好处。
最后要注意的是,您的代码在OpenMP并行化中存在错误,您有#pragma for
而不是#pragma omp for
。在这里复制时不确定这是不是一个拼写错误,但为了以防万一,最好提一下。