Tentei um script bash, mas demorou muito para criar um arquivo simples de 1 MB. Acho que a resposta está em usar /dev/random
or /dev/urandom
, mas outras postagens aqui mostram apenas como adicionar todos os tipos de dados a um arquivo usando essas coisas, mas quero adicionar apenas números.
Então, existe um comando que eu possa usar para criar um arquivo aleatório de tamanho 1 GB contendo apenas números entre 0 e 9?
Edit: Eu quero que a saída seja algo como isto
0 1 4 7 ..... 9
8 7 5 8 ..... 8
....
....
8 7 5 3 ..... 3
O intervalo é de 0 a 9, significando apenas os números 0, 1, 2, 3, 4, 5, 6, 7, 8 e 9. Também preciso que eles sejam separados por espaço e 100 por linha, até o n
número de linhas. Isso é algo que eu não ligo, quero que meu tamanho final seja de 1 GB.
Edit: Estou usando o Ubuntu 16.04 LTS
yes 4 | tr '\n' ' ' | fold -w 200 | head -c1G
Respostas:
Esta é parcialmente uma resposta explícita, por causa do título da pergunta.
Quando você procura "o caminho mais rápido para ..." , a resposta é quase sempre uma ferramenta especializada. Essas "respostas" mostram uma dessas ferramentas, apenas para que você possa experimentar.
Esta não é uma resposta séria, porque você não deve procurar ferramentas especializadas para trabalhos que realiza apenas uma vez ou muito raramente. Veja bem, você passará mais tempo procurando ferramentas e aprendendo sobre elas do que realmente fazendo coisas. Os reservatórios e utilitários gostam
bash
eawk
não são os mais rápidos, mas geralmente você pode escrever uma linha para realizar o trabalho, gastando apenas alguns segundos. Linguagens de script melhores comoperl
também podem ser usadas, embora a curva de aprendizadoperl
seja grande, e hesito em recomendá-la para tais propósitos, porque fiquei traumatizada por projetos perl terríveis.python
por outro lado, é um pouco prejudicado por sua E / S bastante lenta; é apenas um problema quando você filtra ou gera gigabytes de dados.De qualquer forma, o exemplo de programa C89 a seguir (que usa o POSIX.1 para um relógio de maior precisão somente se disponível) deve atingir uma taxa de geração de cerca de 100 MB / s (testada no Linux em um laptop com um processador Intel i5-4200U, canalizando a saída para
/dev/null
), usando um bom gerador de números pseudo-aleatórios. (A saída deve passar em todos os testes do BigCrunch, exceto no teste MatrixRank, pois o código usa xorshift64 * e o método de exclusão para evitar distorções nos dígitos.)dígitos decimais.c:
Podemos torná-lo muito mais rápido, se mudarmos para um buffer de linha, e
fwrite()
uma vez, em vez de emitir cada dígito por vez. Observe que ainda mantemos o fluxo totalmente em buffer, para evitar gravações parciais (sem potência de dois) se a saída for um dispositivo de bloco.Nota: ambos os exemplos editados em 18/11/2016 para garantir a distribuição uniforme de dígitos (zero é excluído; veja, por exemplo, aqui para comparação e detalhes sobre vários geradores de números pseudo-aleatórios).
Compilar usando, por exemplo
e, opcionalmente, instalar todo o sistema para
/usr/bin
usarLeva o número de dígitos por linha e o número de linhas. Porque
1000000000 / 100 / 2 = 5000000
(cinco milhões; bytes totais divididos por colunas divididas por 2), você pode usarpara gerar o tamanho de gigabyte
digits.txt
conforme desejado pelo OP.Observe que o próprio programa é escrito mais com legibilidade do que eficiência em mente. Minha intenção aqui não é mostrar a eficiência do código - eu usaria POSIX.1 e E / S de baixo nível de qualquer maneira, em vez de interfaces C genéricas - mas permitir que você veja facilmente que tipo de equilíbrio existe com o esforço despendido no desenvolvimento de ferramentas dedicadas versus seu desempenho, em comparação com one-liners ou shell curto ou scriptlets awk.
Usando a biblioteca GNU C, chamar a
fputc()
função para cada saída de caracteres gera uma sobrecarga muito pequena (de uma chamada de função indireta ou condicionais - aFILE
interface é realmente bastante complexa e versátil, você vê). Neste laptop Intel Core i5-4200U em particular, o redirecionamento da saída para/dev/null
a primeira versão (fputc) leva cerca de 11 segundos, enquanto a versão linha por vez leva apenas 1,3 segundos.Por vezes, escrevo esses programas e geradores apenas porque gosto de brincar com grandes conjuntos de dados. Eu sou estranho assim. Por exemplo, uma vez eu escrevi um programa para imprimir todos os valores positivos de ponto flutuante IEEE-754 finitos em um arquivo de texto, com precisão suficiente para gerar exatamente o mesmo valor quando analisado. O arquivo tinha alguns gigabytes de tamanho (talvez 4G ou mais); não existem tantos
float
s positivos finitos como se poderia pensar. Eu usei isso para comparar implementações que lêem e analisam esses dados.Para casos de uso normais, como o OP está tendo, scripts de shell, scriptlets e one-liners são a melhor abordagem. Menos tempo gasto para realizar a tarefa geral. (Exceto se eles precisarem de um arquivo diferente a cada dia ou mais, ou se houver muitas pessoas que precisem de um arquivo diferente, no qual, raro, uma ferramenta dedicada como a acima, justifique o esforço despendido.)
fonte
mmap()
é o caminho mais fácil para a melhor velocidade de E / S - mas faça uma referência antes de fazer qualquer reclamação!write()
, o uso , normalmente é mais rápida quemmap()
.fwrite()
não é muito mais lento. Sim, comparei isso (mas não neste exemplo em particular);write()
em pedaços grandes (262144, 524288 ou 1048576 bytes) tende a superar os outros métodos. A versãofputc()
implementada na biblioteca GNU C (que eu também fiz extensivamente comparada) é lenta por vários motivos; em particular, a implementação precisa fazer saltos condicionais ou chamadas indiretas para cada caractere adicionado; que a pequena sobrecarga incorrida muitas vezes aumenta./dev/null
. O scriptlet de Stéphane Chazelas leva cerca de 52 segundos; snippet perl (incluindo ahead
filtragem) cerca de 58 segundos; seushuf
snippet (com o tempo correto; você só mede o tempo shuf, supondo que a pasta não demore mais) leva cerca de 69 segundos. O C ++ 11 de James Hollis, um programa de linha por vez, leva 14 segundos. O programa acima leva 10 segundos.Este:
(assumindo uma
head
implementação compatível-c
) parece ser razoavelmente rápido no meu sistema.tr
converte todo o intervalo de bytes (0 a 255, 0 a 0377 em octal): os 25 primeiros bytes como 0, os 25 seguintes como 1 ... depois 259 o restante (250 a 255) para "x", que então descarte (comtr -d x
) como queremos uma distribuição uniforme (supondo que ela/dev/urandom
tenha uma distribuição uniforme) e, portanto, não forneça um viés para alguns dígitos.Isso produz um dígito para 97% dos bytes de
/dev/urandom
.fold -w 1
torna um dígito por linha.paste -s
é chamado com uma lista de separadores que consiste em 99 caracteres de espaço e um caractere de nova linha, para ter 100 dígitos separados por espaço em cada linha.head -c1G
receberá o primeiro GiB (2 30 ) disso. Observe que a última linha será truncada e não limitada. Você pode truncar para 2 30 -1 e adicionar a nova linha ausente manualmente, ou truncar para 10 9 bytes em vez disso, que são 50 milhões dessas linhas de 200 bytes (head -n 50000000
também o tornariam um comando padrão / portátil).Esses tempos (obtidos
zsh
em um sistema quad-core) fornecem uma indicação de onde o tempo da CPU é gasto:O primeiro
tr
é o gargalo da garrafa, na maioria das vezes gasto no kernel (suponho que seja para a geração de números aleatórios). O tempo está aproximadamente alinhado com a taxa pela qual posso obter bytes/dev/uramdom
(cerca de 19MiB / se aqui produzimos 2 bytes para cada 0,97 byte de / dev / urandom a uma taxa de 32MiB / s).fold
parece estar gastando uma quantidade razoável de tempo de CPU (15s) apenas para inserir um caractere de nova linha após cada byte, mas isso não afeta o tempo geral, pois funciona em uma CPU diferente no meu caso (adicionar a-b
opção torna muito mais eficiente,dd cbs=1 conv=unblock
parece ser uma alternativa melhor).Você pode acabar com o
head -c1G
corte de alguns segundos e definir um limite no tamanho do arquivo (limit filesize 1024m
comzsh
ouulimit -f "$((1024*1024))"
com a maioria dos outros shells (inclusivezsh
)) em vez de um subshell.Isso poderia ser melhorado se extraíssemos 2 dígitos para cada byte, mas precisaríamos de uma abordagem diferente para isso. O acima é muito eficiente, porque
tr
apenas procura cada byte em uma matriz de 256 bytes. Ele não pode fazer isso por 2 bytes de cada vez, e usar coisas comohexdump -e '1/1 "%02u"'
essa que calcula a representação de texto de um byte usando algoritmos mais complexos seria mais caro que a própria geração de números aleatórios. Ainda assim, se, como no meu caso, você tiver núcleos de CPU cujo tempo de sobra, ele ainda poderá raspar alguns segundos:Com:
Eu recebo (observe, porém, que aqui há 1.000.000.000 de bytes em oposição a 1.073.741.824):
Mais tempo de CPU em geral, mas melhor distribuído entre meus 4 núcleos de CPU, por isso acaba levando menos tempo para o relógio de parede. O gargalo é agora
hexdump
.Se usarmos em
dd
vez da linhafold
, poderemos realmente reduzir a quantidade de trabalhohexdump
necessária e melhorar o equilíbrio do trabalho entre as CPUs:(assumindo aqui o GNU
dd
por seuiflag=fullblock
estatus=none
) que fornece:De volta à geração de números aleatórios, sendo o gargalo.
Agora, como apontado por @OleTange, se você tiver o
openssl
utilitário, poderá usá-lo para obter um gerador de bytes pseudo-aleatórios mais rápido (especialmente em processadores que possuem instruções AES).no meu sistema vomita 15 vezes mais bytes por segundo que
/dev/urandom
. (Não posso comentar como ele se compara em termos de fonte de aleatoriedade criptograficamente segura, se isso se aplica ao seu caso de uso).Agora dá:
de volta a
hexdump
ser o gargalo.Como ainda tenho CPUs de sobra, posso executar 3
hexdump
em paralelo.(
<&3
é necessário para shells que não sejam oszsh
comandos stdin de fechamento em / dev / null quando executados em segundo plano).Agora com 6.2 segundos e minhas CPUs quase totalmente utilizadas.
fonte
perl
variante que era significativamente mais lenta de qualquer maneira. Não consigo obter 2 dígitos por byte com essa abordagem de colar | dobra |.bc
(depois solte os 0, 1 ou 2 dígitos mais significativos).Se você tem
shuf
disponível (o recente GNU coreutils possui), você pode fazer o seguinte:Na minha VM, isso agora é um pouco mais lento que a resposta de Stéphane em um fator de 3: 4.
fonte
shuf
no meu PC empresa não tem-r
,fmt
não tem-g
muitopaste
/printf
truque - obrigado. Sua resposta agora é aparentemente mais rápida.Se você não precisa de aleatoriedade de qualidade muito alta e a distribuição quase uniforme é boa o suficiente, você pode ir muito rápido, especialmente em uma CPU moderna com vetores inteiros SIMD eficientes como x86 com SSE2 ou AVX2.
É a resposta do @ NominalAnimal, já que ambos tínhamos a mesma idéia, mas vetorizamos manualmente o x86. (E com números aleatórios de pior qualidade, mas provavelmente bons o suficiente para muitos casos de uso.) Isso é executado 15 ou 30 vezes mais rápido que o código do @ Nominal, a ~ 13 GB / s de saída ASCII em um Intel Haswell de 2,5 GHz CPU com AVX2. Isso ainda é menos do que a largura de banda máxima teórica da memória principal (o DDR3-1600 de canal duplo é de cerca de 25,6 GB / s), mas eu estava escrevendo no / dev / null, então na verdade é apenas reescrevendo um buffer que permanece quente no cache. O Skylake deve executar esse mesmo código significativamente mais rápido que o Haswell (veja o final desta resposta).
Supondo que você realmente gargalo na E / S para o disco ou canalizando isso em algum lugar, uma implementação rápida significa que sua CPU nem precisa ter um clock mais alto do que ocioso. Ele usa muito menos energia total para produzir o resultado. (Vida útil da bateria / aquecimento / aquecimento global.)
Isso é tão rápido que você provavelmente não deseja gravá-lo em disco. Apenas gere novamente conforme necessário (da mesma semente, se desejar os mesmos dados novamente). Mesmo se você quiser alimentá-lo com um processo multiencadeado que possa usar todas as CPUs, executar isso para canalizar os dados para ele deixará quente no cache L3 (e cache L2 no núcleo que o escreveu) e use muito pouco tempo de CPU. (Porém, observe que o encanamento acrescenta muita sobrecarga em relação à gravação
/dev/null
. Em um Skylake i7-6700k, o encanamento parawc -c
ou outro programa que apenas lê + descarta sua entrada, é cerca de 8x mais lento do que gravar/dev/null
e usa apenas 70% de um CPU, mas ainda são 4,0 GB / s em uma CPU de 3,9 GHz.Gerá-lo é mais rápido do que relê-lo, mesmo a partir de um SSD rápido conectado ao PCIe, mas o IDK é mais eficiente em termos de energia (o multiplicador de vetores inteiros fica muito ocupado e provavelmente consome muita energia, juntamente com outros AVX2 ALUs vetoriais 256b). OTOH, não sei quanto tempo de CPU lendo no disco levaria para algo que estava maximizando todos os núcleos que processavam essa entrada. Eu acho que uma alternância de contexto para gerar novamente em blocos de 128k pode ser competitiva com a execução de código do sistema de arquivos / pagecache e a alocação de páginas para ler dados do disco. Claro, se já está quente no pagecache, é basicamente um erro. OTOH, já escrevemos sobre o mais rápido que o memcpy! (que precisa dividir a largura de banda da memória principal entre leitura e gravação). Observe também que escrever na memória que '
rep movsb
(memcpy e memset otimizados em microcódigo, que evitam a RFO, desde a implementação de Andy Glew no P6 (Pentium Pro )).Até agora, isso é apenas uma prova de conceito, e o tratamento da nova linha está apenas aproximadamente correto. Está errado nas extremidades de um buffer de potência de 2. Com mais tempo de desenvolvimento. Estou confiante de que poderia encontrar uma maneira mais eficiente de inserir novas linhas também exatamente corretas, com sobrecarga pelo menos tão baixa quanto essa (em comparação com a saída de apenas espaços). Eu acho que isso é algo como 10 a 20%. Estou interessado apenas em saber o quão rápido poderíamos fazer essa execução, não em ter uma versão aprimorada, então deixarei essa parte como um exercício para o leitor, com comentários descrevendo algumas idéias.
Em um Haswell i5 em seu turbo máximo de 2,5 GHz, com RAM DDR3-1600MHz, o tempo produziu 100 GiB, mas diminuiu. (Cronometrado no cygwin64 no Win10 com o gcc5.4
-O3 -march=native
, omitido-funroll-loops
porque eu estava tendo bastante dificuldade em obter execuções decentes neste laptop emprestado. Deveria ter inicializado o Linux em um USB).escrevendo para / dev / null, a menos que seja especificado de outra forma.
wc -c
, com tamanho de buffer de 128 kB: 0,32s com CPU a 2,38 GHz (máximo de turbo de núcleo duplo). (tempos não dimensionados: real = 32.466s usuário = 11.468s sys = 41.092s, incluindo isso ewc
). Porém, apenas metade dos dados foi realmente copiada, porque meu programa bobo pressupõe que a gravação faça o buffer completo, mesmo que esse não seja o caso, e o cygwin write () faça apenas 64k por chamada em um canal.Portanto, com o SSE2, isso é cerca de 15 vezes mais rápido que o código escalar do @Nominal Animal. Com o AVX2, é cerca de 30 vezes mais rápido. Eu não tentei uma versão do código do Nominal que apenas usa em
write()
vez defwrite()
, mas presumivelmente para buffers grandes, o stdio geralmente fica fora do caminho. Se estiver copiando os dados, isso representaria muita desaceleração.Tempos para produzir 1 GB de dados em um Core2Duo E6600 (caches L2 compartilhados de 2,4 GHz, L1 privado 32kiB, L4 compartilhados com 4MiB), DDR2-533MHz no Linux 4.2 de 64 bits (Ubuntu 15.10). Ainda usando um tamanho de buffer de 128 kB para write (), ainda não exploramos essa dimensão.
escrevendo para / dev / null, a menos que seja especificado de outra forma.
wc -c
: 0.593s (sem escala: real = 59.266s usuário = 20.148s sys = 1m6.548s, incluindo o tempo de CPU do wc). O mesmo número de chamadas do sistema write () que o cygwin, mas na verdade canaliza todos os dados porque o Linux lida com todos os 128k de write () em um pipe.fwrite()
versão do NominalAnimal (gcc5.2-O3 -march=native
) é executada com./decdig 100 $((1024*1024*1024/200)) > /dev/null
: 3.19s +/- 0.1%, com 1,40 instrução por ciclo. -funroll-loops fez talvez uma pequena diferença.clang-3.8 -O3 -march=native
: 3,42s +/- 0,1%fwrite
parawc -c
: real = 3.980s usuário = 3.176s sys = 2.080sclang++-3.8 -O3 -march=native
): 22.885s +/- 0,07%, com 0,84 instruções por ciclo. (g ++ 5.2 foi um pouco mais lento: 22,98s). Escrever apenas uma linha de cada vez provavelmente doeu significativamente.tr < /dev/urandom | ...
: real = 41.430s usuário = 26.832s sys = 40.120s.tr
estava obtendo todo o núcleo da CPU para si próprio na maioria das vezes, gastando quase todo o tempo no driver do kernel, gerando bytes aleatórios e copiando-os para um pipe. O outro núcleo nessa máquina de núcleo duplo estava executando o restante do pipeline.time LC_ALL=C head -c512M </dev/urandom >/dev/null
: ou seja, apenas lendo tanta aleatoriedade sem tubulação: real = 35.018s usuário = 0.036s sys = 34.940s.LANG=en_CA.UTF-8
:: real = 4m32.634s usuário = 4m3.288s sys = 0m29.364.LC_ALL=C LANG=C
: real = 4m18.637s usuário = 3m50.324s sys = 0m29.356s. Ainda muito lento.dig3 = v%10
passo é o ponto de equilíbrio neste HW): 0,166s (instruções de 1,82 por ciclo) . Esse é basicamente o limite mais baixo para o que podemos chegar perto com o manuseio de nova linha perfeitamente eficiente.v%10
, 0.222 segundos +/- 0.4%, 2.12 instruções por ciclo. (Compilado com gcc5.2-march=native -O3 -funroll-loops
,. Desenrola os loops para ajudar nesse código neste hardware. Não o use cegamente, especialmente para programas grandes).Como isso é feito
Um PRNG rápido é obviamente essencial. O xorshift128 + pode ser vetorizado, para que você tenha dois ou quatro geradores de 64 bits em paralelo, em elementos de um vetor SIMD. Cada etapa produz um vetor completo de bytes aleatórios. ( Implementação de 256b AVX2 aqui com intrínsecas da Intel ). Optei pela escolha nominal de xorshift * da Nominal, porque a multiplicação de inteiros vetoriais de 64 bits só é possível no SSE2 / AVX2 com técnicas de precisão estendida .
Dado um vetor de bytes aleatórios, podemos dividir cada elemento de 16 bits em vários dígitos decimais. Produzimos vários vetores de elementos de 16 bits, cada um com um dígito ASCII + espaço ASCII . Armazenamos isso diretamente em nosso buffer de saída.
Minha versão original costumava
x / 6554
obter um dígito aleatório de cada elemento uint16_t de um vetor. É sempre entre 0 e 9, inclusive. É tendencioso9
, porque(2^16 -1 ) / 6554
é apenas 9.99923. (6554 = ceil ((2 ^ 16-1) / 10), que garante que o quociente seja sempre <10.)x/6554
pode ser calculado com uma multiplicação por uma constante "mágica" ( o ponto fixo recíproco ) e um deslocamento à direita do resultado da metade alta. Este é o melhor caso de divisão por uma constante; alguns divisores realizam mais operações e a divisão assinada exige trabalho extra.x % 10
tem um viés semelhante e não é tão barato de calcular. (a saída asm do gcc é equivalente ax - 10*(x/10)
, ou seja, uma multiplicação e subtração extras no topo da divisão usando um inverso multiplicativo modular.) Além disso, o bit mais baixo do xorshift128 + não é de alta qualidade , portanto é melhor dividir para obter entropia de bits altos ( qualidade e velocidade) do que o módulo para obter entropia de bits baixos.No entanto, podemos usar mais da entropia em cada uint16_t observando os dígitos decimais baixos, como a
digit()
função @ Nominal . Para obter o desempenho máximo, decidix/6554
usar os 3 dígitos decimais baixos e , para salvar um PMULLW e PSUBW (e provavelmente algum MOVDQA) versus a opção de qualidade mais alta, usar os 4 dígitos decimais baixos. x / 6554 é levemente afetado pelos três dígitos decimais baixos, portanto, existe alguma correlação entre os dígitos do mesmo elemento (separação de 8 ou 16 dígitos na saída ASCII, dependendo da largura do vetor).Eu acho que o gcc está dividindo por 100 e 1000, em vez de uma cadeia mais longa que sucessivamente se divide por 10, portanto, provavelmente não está diminuindo significativamente o comprimento da cadeia de dependência não transportada por loop que produz 4 resultados de cada saída PRNG. port0 (multiplicação e deslocamento de vetores) é o gargalo por causa dos inversos multiplicativos modulares e as mudanças no xorshift +, portanto é definitivamente útil salvar uma multiplicação de vetores.
O xorshift + é tão rápido que mesmo usando apenas ~ 3,3 bits de aleatoriedade a cada 16 (ou seja, 20% de eficiência) não é muito mais lento do que dividi-lo em vários dígitos decimais. Apenas aproximamos a distribuição uniforme, porque essa resposta é focada na velocidade, desde que a qualidade não seja muito ruim.
Qualquer tipo de comportamento condicional que mantenha um número variável de elementos exigiria muito mais trabalho. (Mas talvez ainda possa ser feito de maneira eficiente usando as técnicas de empacotamento à esquerda do SIMD . No entanto, isso fica menos eficiente para tamanhos de elementos pequenos; as tabelas de pesquisa de máscara aleatória gigantes não são viáveis e não há embaralhamento de faixa de AVX2 com menos de 32 Uma versão PSHUFB de 128b ainda pode gerar uma máscara em tempo real com o BMI2 PEXT / PDEP, como você pode no AVX2 com elementos maiores , mas é complicado porque um número inteiro de 64 bits contém apenas 8 bytes. nessa resposta tem algum código que pode funcionar para contagens mais altas de elementos.)
Se a latência do RNG for um gargalo, poderíamos ir ainda mais rápido executando dois vetores de geradores em paralelo, alternando qual deles usamos. O compilador ainda pode facilmente manter tudo em registros em um loop desenrolado, e isso permite que as duas cadeias de dependência sejam executadas em paralelo.
Na versão atual, detalhando a saída do PRNG, na verdade, gargalos na taxa de transferência da porta 0, não na latência do PRNG, portanto, não há necessidade disso.
O código: versão AVX2
Versão completa com mais comentários sobre o Godbolt compiler explorer .
Não é muito arrumado, desculpe, eu tenho que dormir e quero postar isso.
Para obter a versão SSE2,
s/_mm256/_mm
,s/256/128/
,s/v16u/v8u/
, e mudançavector_size(32)
para 16. Também alterar o incremento de nova linha de 4 * 16-4 * 8. (Como eu disse, o código é confuso e não está bem configurado para compilar duas versões. Não planejava originalmente fazer uma versão AVX2, mas eu realmente queria testar em uma CPU Haswell a que tinha acesso.)Compile com gcc, clang ou ICC (ou, esperançosamente, qualquer outro compilador que entenda o dialeto GNU C de C99 e as intrínsecas da Intel). As extensões vetoriais GNU C são altamente convenientes para que o compilador gere os números mágicos para divisão / módulo usando inversos multiplicativos modulares, e
__attribute__
s ocasionais são úteis.Isso pode ser escrito de forma portável, mas seria necessário mais código.
Notas de desempenho:
A loja sobreposta para inserir novas linhas possui uma sobrecarga significativa para decidir onde colocá-la (previsões incorretas de ramificação e gargalos de front-end no Core2), mas a própria loja não tem impacto no desempenho. Comentar apenas as instruções de armazenamento no asm do compilador (deixando todas as ramificações iguais) deixou o desempenho no Core2 completamente inalterado, com execuções repetidas dando o mesmo tempo a +/- menos de 1%. Portanto, concluo que o buffer / cache da loja lida com isso muito bem.
Ainda assim, usar algum tipo de janela rotativa
ascii_digitspace
com um elemento com uma nova linha pode ser ainda mais rápido, se desenrolarmos o suficiente para que qualquer contador / ramificação desapareça.Gravar em / dev / null é basicamente não operacional, portanto o buffer provavelmente permanece quente no cache L2 (256 kB por núcleo em Haswell). A aceleração perfeita de vetores 128b para 256b é esperada: não há instruções extras e tudo (incluindo as lojas) acontece com o dobro da largura. Porém, a ramificação de inserção de nova linha é obtida duas vezes mais. Infelizmente, não tive tempo na minha configuração de cywell do Haswell com essa parte
#ifdef
editada.2.5GHz * 32B / 13.7GB / s = 5.84 ciclos por loja AVX2 em Haswell. Isso é muito bom, mas poderia ser mais rápido. Talvez haja alguma sobrecarga nas chamadas do sistema cygwin do que eu pensava. Não tentei comentar isso na saída asm do compilador (o que garantiria que nada fosse otimizado).
O cache L1 pode sustentar um armazenamento de 32B por relógio, e o L2 não possui uma largura de banda muito menor (latência mais alta).
Quando examinei a IACA algumas versões atrás (sem a ramificação de novas linhas, mas apenas obtendo um vetor ASCII por vetor RNG), estava prevendo algo como um armazenamento de vetor 32B por 4 ou 5 relógios.
Eu esperava obter mais agilidade ao extrair mais dados de cada resultado RNG, com base em olhar para mim, considerando os guias de Agner Fog e outros recursos de otimização aos quais adicionei links no wiki de tags do SO x86 .)
Provavelmente seria significativamente mais rápido no Skylake , onde o número inteiro de vetores se multiplica e o deslocamento pode ser executado em duas vezes mais portas (p0 / p1) em comparação com Haswell (apenas p0). O xorshift e a extração de dígitos usam muitos turnos e multiplicam. ( Atualização: o Skylake o executa em 3,02 IPC, fornecendo-nos 3,77 ciclos por loja AVX2 de 32 bytes , com tempo de 0,030s por iteração de 1 GB, gravando
/dev/null
no Linux 4.15 no i7-6700k a 3.9GHz.Não requer o modo de 64 bits para funcionar bem . A versão SSE2 é tão rápida quando compilada
-m32
, porque não precisa de muitos registros vetoriais, e toda a matemática de 64 bits é feita em vetores, não em registros de uso geral.Na verdade, é um pouco mais rápido no modo de 32 bits no Core2, porque a macro-fusão de comparação / ramificação só funciona no modo de 32 bits, portanto há menos uops para o núcleo fora de ordem (18,3s (1,85 instruções por relógio) vs 16,9 s (2,0 IPC)). O tamanho do código menor, por não ter prefixos REX, também ajuda os decodificadores do Core2.
Além disso, alguns movimentos do vetor reg-reg são substituídos por cargas, já que nem todas as constantes se fixam mais no vetor regs. Como a taxa de transferência de carga do cache L1 não é um gargalo, isso realmente ajuda. (por exemplo, multiplicando por um vetor constante de
set1(10)
:movdqa xmm0, xmm10
/pmullw xmm0, xmm1
transforma-se emmovdqa xmm0, [constant]
/pmullw xmm0, xmm1
.) Como o MOVDQA reg-reg requer uma porta ALU, ele compete com o trabalho real que está sendo feito, mas uma carga MOVDQA compete apenas pela largura de banda da decodificação do front-end. (Ter um endereço de 4 bytes em muitas instruções cancela muito do ganho ao salvar os prefixos REX.Eu não ficaria surpreso se salvar ALU MOVDQA for de onde vêm os ganhos reais, já que o front-end deve acompanhar muito bem a média de 2,0 IPC.
Todas essas diferenças desaparecem em Haswell, onde tudo deve ser executado a partir do cache decodificado, se não o buffer de loopback. A macro fusão de ramo ALU + funciona nos dois modos desde Nehalem.
fonte
Aqui está uma solução que espero que seja simples de entender:
od
cria um fluxo uniforme de dígitos hexadecimais de/dev/random
.tr
se livrar das letras, mantendo apenas0-9
dígitosfold
garante que haja 100 dígitos por linhaawk
insere espaços dentro de linhashead
trunca a entrada para 1 gigabytefonte
Você pode usar o
jot
comando para isso:fonte
fmt
não tem uma opção de largura da meta. De qualquer forma, será exato porque todos os dígitos ocupam exatamente uma coluna!fmt
versão éfmt (GNU coreutils) 8.25
(Ubuntu 16.04)536870912
Isso é semelhante ao método de Stéphane Chazelas, no entanto, li 64 bits de uma só vez para melhorar o desempenho. A distribuição ainda é uniforme, mas agora você recebe 19 dígitos para cada 8 bytes, em vez de apenas 8 no melhor dos casos, como antes
Na plataforma de 32 bits, 9 dígitos serão lidos a cada vez, em vez de 19.
fonte
perl
não for compilado com suporte para quad.next if $n >= 1000000000; $s = sprintf("%09u", $n);
obter apenas 9 dígitos$n = unpack("Q")
se o quad não for suportado.BEGIN{$/=\4; $,=" "} $n = unpack("L");
também #<16e18
e divide por 16, obtém 18 dígitos 86,7% para 1,95 dpB. Com 32 bits,<4e9 /4
obtém 9 dígitos 93,1% para 2,10 dpB. Mas 5 bytes (como hexadecimal (H10))<1e12
fornecem 12 dígitos 90,9% para 2,18 dpB, ou dividem o hexadecimal ao meio e, a cada metade,<1e6
fornecem 6 dígitos 95,4% para 2,29 dpB; isso se aproxima do limite de log_10 (256) = 2,41.Eu meio que concordo com o Nominal Animal no uso de uma linguagem de programação compilada, se você precisar de velocidade. No entanto, você não precisa escrever seu próprio código RNG em C. O C ++ 11 oferece o excelente Mersenne Twister como parte de sua biblioteca padrão.
O código acima é razoavelmente simples e leva cerca de um minuto quando canalizo a saída para um arquivo. Podemos ir muito mais rápido criando uma string grande o suficiente para 100 dígitos e invadindo-a. Isso nos permite chamar cout todas as linhas e não todos os dígitos.
Esse código leva minha máquina por cerca de seis segundos. Lembre-se de que é uma saída padrão, então coloque-a em um arquivo.
Eu tenho algumas isenções de responsabilidade. Primeiro, estou escrevendo isso em um PC com Windows. Eu acho que as bibliotecas estão todas presentes no Linux, mas se eu estiver errado, não deixe de apontar.
Além disso, ele realmente gera exatamente meio bilhão de dígitos separados por espaço, o que é tecnicamente um gigabyte, mas talvez não seja exatamente o que você queria. Ele produz 5 milhões de linhas, 100 dígitos por linha. Se a diferença for importante, você pode aumentar o número de linhas. Na minha caixa do Windows, o arquivo parece ser um pouco maior que 10 ^ 9 bytes, o que eu acho que tem algo a ver com caracteres extras de nova linha.
fonte
/dev/null
o que seria muito mais rápido do que escrever para um arquivo realwrite()
chamada de sistema grande é um memcpy no pagecache, que bloqueia apenas se o kernel decidir fazer isso em vez de alocar mais espaço no buffer. Este programa deve afunilar apenas a E / S do disco quando a memória estiver fraca ou se você tiver usado o O_DIRECT para ignorar o pagecache. Se você estiverwrite()
em pedaços menores que o tamanho do cache, esperamos que seus dados entrem na memória principal apenas uma vez e o buffer reescrito no local permaneça quente no cache L2 ou L3.Depende da sua definição de "aleatório". Se você quer dizer criptograficamente aleatório, basta obter uma boa biblioteca e morder a bala, aguarde a execução.
Se você só precisa de algo que pareça aleatório, eis uma maneira fácil:
Pode levar uma hora para rodar em uma máquina lenta; rápido o suficiente e aleatório o suficiente para a maioria dos propósitos.
fonte
/dev/urandom
provavelmente será melhor do quegzip
, tanto em velocidade quanto em aleatoriedade.Get a file that is several Gb long
você precisará de um arquivo ** de pelo menos 8 GB "para obter um arquivo de 1 GBfonte
cat file | tr
quando você pode apenastr <file
. IIRC, você pode até<file tr
. Eu pensei que você estava falando sobre esse script shell parecendo desajeitado e lento, comodu | awk
depois de cada linha para verificar o tamanho e reabrindo o arquivo para acrescentar todas as linhas em vez de redirecionar para fora do loop.cat /dev/urandom | busy-cmd
é um daqueles casos raros em que pode fazer sentido, pois pode dividir a geração aleatória e o cmd ocupado entre os processadores. Não é muito para tr, mas faz a diferença para o Sam,od
por exemplo.