이 SOR 솔버 코드를 작성했습니다. 이 알고리즘이하는 일을 너무 신경 쓰지 마라. 걱정할 필요가 없다. 그러나 완전성을 위해 시스템이 얼마나 잘 조절되었는지에 따라 선형 방정식 시스템을 풀 수도 있습니다.왜이 코드는 선형으로 스케일되지 않습니까?
행당 7 개의 0이 아닌 열이있는 2097152 행의 희소 행렬 (수렴하지 않음)을 사용하여 실행합니다.
번역 : 외부 do-while
루프 10000 반복 (I는 max_iters
로 전달할 값을) 수행, 중간 for
는 OpenMP의 스레드 나뉘어 work_line
덩어리에 2097152 반복 분할을 수행한다. 가장 안쪽의 for
루프는 7 회 반복되지만, 적은 경우 (1 % 미만)를 제외하면 7 회 반복됩니다.
sol
값의 스레드에 데이터 종속성이 있습니다. 중간의 각 반복 for
은 하나의 요소를 업데이트하지만 배열의 다른 요소 6 개까지 읽습니다. SOR은 정확한 알고리즘이 아니기 때문에 읽을 때 해당 위치의 이전 값이나 현재 값을 가질 수 있습니다 (솔버를 잘 알고 있다면 이것은 가우스 - 시델 (Gauss-Siedel)입니다. 병행). 당신이 볼 수 있듯이 그들은 항상 우리를 가르 칠 것, 그것은 100 % 병렬 문제의 종류이기
typedef struct{
size_t size;
unsigned int *col_buffer;
unsigned int *row_jumper;
real *elements;
} Mat;
int work_line;
// Assumes there are no null elements on main diagonal
unsigned int solve(const Mat* matrix, const real *rhs, real *sol, real sor_omega, unsigned int max_iters, real tolerance)
{
real *coefs = matrix->elements;
unsigned int *cols = matrix->col_buffer;
unsigned int *rows = matrix->row_jumper;
int size = matrix->size;
real compl_omega = 1.0 - sor_omega;
unsigned int count = 0;
bool done;
do {
done = true;
#pragma omp parallel shared(done)
{
bool tdone = true;
#pragma omp for nowait schedule(dynamic, work_line)
for(int i = 0; i < size; ++i) {
real new_val = rhs[i];
real diagonal;
real residual;
unsigned int end = rows[i+1];
for(int j = rows[i]; j < end; ++j) {
unsigned int col = cols[j];
if(col != i) {
real tmp;
#pragma omp atomic read
tmp = sol[col];
new_val -= coefs[j] * tmp;
} else {
diagonal = coefs[j];
}
}
residual = fabs(new_val - diagonal * sol[i]);
if(residual > tolerance) {
tdone = false;
}
new_val = sor_omega * new_val/diagonal + compl_omega * sol[i];
#pragma omp atomic write
sol[i] = new_val;
}
#pragma omp atomic update
done &= tdone;
}
} while(++count < max_iters && !done);
return count;
}
는,이 병렬 지역 내부에 잠금 없기 때문에. 그건 내가 실제로 보는 것이 아니다.
내 테스트는 인텔 ® 제온 ® CPU E5-2670 v2 @ 2.50GHz, 2 프로세서, 10 코어, 하이퍼 스레드 사용, 최대 40 논리 코어에서 실행되었습니다.
첫 번째 실행시에는 work_line
이 2048로 고정되었고 스레드 수는 1에서 40까지 다양했습니다 (총 40 회 실행). 이것은 각 실행의 실행 시간과 그래프 (스레드 초에서 x)
놀람 대수 곡선이고, 그래서 생각한 작업 줄 정도로 컸다 때문에, 공유 캐시 잘 사용되지 않았으므로이 프로세서의 L1 캐시가 64 바이트 (어레이의 8 배 2 배)의 업데이트를 동기화한다는 가상 파일 /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
을 파헤 쳤습니다. 그래서 나는 설정 work_line
8 : 다음
나는 8 NUMA 노점을 피하기 위해 너무 낮은 생각 work_line
16으로 설정 : 위의를 실행하는 동안
, 나는 생각 "work_line
은 무엇이 좋을지 예측할 수 있습니까? 그냥 볼 수 있습니다 ..."그리고 모든 work_line
이 8에서 2048, 8 단계 (즉 캐시 라인의 모든 배수, 1에서 256까지)로 실행되도록 예약되었습니다. 20에 대한 결과 및 40 개 스레드 (스레드 사이에서 나누어 중간 for
루프의 분할 초 X 크기) :
나는 더 큰 동안 낮은 work_line
을 가진 경우는, 캐시 동기화에서 심하게 앓고 생각 work_line
은 특정 수의 스레드를 초과하여 아무런 이점도 제공하지 않습니다 (메모리 경로가 병목 현상이라고 생각합니다).100 % 병렬로 보이는 문제가 실제 기계에서 그러한 나쁜 행동을 나타냄을 아는 것은 매우 슬픈 일입니다. 따라서 멀티 코어 시스템이 매우 잘 팔리고 있다는 확신이 들기 전에 먼저 여기에서 묻습니다.
이 코드 배율을 코어 수에 선형으로 적용하려면 어떻게해야합니까? 내가 뭘 놓치고 있니? 처음에는 보이는 것처럼 좋지 않게 만드는 문제가 있습니까?
업데이트
에 따라 제안, 나는 static
과 dynamic
일정으로 모두를 테스트하지만, 아토 배열 sol
에 읽기/쓰기 제거. 참고로 파란색과 주황색 선은 이전 그래프와 동일합니다 (최대 work_line = 248;
까지). 노란색과 녹색 선이 새로운 선입니다. 내가 볼 수있는 것 : 은 낮은 work_line
의 중요한 차이를 만듭니다. 그러나 96 후에는 dynamic
의 이점이 오버 헤드를 능가하므로 속도가 빠릅니다. 원자 적 연산은 전혀 차이가 없습니다.
나는 SOR/Gauss-Seidel 방법에 익숙하지 않지만 행렬 곱셈이나 Cholesky 분해법을 사용하면 좋은 스케일링을 얻을 수있는 유일한 방법은 루프 타일링을 사용하여 데이터가 아직있는 동안 다시 사용하는 것입니다. 캐시. http://stackoverflow.com/questions/22479258/cholesky-decomposition-with-openmp를 참조하십시오. 그렇지 않으면 메모리가 바운드됩니다. –
알고리즘에 익숙하지는 않지만, 내부 루프를 빠르게 보면 매우 빈약 한 공간 메모리 지역이 있음을 알 수 있습니다. (일반적으로 희소 선형 대수학의 경우처럼)이 경우 메모리 액세스가 제한 될 수 있습니다. – Mysticial
SOR의 시간 복잡성은 얼마나됩니까? http://www.cs.berkeley.edu/~demmel/cs267/lecture24/lecture24.html#link_4 O (N^3/2)? Matrix Mult에서 계산은 N^3으로 가고 읽기는 N^2로 진행하므로 확장이 용이합니다. 따라서 계산 횟수가 읽기보다 훨씬 크지 않으면 메모리가 바운드됩니다. 코어가 빠르며 주 메모리가 느리다는 사실을 무시하면 많은 기본 알고리즘이 확장 성이 좋습니다. BLAS 레벨 2 (예 : 행렬 * vec)는 느린 메모리를 무시하고 확장합니다. 슬로우 메모리로 잘 확장되는 것은 BLAS 레벨 3 (O (N^3) 예 : GEMM, Choleksy ...)입니다. –