Алгоритм Флойда Уоршалла параллельно с использованием cuda

Я пытаюсь реализовать алгоритм Флойда Уоршалла с помощью cuda, но у меня проблема с синхронизацией. Это мой код:

__global__ void run_on_gpu(const int graph_size, int *output, int k) {
  int i = blockDim.y * blockIdx.y + threadIdx.y;
  int j = blockDim.x * blockIdx.x + threadIdx.x;

  if (D(i, k) + D(k, j) < D(i, j)) {
    D(i, j) = D(i, k) + D(k, j);
  }
}

void floyd_warshall_gpu(const int *graph, int graph_size, int *output) {
  int *dev_output;

  HANDLE_ERROR( cudaMalloc(&dev_output, sizeof(int) * graph_size * graph_size) );

  cudaMemcpy(dev_output, graph, sizeof(int) * graph_size * graph_size, cudaMemcpyHostToDevice);
  dim3 blocks(BLOCKS_PER_GRAPH_SIDE, BLOCKS_PER_GRAPH_SIDE, 1);
  dim3 threadsPerBlock(THREADS_PER_BLOCK_SIDE, THREADS_PER_BLOCK_SIDE, 1);
  int k;
  for (k = 0; k < graph_size; k++) {
    run_on_gpu<<<blocks, threadsPerBlock>>>(graph_size, dev_output, k);
  }
  cudaMemcpy(output, dev_output, sizeof(int) * graph_size * graph_size, cudaMemcpyDeviceToHost);

  cudaFree(dev_output);
}

Это мои исходные переменные:

#define GRAPH_SIZE 2000

#define EDGE_COST(graph, graph_size, a, b) graph[a * graph_size + b]
#define D(a, b) EDGE_COST(output, graph_size, a, b)

#define INF 0x1fffffff

#define THREADS_PER_BLOCK_SIDE 16 // Each block have 16 * 16 = 256 threads
#define BLOCKS_PER_GRAPH_SIDE GRAPH_SIZE / THREADS_PER_BLOCK_SIDE

Вот как я генерирую график:

void generate_random_graph(int *output, int graph_size) {
  int i, j;

  srand(0xdadadada);

  for (i = 0; i < graph_size; i++) {
    for (j = 0; j < graph_size; j++) {
      if (i == j) {
        D(i, j) = 0;
      }
      else {
        int r;
        r = rand() % 40;
        if (r > 20) {
          r = INF;
        }

        D(i, j) = r;
      }
    }
  }
}

Когда я устанавливаю GRAPH_SIZE на меньшее число, например 100, результат неверен.

Я последовательно написал алгоритм на процессоре, как показано ниже:

void floyd_warshall_cpu(const int *graph, int graph_size, int *output) {
  int i, j, k;

  memcpy(output, graph, sizeof(int) * graph_size * graph_size);

  for (k = 0; k < graph_size; k++) {
    for (i = 0; i < graph_size; i++) {
      for (j = 0; j < graph_size; j++) {
        if (D(i, k) + D(k, j) < D(i, j)) {
          D(i, j) = D(i, k) + D(k, j);
        }
      }
    }
  }
}

И я запускаю и тестирую это так:

int main(int argc, char **argv) {
  int *graph, *output_cpu, *output_gpu;
  int size;
  size = sizeof(int) * GRAPH_SIZE * GRAPH_SIZE;
  graph = (int *)malloc(size);
  output_cpu = (int *)malloc(size);
  assert(output_cpu);
  memset(output_cpu, 0, size);
  output_gpu = (int *)malloc(size);
  generate_random_graph(graph, GRAPH_SIZE);
  floyd_warshall_cpu(graph, GRAPH_SIZE, output_cpu);
  floyd_warshall_gpu(graph, GRAPH_SIZE, output_gpu);
  if (memcmp(output_cpu, output_gpu, size) != 0) {
    fprintf(stderr, "FAIL!\n");
  }
  else {
    fprintf(stderr, "SUCCESS!\n");
  }
  free(graph);
  free(output_cpu);
  free(output_gpu);
  return 0;
}

Может ли кто-нибудь дать мне идею, как это решить?


person Madroks    schedule 27.11.2020    source источник
comment
У меня алгоритм реализован последовательно на процессоре, поэтому для проверки я просто сравниваю результат   -  person Madroks    schedule 27.11.2020


Ответы (1)


Основная проблема, которую я мог найти, заключается в том, что размер вашей сетки выполнен неправильно.

При N = 2000 и боковом размере блока резьбы 16 это число делится на целое число. Но если уменьшить N до 100, это не так.

Мы можем исправить это, округлив размеры сетки в большую сторону:

#define BLOCKS_PER_GRAPH_SIDE ((GRAPH_SIZE+THREADS_PER_BLOCK_SIDE-1) / THREADS_PER_BLOCK_SIDE)

И добавление проверки потока в ваше ядро:

  if ((i < graph_size) && (j < graph_size))

Вот модифицированный код, который у меня работает правильно:

$ cat t92.cu
#include <cstdio>
#include <cassert>


#define GRAPH_SIZE 100

#define EDGE_COST(graph, graph_size, a, b) graph[a * graph_size + b]
#define D(a, b) EDGE_COST(output, graph_size, a, b)

#define INF 0x1fffffff

#define THREADS_PER_BLOCK_SIDE 16
#define BLOCKS_PER_GRAPH_SIDE ((GRAPH_SIZE+THREADS_PER_BLOCK_SIDE-1) / THREADS_PER_BLOCK_SIDE)
#define HANDLE_ERROR(x) x



__global__ void run_on_gpu(const int graph_size, int *output, int k) {
  int i = blockDim.y * blockIdx.y + threadIdx.y;
  int j = blockDim.x * blockIdx.x + threadIdx.x;
  if ((i < graph_size) && (j < graph_size))
    if (D(i, k) + D(k, j) < D(i, j)) {
      D(i, j) = D(i, k) + D(k, j);
  }
}

void floyd_warshall_gpu(const int *graph, int graph_size, int *output) {
  int *dev_output;

  HANDLE_ERROR( cudaMalloc(&dev_output, sizeof(int) * graph_size * graph_size) );

  cudaMemcpy(dev_output, graph, sizeof(int) * graph_size * graph_size, cudaMemcpyHostToDevice);
  dim3 blocks(BLOCKS_PER_GRAPH_SIDE, BLOCKS_PER_GRAPH_SIDE, 1);
  dim3 threadsPerBlock(THREADS_PER_BLOCK_SIDE, THREADS_PER_BLOCK_SIDE, 1);
  int k;
  for (k = 0; k < graph_size; k++) {
    run_on_gpu<<<blocks, threadsPerBlock>>>(graph_size, dev_output, k);
  }
  cudaMemcpy(output, dev_output, sizeof(int) * graph_size * graph_size, cudaMemcpyDeviceToHost);

  cudaFree(dev_output);
}

void generate_random_graph(int *output, int graph_size) {
  int i, j;

  srand(0xdadadada);

  for (i = 0; i < graph_size; i++) {
    for (j = 0; j < graph_size; j++) {
      if (i == j) {
        D(i, j) = 0;
      }
      else {
        int r;
        r = rand() % 1000;
        if (r > 20) {
          D(i, j) = INF;
        }
        else
          D(i, j) = r+10;
      }
    }
  }
}

void floyd_warshall_cpu(const int *graph, int graph_size, int *output) {
  int i, j, k;

  memcpy(output, graph, sizeof(int) * graph_size * graph_size);

  for (k = 0; k < graph_size; k++) {
    for (i = 0; i < graph_size; i++) {
      for (j = 0; j < graph_size; j++) {
        if (D(i, k) + D(k, j) < D(i, j)) {
          D(i, j) = D(i, k) + D(k, j);
        }
      }
    }
  }
}

int main(int argc, char **argv) {
  int *graph, *output_cpu, *output_gpu;
  int size;
  size = sizeof(int) * GRAPH_SIZE * GRAPH_SIZE;
  graph = (int *)malloc(size);
  output_cpu = (int *)malloc(size);
  assert(output_cpu);
  memset(output_cpu, 0, size);
  output_gpu = (int *)malloc(size);
  generate_random_graph(graph, GRAPH_SIZE);
  floyd_warshall_cpu(graph, GRAPH_SIZE, output_cpu);
  floyd_warshall_gpu(graph, GRAPH_SIZE, output_gpu);
  if (memcmp(output_cpu, output_gpu, size) != 0) {
    fprintf(stderr, "FAIL!\n");
    int qq = 0;
    for (int i = 0; i < GRAPH_SIZE*GRAPH_SIZE; i++)
    if (output_cpu[i] != output_gpu[i]) {qq++; printf("i: %d, cpu: %d, gpu: %d\n",i, output_cpu[i], output_gpu[i]);}
    printf("# mismatches: %d\n", qq);
  }
  else {
    fprintf(stderr, "SUCCESS!\n");
  //  for (int i = 0; i < 100; i++)
  //   printf("i: %d, cpu: %d, gpu: %d\n",i, output_cpu[i], output_gpu[i]);
  }
  free(graph);
  free(output_cpu);
  free(output_gpu);
  return 0;
}
$ nvcc -o t92 t92.cu
$ vi t92.cu
$ cuda-memcheck ./t92
========= CUDA-MEMCHECK
SUCCESS!
========= ERROR SUMMARY: 0 errors
$

(Я немного изменил ваш тестовый пример, так как он создавал выходную матрицу, которая была в основном нулевой.)

person Robert Crovella    schedule 27.11.2020