Возможные источники для случайного числа семян
Два момента - во-первых, пример на Фортране, но я думаю, что он подходит для любого языка; во-вторых, встроенные генераторы случайных чисел не являются действительно случайными, и существуют другие генераторы, но мы не заинтересованы в их использовании для того, что мы делаем.
В большинстве дискуссий о случайных начальных числах признается, что если программа не заполняет их во время выполнения, то начальная величина создается во время компиляции. Таким образом, при каждом запуске программы генерируется одна и та же последовательность чисел, что не подходит для случайных чисел. Одним из способов преодоления этого является заполнение генератора случайных чисел системными часами.
Тем не менее, при работе параллельно с MPI на многоядерной машине, системный тактовый подход для нас породил такие же проблемы. В то время как последовательности менялись от запуска к запуску, все процессоры получали одинаковые системные часы и, следовательно, одинаковое случайное начальное число и одинаковые последовательности.
Итак, рассмотрим следующий пример кода:
PROGRAM clock_test
IMPLICIT NONE
INCLUDE "mpif.h"
INTEGER :: ierr, rank, clock, i, n, method
INTEGER, DIMENSION(:), ALLOCATABLE :: seed
REAL(KIND=8) :: random
INTEGER, PARAMETER :: OLD_METHOD = 0, &
NEW_METHOD = 1
CALL MPI_INIT(ierr)
CALL MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)
CALL RANDOM_SEED(SIZE=n)
ALLOCATE(seed(n))
DO method = 0, 1
SELECT CASE (method)
CASE (OLD_METHOD)
CALL SYSTEM_CLOCK(COUNT=clock)
seed = clock + 37 * (/ (i - 1, i = 1, n) /)
CALL RANDOM_SEED(put=seed)
CALL RANDOM_NUMBER(random)
WRITE(*,*) "OLD Rank, dev = ", rank, random
CASE (NEW_METHOD)
OPEN(89,FILE='/dev/urandom',ACCESS='stream',FORM='UNFORMATTED')
READ(89) seed
CLOSE(89)
CALL RANDOM_SEED(put=seed)
CALL RANDOM_NUMBER(random)
WRITE(*,*) "NEW Rank, dev = ", rank, random
END SELECT
CALL MPI_BARRIER(MPI_COMM_WORLD, ierr)
END DO
CALL MPI_FINALIZE(ierr)
END PROGRAM clock_test
Который при запуске на моей рабочей станции с 2 ядрами, дает:
OLD Rank, dev = 0 0.330676306089146
OLD Rank, dev = 1 0.330676306089146
NEW Rank, dev = 0 0.531503215980609
NEW Rank, dev = 1 0.747413828750221
Итак, мы преодолели проблему с часами, прочитав /dev/urandom
вместо. Таким образом, каждое ядро получает свое случайное число.
Какие еще начальные подходы существуют, которые будут работать в многоядерной системе MPI и при этом быть уникальными для каждого ядра, от запуска к запуску?
2 ответа
Если вы посмотрите на Случайные числа в Научных Вычислениях: Введение Кацграббера (которое является превосходным, ясным обсуждением входов и выходов использования PRNG для технических вычислений), параллельно они предлагают использовать хэш-функцию времени и PID для создать семя. Из их раздела 7.1:
long seedgen(void) {
long s, seed, pid;
pid = getpid();
s = time ( &seconds ); /* get CPU seconds since 01/01/1970 */
seed = abs(((s*181)*((pid-83)*359))%104729);
return seed;
}
конечно, в Фортране это было бы что-то вроде
function seedgen(pid)
use iso_fortran_env
implicit none
integer(kind=int64) :: seedgen
integer, intent(IN) :: pid
integer :: s
call system_clock(s)
seedgen = abs( mod((s*181)*((pid-83)*359), 104729) )
end function seedgen
Также иногда удобно иметь возможность скоротать время, а не вызывать его изнутри. seedgen
, чтобы при тестировании вы могли присвоить ему фиксированные значения, которые затем генерируют воспроизводимую (== тестируемую) последовательность.
Системное время обычно возвращается в (или, по крайней мере, легко преобразуется в) целочисленный тип: просто добавьте ранг процесса к значению и используйте его для заполнения генератора случайных чисел.