Количество процессоров MPI создает ошибку, как реализовать широковещательную рассылку?

Я создал программу на Python для вычисления числа пи. Затем я решил написать его с помощью mpi4py для запуска с несколькими процессами. Программа работает, но возвращает значение pi, отличное от исходной версии Python. По мере изучения этой проблемы я обнаружил, что она возвращает менее точное значение, когда я запускаю ее с большим количеством процессоров. Почему версия MPI изменяет результат при большем количестве процессоров? Также было бы разумнее использовать широковещательную рассылку, а не отправлять множество отдельных сообщений? Как бы я реализовал трансляцию, если бы она была более эффективной?

MPI-версия:

#!/apps/moose/miniconda/bin/python
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()
name = MPI.Get_processor_name()
def f(x):
    return (1-(float(x)**2))**float(0.5)
n = 1000000
nm = dict()
pi = dict()
for i in range(1,size+1):
    if i == size:
        nm[i] = (i*n/size)+1
    else:
        nm[i] = i*n/size
if rank == 0:
    val = 0
    for i in range(0,nm[1]):
        val = val+f(float(i)/float(n))
    val = val*2
    pi[0] = (float(2)/n)*(float(1)+val)
    print name, "rank", rank, "calculated", pi[0]
    for i in range(1, size):
        pi[i] = comm.recv(source=i, tag=i)
    number = sum(pi.itervalues())
    number = "%.20f" %(number)
    import time
    time.sleep(0.3)
    print "Pi is approximately", number
for proc in range(1, size):
    if proc == rank:
        val = 0
        for i in range(nm[proc]+1,nm[proc+1]):
            val = val+f(float(i)/float(n))
        val = val*2
        pi[proc] = (float(2)/n)*(float(1)+val)
        comm.send(pi[proc], dest=0, tag = proc)
        print name, "rank", rank, "calculated", pi[proc]

Оригинальная версия Python:

#!/usr/bin/python
n = 1000000
def f(x):
    return (1-(float(x)**2))**float(0.5)
val = 0
for i in range(n):
    i = i+1
    val = val+f(float(i)/float(n))
val = val*2
pi = (float(2)/n)*(float(1)+val)
print pi

person Paul    schedule 13.07.2017    source источник


Ответы (1)


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

Проблема вашего кода в том, что диапазоны значений i для каждого процесса не полны. Действительно, используйте маленькую n и напечатайте i, чтобы увидеть, что происходит. Например, for i in range(nm[proc]+1,nm[proc+1]): нужно заменить на for i in range(nm[proc],nm[proc+1]):. В противном случае i=nm[proc] никогда не обрабатывается. Кроме того, в pi[0] = (float(2)/n)*(float(1)+val) и pi[proc] = (float(2)/n)*(float(1)+val) член float(1) происходит от x=0 в интеграле. Но он считается много раз, один раз каждым процессом! Поскольку количество ошибок напрямую зависит от количества процессов, увеличение количества процессов снижает точность, что является симптомом, о котором вы сообщили.

Широковещательная передача соответствует ситуации, когда все процессы коммуникатора должны получить один и тот же фрагмент данных от данного процесса. Наоборот, здесь требуется, чтобы данные от всех процессоров были объединены с использованием суммы для получения результата, доступного для одного процесса (называемого «корневым»). Последняя операция называется редукция и выполняется comm.Reduce().

Вот фрагмент кода, основанный на вашем, использующем comm.Reduce() вместо send() и recv().

from mpi4py import MPI
import numpy as np

comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()
name = MPI.Get_processor_name()
def f(x):
    return (1-(float(x)**2))**float(0.5)

n = 10000000
nm =np.zeros(size+1,'i')

nm[0]=1
for i in range(1,size+1):
    if i == size:
        nm[i]=n
    else:
        nm[i] = (i*n)/size

val=0
for i in range(nm[rank],nm[rank+1]):
    val = val+f((float(i))/float(n))

out=np.array(0.0, 'd')
vala=np.array(val, 'd')
comm.Reduce([vala,MPI.DOUBLE],[out,MPI.DOUBLE],op=MPI.SUM,root=0)
if rank == 0:
    number =(float(4)/n)*(out)+float(2)/n
    number = "%.20f" %(number)
    import time
    time.sleep(0.3)
    print "Pi is approximately", number
person francis    schedule 14.07.2017
comment
Ваш код намного точнее, и спасибо вам за это. Однако я только что заметил, что когда я запускаю его с разным количеством процессов, он возвращает разные результаты. Они гораздо точнее, но все же другие. Любая идея, почему это так? - person Paul; 14.07.2017
comment
Вывод с одним процессом: 3,14159265355250161278, с двумя — 3,14159265355271211106, с тремя — 3,14159265355250472140. - person Paul; 14.07.2017
comment
Я также запускал его на большом кластере с интервалами 10000000000 и 500 процессорами. Этот тест дал результат 3,95590113700216061687, что кажется странным, поскольку должно быть более точным при большем числе интервалов для интеграла. - person Paul; 14.07.2017
comment
Что касается первых ошибок, я предполагаю, что это связано с конечной точностью числа с плавающей запятой Python, соответствующей двойному значению. Действительно, трудно получить более 13 значащих цифр. Небольшие различия обусловлены порядком суммирования. Что касается последней большой ошибки, 10000000000 превышает предел 32-битных целых чисел: я предполагаю, что ошибка может быть связана с переполнением. Наконец, это также большое количество дополнений: изучение ошибок с плавающей запятой может быть полезным. См. stackoverflow.com/questions/19130042/ - person francis; 14.07.2017