Normalmente, quando eu proponho um gerador seqüencial de números aleatórios em C, uso a chamada
srand(time(NULL))
então use
rand() mod N
para obter um número inteiro aleatório entre 0 e N-1. No entanto, quando faço isso em paralelo, as chamadas ao tempo (NULL) são tão próximas uma da outra que acabam sendo exatamente o mesmo número.
Eu tentei usar um gerador de números aleatórios congruencial linear:
para alguns inteiros e .
Eu sei que escolher para um número inteiro grande produz resultados rápidos porque o operador do módulo pode ser calculado truncando dígitos. No entanto, acho difícil estabelecer sementes que produzam seqüências aleatórias com um grande período em paralelo. Eu sei que a duração de um período é máxima se
- c e m são números primos entre si
- a-1 é divisível por todos os fatores primos de m
- se m for múltiplo de 4, a-1 também deverá ser múltiplo de 4.
(fonte: wikipedia )
Mas como garantir que todos os fluxos de números aleatórios tenham essa propriedade máxima? Em termos de MPI, como incorporar rank
e size
produzir períodos máximos usando o método congruencial linear? Seria mais fácil usar um Fibonacci Lagged ou Mersenne Twister para produzir fluxos aleatórios paralelos mais longos?
mod
os bits de baixa ordem - como sugeriu Jonathan Dursi , eles são muito menos aleatórios. Em vez disso, divida seu número aleatório (int) por maxint / range para obter o range necessário. Custa uma divisão, mas provavelmente é uma opção mais barata para melhorar a qualidade do seu fluxo de números aleatórios do que mudar para outro PRNG.Respostas:
O truque é intercalar o fluxo LCG de cada processo: para processos , modificamos o LCGp
ser estar
onde e efetivamente etapas. Podemos derivá-los rapidamente expandindo a etapa LCG original:Ap Cp p
e o padrão é que e é o resultado de, começando com o número , multiplicando sucessivamente por e adicionando vezes , depois multiplicando por , todos .Ap≡apmodm, Cp 0 a 1 p c modm
A última etapa é garantir que o LCG do passo de cada processo não se sobreponha: simplesmente inicialize o processo com a classificação com e um LCG paralelo com períodos individuais de está pronto, onde é o período original é o número de processos. Se o LCG modificado de cada processo for usado igualmente, o período completo será recuperado em paralelo.p r xr N/p N p
Eu implementei isso há cerca de seis meses (talvez ingenuamente), e você pode encontrar o código aqui .
fonte
Há um artigo muito bom sobre o tutorial de Katzgrabber, Números Aleatórios em Computação Científica: Uma Introdução , que eu indico para quem deseja ser usuário de PRNGs para computação científica. Geradores congruenciais lineares são rápidos, mas isso é tudo o que eles têm a seu favor; eles têm períodos curtos e podem facilmente errar; combinações perfeitamente razoáveis de a, c e m podem resultar em saídas terrivelmente correlacionadas, mesmo se você atender aos requisitos usuais entre a, c e m.
Pior, em um caso comum em que m é uma potência de dois (para que a operação de modificação seja rápida), os bits de ordem inferior têm um período muito mais curto que a sequência como um todo; portanto, se você estiver usando rand ()% N, você tem um período ainda mais curto do que o esperado.
Como regra geral, os geradores de lag-fibbonacci, MT e WELL têm propriedades muito melhores e ainda são muito rápidos.
Em termos de propagação em paralelo, o método de Jack Poulson é bom porque fornece uma sequência bem definida de números divididos igualmente entre os processadores. Se isso não importa, você pode fazer algo razoável para propagar os diferentes PRNGs; o mesmo artigo sugere algo que muitas pessoas inventaram independentemente, combinando o número da tarefa PID ou MPI com o tempo. A fórmula específica sugerida existe
Não tenho opiniões particulares sobre essa implementação específica, mas a abordagem geral é certamente razoável.
fonte
Uma idéia simples para espalhar um RNG seqüencial típico em um número decente de encadeamentos é fazer com que um único encadeamento avance a semente o mais rápido possível e envie apenas a cada milésima semente para a memória. Em seguida, faça com que cada um dos seus outros threads pegue uma dessas sementes de referência espaçadas e processe os 1000 valores nesse bloco, ou seja, gere novamente as 1000 sementes no bloco, gere seus empates psuedorandom e faça o que qualquer outro processamento envolver sua tarefa.
Isso funciona porque para RNGs que não computam tanto assim (o LCG é certamente um, mas muitos outros deveriam estar nessa categoria) o gargalo real está enviando as sementes para a memória (e provavelmente também para o processamento subsequente). Se você executa um LCG sem enviar nada para a memória, tudo deve permanecer nos registros da CPU e ser extremamente rápido. Mesmo para um RNG mais complicado, você deve permanecer no cache L1 e ser muito rápido.
Eu usei essa abordagem muito simples com um LCG que, por motivos legados, precisamos manter. Basicamente, obtemos aceleração linear até os 4-8 threads em uma estação de trabalho multicore típica. Mas agora vou tentar o método da resposta de Jack Poulson e espero ficar ainda mais rápido :).
OTOH, acredito que esse truque simples funcione para outros RNGs inerentemente sequenciais.
fonte
Esta resposta está atrasada, mas você deve dar uma olhada no SPRNG . Ele foi projetado especificamente para escalabilidade em paralelo e suporta vários tipos de PRNGs.
fonte