Одним из самых популярных первых примеров написания программ с использованием MPI является вычисление числа Пи методом Монте-Карло на окружности радиуса \(1\). В этом примере, берётся круг радиуса \(1\) с центром в точке \((0, 0)\), рассматривается его сектор в I-ой четверти и квадрат \([0, 1]\times[0, 1]\).
Рис. 1. Графическое представление метода Монте-Карло
Далее берём случаёные точки \((x_i, y_i)\) равномерно из \([0, 1]\times[0, 1]\) и проверяем, попадают ли они в круг (т.е. \(x_i^2+y_i^2 < 1^2\)).
Соотношение попаданий к общему количеству бросков, будет стремиться к соотношению площадей сектора круга и квадрата, при увеличении числа попыток.
Введём функцию 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;
Основным недостатком предыдущего примера был выбор “плохого” генератора псевдослучайных чисел. Для решения этой проблемы воспльзуемся вспомогательной библиотекой: GNU Scientific Library.
Подключение:
#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 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 |
|
Компиляция:
$ 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
Используя функцию подсчёта количества попаданий compute_pi_montecarlo_hits
решение может быть оформлено по следующему плану:
Инициализация подсистемы 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 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 |
|
Компиляция:
$ 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
JMP label ; безусловный переход на метку label
Jсс label ; переход на метку label при выполнении условия сс
CMP
, TEST
).сс
можно представить как: [N]{{[G|L|A|B][E]}|{Z|S|C|O|P}}
, в соответствии со следующей таблицей:Код | 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 | Паритет |
JCXZ
/JECXZ
/JRCXZ
для перехода в случае равенства нулю регистра CX
/ECX
/RCX
(в соответствующем режиме процессора). CMP operand_1, operand_2
TEST operand_1, operand_2
CMP
и TEST
, как правило, используются перед командами условного перехода.CMP
аналогична команде SUB
, но не сохраняет результат. Она удобна для сравнения чисел. cmp al,5 ; Сравнение AL и 5
jl C1 ; Переход, если AL < 5 (числа со знаком)
cmp al,5 ; Сравнение AL и 5
jb C1 ; Переход, если AL < 5 (числа без знака)
TEST
аналогична команде AND
, но не сохраняет результат. Она удобна для проверки битов. test bl,00000100b ; Проверить состояние 2-го бита BL
jz C1 ; Переход, если 2-й бит равен 0