1 Пример вычисления числа Пи методом Монте-Карло в MPI

Одним из самых популярных первых примеров написания программ с использованием MPI является вычисление числа Пи методом Монте-Карло на окружности радиуса \(1\). В этом примере, берётся круг радиуса \(1\) с центром в точке \((0, 0)\), рассматривается его сектор в I-ой четверти и квадрат \([0, 1]\times[0, 1]\).

Рис. 1. Графическое представление метода Монте-Карло

Рис. 1. Графическое представление метода Монте-Карло

Далее берём случаёные точки \((x_i, y_i)\) равномерно из \([0, 1]\times[0, 1]\) и проверяем, попадают ли они в круг (т.е. \(x_i^2+y_i^2 < 1^2\)).

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

1.1 Реализация последовательного варианта на языке программирования Си

Введём функцию compute_pi_montecarlo_hits для подсчёта количества попаданий от числа попыток.

uint64_t compute_pi_montecarlo_hits(uint64_t steps)
{
  uint64_t i;
  uint64_t n_hits = 0;

  for (i = 0; i < steps; ++i) {
    double x = (double)rand() / RAND_MAX;
    double y = (double)rand() / RAND_MAX;
    double z = (x * x) + (y * y);
    if (z < 1.0) {
      ++n_hits;
    }
  }

  return n_hits;
}

Тогда, решение задачи может быть таким:

  srand(time(0));
  steps = DEFAULT_STEPS;
  hits = compute_pi_montecarlo_hits(steps);
  pi_montecarlo = 4.0 * hits / steps;

1.2 Генератор псевдослучайных чисел из библиотеки GSL

Основным недостатком предыдущего примера был выбор “плохого” генератора псевдослучайных чисел. Для решения этой проблемы воспльзуемся вспомогательной библиотекой: GNU Scientific Library.

1.2.1 Основные этапы использования

Подключение:

#include <gsl/gsl_rng.h>

Типовой порядок инициализации подсистемы и генератора псевдослучайных чисел:

  gsl_rng *random_generator;
  const gsl_rng_type *T;

  gsl_rng_env_setup();
  T = gsl_rng_default;
  random_generator = gsl_rng_alloc(T);
  gsl_rng_set(random_generator, SEED);

Использование:

  double x = gsl_rng_uniform(random_generator);
  double y = gsl_rng_uniform(random_generator);

Уничтожение генератора псевдослучайных чисел:

  gsl_rng_free(random_generator);

Линковка:

$ gcc ... -lgsl ...

1.2.2 Итоговый код

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include <gsl/gsl_rng.h>

/* https://www.wolframalpha.com/input/?i=lcm(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
 */
#define DEFAULT_STEPS 232792560ULL

/**
 *
 */
gsl_rng *new_random_generator(uint64_t seed)
{
  gsl_rng *random_generator;
  const gsl_rng_type *T;

  gsl_rng_env_setup();
  T = gsl_rng_default;
  random_generator = gsl_rng_alloc(T);
  gsl_rng_set(random_generator, seed);
  return random_generator;
}

/**
 *
 */
void free_random_generator(gsl_rng *random_generator)
{
  gsl_rng_free(random_generator);
}

/**
 *
 */
uint64_t compute_pi_montecarlo_hits(uint64_t steps, gsl_rng *random_generator)
{
  uint64_t i;
  uint64_t n_hits = 0;

  for (i = 0; i < steps; ++i) {
    double x = gsl_rng_uniform(random_generator);
    double y = gsl_rng_uniform(random_generator);
    double z = (x * x) + (y * y);
    if (z < 1.0) {
      ++n_hits;
    }
  }

  return n_hits;
}

/**
 *
 */
int main(int argc, char **argv)
{
  gsl_rng *random_generator;
  uint64_t steps;
  uint64_t hits;
  double pi_montecarlo;

  random_generator = new_random_generator(time(0));

  steps = DEFAULT_STEPS;

  hits = compute_pi_montecarlo_hits(steps, random_generator);

  pi_montecarlo = 4.0 * hits / steps;

  printf("Pi: %.8f\n", pi_montecarlo);

  free_random_generator(random_generator);
  return 0;
}

Компиляция:

$ gcc -O -std=c11 -Wall -Wpedantic pi_montecarlo_serial.c -lgsl -o pi_montecarlo_serial

Запуск:

$ time ./pi_montecarlo_serial 
Pi: 3.14154421

real    0m3.565s
user    0m3.560s
sys     0m0.002s

1.3 Реализация параллельного варианта на языке программирования Си

Используя функцию подсчёта количества попаданий compute_pi_montecarlo_hits решение может быть оформлено по следующему плану:

1.3.1 Основные этапы использования

Инициализация подсистемы MPI:

  MPI_Init(&argc, &argv);

Получение номера текущенго процесса и общего числа процеесов:

  int comm_rank;
  int comm_size;

  MPI_Comm_rank(MPI_COMM_WORLD, &comm_rank);
  MPI_Comm_size(MPI_COMM_WORLD, &comm_size);

Инициализация подсистемы случайных чисел, с учётом необходимости разного начального зерна в разных процессах:

  random_generator = new_random_generator(time(0) + 1234ULL * comm_rank);

Задание числа бросков для каждого из процессов. Это число будет одинаковым для всех процессов, определяться в 0-м процессе, и рассылаться одновременно всем (функция MPI_Bcast):

  if (0 == comm_rank) {
    steps_by_process = DEFAULT_STEPS / comm_size;
  }

  MPI_Bcast(&steps_by_process, 1, MPI_INT64_T, 0, MPI_COMM_WORLD);

Расчёт количества попаданий в каждом из процессов:

  uint64_t hits_by_process

  hits_by_process = compute_pi_montecarlo_hits(steps_by_process, random_generator);

Суммирование полученных результатов в 0-м процессе (функция MPI_Reduce с параметром MPI_SUM):

  uint64_t total_hits;

  total_hits = 0;
  MPI_Reduce(&hits_by_process, &total_hits, 1, MPI_INT64_T, MPI_SUM, 0, MPI_COMM_WORLD);

Расчёт числа Пи в 0-м процессе:

  if (0 == comm_rank) {
    pi_montecarlo = (4.0 * total_hits) / (comm_size * steps_by_process);
    printf("Pi: %.8f\n", pi_montecarlo);
  }

Завершению работы подсистемы MPI и выход из программы:

  MPI_Finalize();
  return 0;

1.3.2 Итоговый код

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include <mpi.h>
#include <gsl/gsl_rng.h>

/* https://www.wolframalpha.com/input/?i=lcm(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
 */
#define DEFAULT_STEPS 232792560ULL

/**
 *
 */
gsl_rng *new_random_generator(uint64_t seed)
{
  gsl_rng *random_generator;
  const gsl_rng_type *T;

  gsl_rng_env_setup();
  T = gsl_rng_default;
  random_generator = gsl_rng_alloc(T);
  gsl_rng_set(random_generator, seed);
  return random_generator;
}

/**
 *
 */
void free_random_generator(gsl_rng *random_generator)
{
  gsl_rng_free(random_generator);
}

/**
 *
 */
uint64_t compute_pi_montecarlo_hits(uint64_t steps, gsl_rng *random_generator)
{
  uint64_t i;
  uint64_t n_hits = 0;

  for (i = 0; i < steps; ++i) {
    double x = gsl_rng_uniform(random_generator);
    double y = gsl_rng_uniform(random_generator);
    double z = (x * x) + (y * y);
    if (z < 1.0) {
      ++n_hits;
    }
  }

  return n_hits;
}

/**
 *
 */
int main(int argc, char **argv)
{
  int comm_rank;
  int comm_size;
  gsl_rng *random_generator;
  double time_start;
  double time_end;
  uint64_t steps_by_process;
  uint64_t hits_by_process;
  uint64_t total_hits;
  double pi_montecarlo;

  MPI_Init(&argc, &argv);
  MPI_Comm_rank(MPI_COMM_WORLD, &comm_rank);
  MPI_Comm_size(MPI_COMM_WORLD, &comm_size);

  random_generator = new_random_generator(time(0) + 1234ULL * comm_rank);
  time_start = MPI_Wtime();

  if (0 == comm_rank) {
    steps_by_process = DEFAULT_STEPS / comm_size;
  }

  MPI_Bcast(&steps_by_process, 1, MPI_INT64_T, 0, MPI_COMM_WORLD);

  hits_by_process = compute_pi_montecarlo_hits(steps_by_process, random_generator);

  total_hits = 0;
  MPI_Reduce(&hits_by_process, &total_hits, 1, MPI_INT64_T, MPI_SUM, 0, MPI_COMM_WORLD);

  time_end = MPI_Wtime();

  if (0 == comm_rank) {
    pi_montecarlo = (4.0 * total_hits) / (comm_size * steps_by_process);
    printf("Pi: %.8f\n", pi_montecarlo);
    printf("Elapsed time is %f\n", time_end - time_start);
  }

  free_random_generator(random_generator);
  MPI_Finalize();
  return 0;
}

Компиляция:

$ module load mpi/openmpi-x86_64
$ mpicc -O -std=c11 -Wall -Wpedantic pi_montecarlo_mpi.c -lgsl -o pi_montecarlo_mpi

Запуск:

$ mpirun -n 1 ./pi_montecarlo_mpi
Pi: 3.14161284
Elapsed time is 3.566737

$ mpirun -n 2 ./pi_montecarlo_mpi
Pi: 3.14155102
Elapsed time is 1.804003

$ mpirun -n 3 ./pi_montecarlo_mpi
Pi: 3.14164465
Elapsed time is 1.226319

$ mpirun -n 4 ./pi_montecarlo_mpi
Pi: 3.14146078
Elapsed time is 0.978601

2 Дополнительные примеры

Команды перехода
        JMP     label ; безусловный переход на метку label
        Jсс     label ; переход на метку label при выполнении условия сс
Код Name Флаг Название
N Not - Отрицание условия (может комбинироваться со всеми)
E Equal ZF=1 Равно (может комбинироваться с G, L, A, B)
G Greater ZF=0 и SF=OF Больше (с учётом знака)
L Less SF≠OF Меньше (с учётом знака)
A Above CF=0 и ZF=0 Выше
B Below CF=1 Ниже
Z Zero ZF=1 Ноль
S Sign SF=1 Отрицательное
C Carry СF=1 Перенос
O Overflow OF=1 Переполнение
P Parity PF=1 Паритет
Команды CMP и TEST
        CMP    operand_1, operand_2
        TEST   operand_1, operand_2

        cmp     al,5    ; Сравнение AL и 5
        jl      C1      ; Переход, если AL < 5 (числа со знаком)
        cmp     al,5    ; Сравнение AL и 5
        jb      C1      ; Переход, если AL < 5 (числа без знака)
        test    bl,00000100b ; Проверить состояние 2-го бита BL
        jz      C1           ; Переход, если 2-й бит равен 0