임의의 숫자가 포함 된 1GB 텍스트 파일을 생성하는 가장 빠른 방법은 무엇입니까?


52

bash 스크립트를 시도했지만 간단한 1MB 파일을 만드는 데 너무 오래 걸렸습니다. 대답은 /dev/randomor 사용하는 것이라고 생각 /dev/urandom하지만 여기의 다른 게시물은 이러한 것들을 사용하여 모든 종류의 데이터를 파일에 추가하는 방법 만 보여 주지만 숫자 만 추가하고 싶습니다.

그렇다면 0에서 9 사이의 숫자 만 포함하는 1GB 크기의 임의 파일을 만드는 데 사용할 수있는 명령이 있습니까?

편집 : 출력을 다음과 같이하고 싶습니다.

0 1 4 7 ..... 9
8 7 5 8 ..... 8
....
....
8 7 5 3 ..... 3

범위는 0-9이며 숫자 0, 1, 2, 3, 4, 5, 6, 7, 8 및 9만을 의미합니다. 또한 공백으로 구분되고 줄 당 100 줄까지 줄 n수 까지 있어야 합니다. 이 n은 신경 쓰지 않는 것입니다. 최종 크기가 1GB가되기를 바랍니다.

편집 : 우분투 16.04 LTS를 사용하고 있습니다



21
당신은 아마도 "무작위"의 의미를 말해야합니다. 암호 강도는 무작위입니까, 아니면 의사 난수 시퀀스가 ​​적절합니까?
Toby Speight

4
@posixKing : 내 대답은 확실히 혀로 들리지만 실제로 그러한 작업을 위해 C 프로그램을 작성하는 것은 제안하지 않습니다! -, 이러한 거대한 데이터 세트를 정기적으로 생성하거나 자주 생성하는 경우 시간이 절약 될 수 있습니다. (노트북에서 약 10 초 안에 1GB의 공백으로 구분 된 숫자가 생성됩니다.) 그러나 이것이 일회성이라면 C 프로그램 작성에 대해 생각조차하지 마십시오 (프로그래밍을 좋아하지 않는 한 이것을 고려하십시오) 연습 등); 쉘 명령과 유틸리티는 총 시간과 노력을 덜 들이고 작업을 수행합니다.
공칭 동물

7
이것은 매우 빠르고 RFC 1149.5를 준수합니다.yes 4 | tr '\n' ' ' | fold -w 200 | head -c1G
Matthew Crumley

답변:


38

이것은 질문의 제목 때문에 부분적으로 뺨에 답입니다.

"가장 빠른 방법 ..." 을 찾으면 거의 항상 전문화 된 도구가됩니다. 이 "답변"에는 그러한 도구 중 하나가 표시되어 실험 할 수 있습니다.

한 번만 또는 매우 드물게 수행하는 작업을위한 특수 도구를 조사해서는 안되므로 이것은 심각한 대답이 아닙니다. 실제로 작업을 수행하는 것보다 도구를 찾고 도구에 대해 배우는 데 더 많은 시간을 소비하게됩니다. 쉘과 같은 유틸리티 bash와는 awk가장 빠른 아니지만, 당신은 일반적으로 쓸 수있는 한 라이너를 몇 초 지출, 작업을 달성하기 위해. perl학습 곡선 perl이 가파르 지만 더 나은 스크립팅 언어도 사용할 수 있습니다 . python반면에 다소 느린 I / O로 인해 약간의 장애가 있습니다. 그러나 기가 바이트의 데이터를 필터링하거나 생성 할 때만 문제가됩니다.

어쨌든 다음 C89 예제 프로그램 (사용 가능한 경우에만보다 높은 정확도의 클럭에 POSIX.1을 사용함)은 약 100MB / s의 생성 속도를 달성해야합니다 (Linux에서 Intel i5-4200U 프로세서가 장착 된 랩톱에서 테스트 됨). 에 /dev/null) 꽤 좋은 의사 난수 생성기를 사용하여. (코드는 xorshift64 * 및 배제 방법을 사용하여 숫자 바이어스를 피 하므로 MatrixRank 테스트를 제외한 모든 BigCrunch 테스트를 통과해야합니다 .)

10 진수 .c :

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <locale.h>
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>

/* This program is licensed under the CC0 license,
       https://creativecommons.org/publicdomain/zero/1.0/
   In other words, this is dedicated to the public domain.
   There are no warranties either, so if something breaks,
   you only have yourself to blame.
*/

#if _POSIX_C_SOURCE-199309 >= 0
static uint64_t time_seed(void)
{
    struct timespec  ts;

    if (clock_gettime(CLOCK_REALTIME, &ts))
        return (uint64_t)time(NULL);

    return (uint64_t)ts.tv_sec
         ^ (((uint64_t)ts.tv_nsec) << 32);
}
#else
static uint64_t time_seed(void)
{
    return (uint64_t)time(NULL);
}
#endif

/* Preferred output I/O block size.
 * Currently, about 128k blocks yield
 * maximum I/O throughput on most devices.
 * Note that this is a heuristic value,
 * and may be increased in the future.
*/
#ifndef  IO_BLOCK_SIZE
#define  IO_BLOCK_SIZE  262144
#endif

/* This is the Xorshift* pseudo-random number generator.
 * See https://en.wikipedia.org/wiki/Xorshift#xorshift.2A
 * for details. This is an incredibly fast generator that
 * passes all but the MatrixRank test of the BigCrush
 * randomness test suite, with a period of 2^64-1.
 * Note that neither xorshift_state, nor the result of
 * this function, will ever be zero.
*/
static uint64_t xorshift_state;

static uint64_t xorshift_u64(void)
{
    xorshift_state ^= xorshift_state >> 12;
    xorshift_state ^= xorshift_state << 25;
    xorshift_state ^= xorshift_state >> 27;
    return xorshift_state * UINT64_C(2685821657736338717);
}

/* This function returns a number between (inclusive)
 * 0 and 999,999,999,999,999,999 using xorshift_u64()
 * above, using the exclusion method. Thus, there is
 * no bias in the results, and each digit should be
 * uniformly distributed in 0-9.
*/
static uint64_t quintillion(void)
{
    uint64_t result;

    do {
        result = xorshift_u64() & UINT64_C(1152921504606846975);
    } while (!result || result > UINT64_C(1000000000000000000));

    return result - UINT64_C(1);
}

/* This function returns a single uniformly random digit.
*/
static unsigned char digit(void)
{
    static uint64_t       digits_cache = 0;
    static unsigned char  digits_cached = 0;
    unsigned char         retval;

    if (!digits_cached) {
        digits_cache = quintillion();
        digits_cached = 17; /* We steal the first one! */
    } else
        digits_cached--;

    retval = digits_cache % (uint64_t)(10);
    digits_cache /= (uint64_t)(10);

    return retval;
}

static int parse_ulong(const char *src, unsigned long *to)
{
    const char   *end = src;
    unsigned long value;

    if (!src)
        return errno = EINVAL;

    errno = 0;
    value = strtoul(src, (char **)&end, 0);
    if (errno)
        return errno;

    if (end == src)
        return errno = EINVAL;
    while (*end)
        if (isspace(*end))
            end++;
        else
            return errno = EINVAL;

    if (to)
        *to = value;
    return 0;
}

int main(int argc, char *argv[])
{
    unsigned long lines, cols, line, col, seed;

    /* When parsing the command-line parameters,
     * use locale conventions. */
    setlocale(LC_ALL, "");

    /* Standard output should be fully buffered, if possible.
     * This only affects output speed, so we're not too worried
     * if this happens to fail. */
    (void)setvbuf(stdout, NULL, _IOFBF, (size_t)IO_BLOCK_SIZE);

    if (argc < 3 || argc > 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s COLS LINES [ SEED ]\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program generates random decimal digits\n");
        fprintf(stderr, "0 - 9, separated by spaces, COLS per line,\n");
        fprintf(stderr, "LINES lines.  In total, COLS*LINES*2 bytes\n");
        fprintf(stderr, "will be used.\n");
        fprintf(stderr, "\n");
        fprintf(stderr, "SEED is the optional seed for the Xorshift64*\n");
        fprintf(stderr, "pseudo-random number generator used in this program.\n");
        fprintf(stderr, "If omitted, current time is used as the seed.\n");
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    if (parse_ulong(argv[1], &cols) || cols < 1UL) {
        fprintf(stderr, "%s: Invalid number of digits per line.\n", argv[1]);
        return EXIT_FAILURE;
    }
    if (parse_ulong(argv[2], &lines) || lines < 1UL) {
        fprintf(stderr, "%s: Invalid number of lines.\n", argv[2]);
        return EXIT_FAILURE;
    }

    if (argc > 3) {
        if (parse_ulong(argv[3], &seed)) {
            fprintf(stderr, "%s: Invalid Xorshift64* seed.\n", argv[3]);
            return EXIT_FAILURE;
        }
    } else
        seed = time_seed();

    /* Since zero seed is invalid, we map it to ~0. */
    xorshift_state = seed;
    if (!xorshift_state)
        xorshift_state = ~(uint64_t)0;

    /* Discard first 1000 values to make the initial values unpredictable. */
    for (col = 0; col < 1000; col++)
        xorshift_u64();

    for (line = 0UL; line < lines; line++) {
        fputc('0' + digit(), stdout);
        for (col = 1UL; col < cols; col++) {
            fputc(' ', stdout);
            fputc('0' + digit(), stdout);
        }
        fputc('\n', stdout);

        /* Check for write errors. */
        if (ferror(stdout))
            return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

라인 버퍼로 전환 fwrite()하면 한 번에 각 숫자를 출력하는 대신 한 번만 더 빠르게 만들 수 있습니다 . 출력이 블록 장치 인 경우 부분 (2의 제곱이 아닌) 쓰기를 피하기 위해 스트림을 완전히 버퍼링 한 상태로 유지합니다.

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <locale.h>
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>

#if _POSIX_C_SOURCE-199309 >= 0
static uint64_t time_seed(void)
{
    struct timespec  ts;

    if (clock_gettime(CLOCK_REALTIME, &ts))
        return (uint64_t)time(NULL);

    return (uint64_t)ts.tv_sec
         ^ (((uint64_t)ts.tv_nsec) << 32);
}
#else
static uint64_t time_seed(void)
{
    return (uint64_t)time(NULL);
}
#endif

/* Preferred output I/O block size.
 * Currently, about 128k blocks yield
 * maximum I/O throughput on most devices.
 * Note that this is a heuristic value,
 * and may be increased in the future.
*/
#ifndef  IO_BLOCK_SIZE
#define  IO_BLOCK_SIZE  262144
#endif

/* This is the Xorshift* pseudo-random number generator.
 * See https://en.wikipedia.org/wiki/Xorshift#xorshift.2A
 * for details. This is an incredibly fast generator that
 * passes all but the MatrixRank test of the BigCrush
 * randomness test suite, with a period of 2^64-1.
 * Note that neither xorshift_state, nor the result of
 * this function, will ever be zero.
*/
static uint64_t xorshift_state;

static uint64_t xorshift_u64(void)
{
    xorshift_state ^= xorshift_state >> 12;
    xorshift_state ^= xorshift_state << 25;
    xorshift_state ^= xorshift_state >> 27;
    return xorshift_state * UINT64_C(2685821657736338717);
}

/* This function returns a number between (inclusive)
 * 0 and 999,999,999,999,999,999 using xorshift_u64()
 * above, using the exclusion method. Thus, there is
 * no bias in the results, and each digit should be
 * uniformly distributed in 0-9.
*/
static uint64_t quintillion(void)
{
    uint64_t result;

    do {
        result = xorshift_u64() & UINT64_C(1152921504606846975);
    } while (!result || result > UINT64_C(1000000000000000000));

    return result - UINT64_C(1);
}

/* This function returns a single uniformly random digit.
*/
static unsigned char digit(void)
{
    static uint64_t       digits_cache = 0;
    static unsigned char  digits_cached = 0;
    unsigned char         retval;

    if (!digits_cached) {
        digits_cache = quintillion();
        digits_cached = 17; /* We steal the first one! */
    } else
        digits_cached--;

    retval = digits_cache % (uint64_t)(10);
    digits_cache /= (uint64_t)(10);

    return retval;
}

static int parse_ulong(const char *src, unsigned long *to)
{
    const char   *end = src;
    unsigned long value;

    if (!src)
        return errno = EINVAL;

    errno = 0;
    value = strtoul(src, (char **)&end, 0);
    if (errno)
        return errno;

    if (end == src)
        return errno = EINVAL;
    while (*end)
        if (isspace(*end))
            end++;
        else
            return errno = EINVAL;

    if (to)
        *to = value;
    return 0;
}

int main(int argc, char *argv[])
{
    unsigned long lines, cols, line, col, seed;
    char         *oneline;

    /* When parsing the command-line parameters,
     * use locale conventions. */
    setlocale(LC_ALL, "");

    /* Standard output should be fully buffered, if possible.
     * This only affects output speed, so we're not too worried
     * if this happens to fail. */
    (void)setvbuf(stdout, NULL, _IOFBF, (size_t)IO_BLOCK_SIZE);

    if (argc < 3 || argc > 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s COLS LINES [ SEED ]\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program generates random decimal digits\n");
        fprintf(stderr, "0 - 9, separated by spaces, COLS per line,\n");
        fprintf(stderr, "LINES lines.  In total, COLS*LINES*2 bytes\n");
        fprintf(stderr, "will be used.\n");
        fprintf(stderr, "\n");
        fprintf(stderr, "SEED is the optional seed for the Xorshift64*\n");
        fprintf(stderr, "pseudo-random number generator used in this program.\n");
        fprintf(stderr, "If omitted, current time is used as the seed.\n");
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    if (parse_ulong(argv[1], &cols) || cols < 1UL) {
        fprintf(stderr, "%s: Invalid number of digits per line.\n", argv[1]);
        return EXIT_FAILURE;
    }
    if (parse_ulong(argv[2], &lines) || lines < 1UL) {
        fprintf(stderr, "%s: Invalid number of lines.\n", argv[2]);
        return EXIT_FAILURE;
    }

    if (argc > 3) {
        if (parse_ulong(argv[3], &seed)) {
            fprintf(stderr, "%s: Invalid Xorshift64* seed.\n", argv[3]);
            return EXIT_FAILURE;
        }
    } else
        seed = time_seed();

    /* Since zero seed is invalid, we map it to ~0. */
    xorshift_state = seed;
    if (!xorshift_state)
        xorshift_state = ~(uint64_t)0;

    /* Discard first 1000 values to make the initial values unpredictable. */
    for (col = 0; col < 1000; col++)
        xorshift_u64();

    /* Allocate memory for a full line. */
    oneline = malloc((size_t)(2 * cols + 1));
    if (!oneline) {
        fprintf(stderr, "Not enough memory for %lu column buffer.\n", cols);
        return EXIT_FAILURE;
    }

    /* Set spaces and terminating newline. */
    for (col = 0; col < cols; col++)
        oneline[2*col + 1] = ' ';
    oneline[2*cols-1] = '\n';

    /* Not needed, but in case a code modification treats it as a string. */
    oneline[2*cols] = '\0';

    for (line = 0UL; line < lines; line++) {
        for (col = 0UL; col < cols; col++)
            oneline[2*col] = digit();

        if (fwrite(oneline, 2*cols, 1, stdout) != 1)
            return EXIT_FAILURE; 
    }

    /* Check for write errors. */
    if (ferror(stdout))
        return EXIT_FAILURE;

    return EXIT_SUCCESS;
}

참고 : 두 숫자 모두 2016-11-18에 편집되어 숫자의 균일 한 분포 를 보장합니다 (0은 제외됩니다. 예를 들어 다양한 의사 난수 생성기에 대한 비교 및 ​​세부 사항은 여기 참조 ).

예를 들어 컴파일

gcc -Wall -O2 decimal-digits.c -o decimal-digits

선택적으로 /usr/bin사용 하기 위해 시스템 전체에 설치

sudo install -o root -g root -m 0755 decimal-digits /usr/bin

행당 자릿수와 행 수를 사용합니다. 때문에 1000000000 / 100 / 2 = 5000000(오백 만 2로 나눈 열로 나누어 총 바이트), 당신은 사용할 수 있습니다

./decimal-digits 100 5000000 > digits.txt

digits.txtOP가 원하는대로 기가 바이트 크기를 생성합니다 .

프로그램 자체는 효율성을 염두에두고 읽기 편하게 작성되었습니다. 필자의 의도는 일반적인 C 인터페이스 대신 POSIX.1 및 저수준 I / O를 사용하는 코드의 효율성을 보여 주려는 것이 아니라 노력과 함께 어떤 종류의 균형이 있는지 쉽게 확인할 수 있도록하는 것입니다. 한 줄짜리 또는 짧은 쉘 또는 awk 스크립틀릿과 비교하여 전용 도구 개발과 성능 비교.

GNU C 라이브러리를 사용하면 fputc()모든 문자 출력에 대해 함수를 호출하면 (간접 함수 호출 또는 조건부) 매우 작은 오버 헤드가 발생합니다. FILE인터페이스는 실제로 매우 복잡하고 다재다능합니다. 이 특정 Intel Core i5-4200U 랩톱에서 출력을로 리디렉션하면 /dev/null첫 번째 (fputc) 버전은 약 11 초가 걸리지 만 한 번에 한 줄 버전은 1.3 초 밖에 걸리지 않습니다.

나는 종종 거대한 데이터 세트를 가지고 노는 것을 좋아하기 때문에 그러한 프로그램과 생성기를 자주 작성합니다. 나는 그렇게 이상하다. 예를 들어, 나는 모든 유한 양의 IEEE-754 부동 소수점 값을 텍스트 파일로 인쇄하는 프로그램을 작성하여 구문 분석 할 때 정확히 동일한 값을 산출하기에 충분한 정밀도를 가졌습니다. 파일 크기는 몇 기가 바이트 (4G 정도 정도)였습니다. float생각할 수 있듯이 유한 한 긍정적 인 것이 많지 않습니다 . 나는 이것을 사용하여 그러한 데이터를 읽고 구문 분석하는 구현을 비교했습니다.

OP와 같은 일반적인 사용 사례의 경우 셸 스크립트와 스크립틀릿과 한 줄짜리가 더 나은 방법입니다. 전체 작업을 수행하는 데 소요되는 시간이 줄어 듭니다. (매일 다른 파일을 필요로하거나 다른 파일을 필요로하는 사람들이 많을 경우를 제외하고는 위와 같은 전용 도구 인 드문 경우지만 보전 노력이 필요할 수 있습니다.)


아마도 mmap()최고의 I / O 속도로가는 가장 쉬운 방법 일 것 입니다. 그러나 어떤 주장을하기 전에 벤치 마크를하십시오!
Toby Speight

@TobySpeight : Linux에서는 일반적으로을 사용하는 저수준 I / O가보다 write()빠릅니다 mmap(). fwrite()많이 느리지 않습니다. 예, 나는 그것을 벤치마킹했습니다 (이 특정 예에서는 아닙니다). write()큰 청크 (262144, 524288 또는 1048576 바이트)에서는 다른 방법보다 성능이 뛰어납니다. fputc()GNU C 라이브러리에서 구현 된 버전 (나도 광범위하게 벤치 마크 한)은 여러 가지 이유로 느립니다. 특히, 추가 된 모든 문자에 대해 조건부 점프 또는 간접 호출을 수행해야하는 구현; 약간의 오버 헤드가 발생하여 종종 더해집니다.
공칭 동물

관심이없는 경우-다른 답변과 성능을 비교 했습니까?
디지털 외상

2
@DigitalTrauma : 방금 당신을 위해 출력을 리디렉션했습니다 /dev/null. Stéphane Chazelas의 스크립틀릿 은 약 52 초가 걸립니다. 펄 스 니펫 ( head필터링 포함 ) 약 58 초; 당신의 shuf조각은, 69 초 정도 소요됩니다 (정확한 타이밍으로 만 못해 더 이상 걸릴 붙여 넣기를 가정 SHUF 시간을 측정). James Hollis의 C ++ 11 라인 타임 프로그램은 14 초가 걸립니다. 위의 프로그램은 10 초가 걸립니다.
명목 동물

3
(위의 내 생각의 기차를 잃어 버렸다. 죄송합니다.) 포인트는, 여기에서 충분히 임의적이지만 매우 빠른 PRNG 인 올바른 알고리즘을 고르면 거의 10 배의 속도 증가를 낳았습니다. (내 프로그램의 후자 버전은 쉘 또는 펄 스 니펫보다 약 40 배 빠릅니다.) 이것은 일반적입니다. 아마도 위의 답변에서 프로그램을작성할 때 올바른 알고리즘을 선택하는 것을 강조했을 것 입니까? (반면에 이것은 프로그래밍 질문이 아니라 많은 숫자를 생성하는 방법에 관한 유닉스 / 리눅스 질문입니다.)
Nominal Animal

81

이:

 LC_ALL=C tr '\0-\377' \
             '[0*25][1*25][2*25][3*25][4*25][5*25][6*25][7*25][8*25][9*25][x*]' \
    < /dev/urandom |
    tr -d x |
    fold -w 1 |
    paste -sd "$(printf '%99s\\n')" - |
    head -c1G

( head을 지원 하는 구현으로 가정 -c) 내 시스템에서 상당히 빠른 것으로 보입니다.

tr전체 바이트 범위 (0에서 255, 0에서 0377까지 8 진수)를 변환합니다. 25 번째 첫 바이트는 0으로, 다음 25는 1로 ... 25는 나머지 (250에서 255)를 "x"로 변환합니다. tr -d x균일 분포를 원하기 때문에 (로 )를 폐기 하고 (고유 /dev/urandom분포 자체가 있다고 가정 ) 일부 숫자에 치우침을주지 마십시오.

바이트의 97 %에 대해 하나의 숫자를 생성합니다 /dev/urandom. fold -w 1한 줄에 한 자리 숫자로 만듭니다. paste -s99 개의 공백 문자와 하나의 개행 문자로 구성되는 구분 기호 목록과 함께 호출되므로 각 행에 100 개의 공백으로 구분 된 숫자가 있습니다.

head -c1G그것의 첫 번째 GiB (2 30 )를 얻을 것 입니다. 마지막 줄은 잘리고 구분되지 않습니다. 2 30 -1로 자르고 누락 된 줄 바꿈을 수동으로 추가하거나 200 바이트 줄 중 5 천만 인 10 9 바이트로 자를 수 있습니다 ( head -n 50000000표준 / 휴대용 명령이 됨).

이러한 타이밍 ( zsh쿼드 코어 시스템에서 획득 )은 CPU 시간이 소비되는 위치를 나타냅니다.

LC_ALL=C tr '\0-\377'  < /dev/urandom  0.61s user 31.28s system 99% cpu 31.904 total
tr -d x  1.00s user 0.27s system 3% cpu 31.903 total
fold -w 1  14.93s user 0.48s system 48% cpu 31.902 total
paste -sd "$(printf '%99s\\n')" -  7.23s user 0.08s system 22% cpu 31.899 total
head -c1G > /dev/null  0.49s user 1.21s system 5% cpu 31.898 total

첫 번째 tr는 병목이며 커널에서 보낸 대부분의 시간입니다 (난수 생성을 가정합니다). 타이밍은 대략 바이트를 얻을 수있는 속도 /dev/uramdom(약 19MiB / s와 32MiB / s의 속도로 0.97 바이트의 / dev / urandom마다 2 바이트를 생성합니다)와 거의 일치합니다. fold매 바이트마다 줄 바꿈 문자를 삽입하는 데 불합리한 CPU 시간 (15 초)을 소비하는 것처럼 보이지만 내 경우에는 다른 CPU에서 작동하므로 전체 시간에 영향을 미치지 않습니다 ( -b옵션을 추가하면 약간 더 많이 만듭니다) 효율적인 dd cbs=1 conv=unblock) 더 나은 대안처럼 보인다.

당신은 멀리 할 수있는 head -c1G과 (파일 크기에 제한을 설정하여 몇 초 면도 limit filesize 1024mzshulimit -f "$((1024*1024))"(를 포함하여 대부분 껍질 zsh)) 대신 서브 쉘에서합니다.

각 바이트 당 2 자리를 추출하면 개선 될 수 있지만 다른 접근 방식이 필요합니다. 위의 내용은 tr256 바이트 배열에서 각 바이트를 조회하기 때문에 매우 효율적 입니다. 한 번에 2 바이트에 대해서는 그렇게 할 수 없으며, hexdump -e '1/1 "%02u"'더 복잡한 알고리즘을 사용하여 바이트의 텍스트 표현을 계산하는 것과 같은 것을 사용하면 난수 생성 자체보다 비쌉니다. 그래도 내 경우와 같이 여분의 시간이 소요되는 CPU 코어가 있으면 몇 초 동안 면도를 계속 할 수 있습니다.

와:

< /dev/urandom LC_ALL=C tr '\0-\377' '\0-\143\0-\143[x*]' |
  tr -d x |
  hexdump -n250000000 -ve '500/1 "%02u" "\n"' |
  fold -w1 |
  paste -sd "$(printf '%99s\\n')" - > /dev/null

나는 얻는다 (그러나 여기에 1,073,741,824와 반대로 1,000,000,000 바이트 임) :

LC_ALL=C tr '\0-\377' '\0-\143\0-\143[x*]' < /dev/urandom  0.32s user 18.83s system 70% cpu 27.001 total
tr -d x  2.17s user 0.09s system 8% cpu 27.000 total
hexdump -n250000000 -ve '500/1 "%02u" "\n"'  26.79s user 0.17s system 99% cpu 27.000 total
fold -w1  14.42s user 0.67s system 55% cpu 27.000 total
paste -sd "$(printf '%99s\\n')" - > /dev/null  8.00s user 0.23s system 30% cpu 26.998 total

전반적으로 더 많은 CPU 시간이지만 4 개의 CPU 코어간에 더 잘 분산되므로 벽시계 시간이 줄어 듭니다. 병목 현상이 발생했습니다 hexdump.

ddline-based 대신에 사용하면 fold실제로 hexdump필요한 작업량을 줄이고 CPU 간의 작업 균형을 향상시킬 수 있습니다.

< /dev/urandom LC_ALL=C tr '\0-\377' '\0-\143\0-\143[x*]' |
  tr -d x |
  hexdump -ve '"%02u"' |
  dd bs=50000 count=10000 iflag=fullblock status=none cbs=1 conv=unblock |
  paste -sd "$(printf '%99s\\n')" -

(여기서 GNU dd를 가정 하고 iflag=fullblockstatus=none)는 다음을 제공합니다.

LC_ALL=C tr '\0-\377' '\0-\143\0-\143[x*]' < /dev/urandom  0.32s user 15.58s system 99% cpu 15.915 total
tr -d x  1.62s user 0.16s system 11% cpu 15.914 total
hexdump -ve '"%02u"'  10.90s user 0.32s system 70% cpu 15.911 total
dd bs=50000 count=10000 iflag=fullblock status=none cbs=1 conv=unblock  5.44s user 0.19s system 35% cpu 15.909 total
paste -sd "$(printf '%99s\\n')" - > /dev/null  5.50s user 0.30s system 36% cpu 15.905 total

병목 현상 인 난수 생성으로 돌아갑니다.

이제 @OleTange가 지적한 것처럼 openssl유틸리티 가 있으면 유틸리티를 사용하여 더 빠른 (특히 AES 명령이있는 프로세서에서) 의사 랜덤 바이트 생성기를 얻을 수 있습니다.

</dev/zero openssl enc -aes-128-ctr -nosalt -pass file:/dev/urandom

내 시스템에서보다 초당 15 배 많은 바이트를 뿜어 /dev/urandom냅니다. ( 사용 사례에 적용되는 경우 암호로 안전한 무작위 소스의 관점에서 비교하는 방법에 대해서는 언급 할 수 없습니다 ).

</dev/zero openssl enc -aes-128-ctr -nosalt -pass file:/dev/urandom 2> /dev/null | 
  LC_ALL=C tr '\0-\377' '\0-\143\0-\143[x*]' |
  tr -d x |
  hexdump -ve '"%02u"' |
  dd bs=50000 count=10000 iflag=fullblock status=none cbs=1 conv=unblock |
  paste -sd "$(printf '%99s\\n')" -

이제 다음을 제공합니다.

openssl enc -aes-128-ctr -nosalt -pass file:/dev/urandom < /dev/zero 2>   1.13s user 0.16s system 12% cpu 10.174 total
LC_ALL=C tr '\0-\377' '\0-\143\0-\143[x*]'  0.56s user 0.20s system 7% cpu 10.173 total
tr -d x  2.50s user 0.10s system 25% cpu 10.172 total
hexdump -ve '"%02u"'  9.96s user 0.19s system 99% cpu 10.172 total
dd bs=50000 count=10000 iflag=fullblock status=none cbs=1 conv=unblock  4.38s user 0.20s system 45% cpu 10.171 total
paste -sd "$(printf '%99s\\n')" - > /dev/null

hexdump병목 현상으로 돌아갑니다 .

여전히 여분의 CPU가 있으므로 3 개를 hexdump병렬로 실행할 수 있습니다 .

</dev/zero openssl enc -aes-128-ctr -nosalt -pass file:/dev/urandom 2> /dev/null | 
  LC_ALL=C tr '\0-\377' '\0-\143\0-\143[x*]' |
  tr -d x |
  (hexdump -ve '"%02u"' <&3 & hexdump -ve '"%02u"' <&3 & hexdump -ve '"%02u"') 3<&0 |
  dd bs=50000 count=10000 iflag=fullblock status=none cbs=1 conv=unblock |
  paste -sd "$(printf '%99s\\n')" -

( 백그라운드에서 실행될 때 / dev / null에서 닫기 명령의 stdin <&3이외의 쉘에 필요합니다 zsh).

이제 6.2 초로 줄었고 CPU가 거의 완전히 활용되었습니다.


3
방금 이전 답변을 삭제하고이 답변에 투표했습니다. 요구 사항 중 일부를 얻지 못했습니다. 좋은 대답 btw.
Marcelo

3
각 패스마다 여러 자리를 생성하지 않는 이유는 무엇입니까? 바이트 단위로 읽더라도 매번 2 자리 숫자를 생성 할 수 있습니다
phuclv

@ LưuVĩnhPhúc, perl어쨌든 상당히 느린 변형을 제거했습니다 . 해당 tr | 접착 방식으로 바이트 당 2 자리 숫자를 얻을 수 없습니다.
Stéphane Chazelas

나는 afk이거나 직접 시도하지만, 한 번에 42 바이트를 100-102 자릿수로 변환하여 시도 할 수 있습니다 bc(0, 1 또는 2 개의 최상위 숫자를 삭제하십시오).
Eric Towers

gitlab.com/ole.tange/tangetools/tree/master/rand 는 초당 1-4GB의 의사 난수 (AES 품질)를 생성합니다.
Ole Tange

23

shuf사용 가능한 경우 (최근 GNU coreutils에서 가능) 다음을 수행 할 수 있습니다.

time shuf -r -n $((512*1024*1024)) -i 0-9 | paste -sd "$(printf '%99s\\n')" -

내 VM에서 이것은 이제 3 : 4 비율로 스테판의 답변보다 약간 느립니다.


shuf내 회사 PC에는 -r, 너무도 fmt없습니다-g
phuclv

2
@ LưuVĩnhPhúc Yep-YMMV. core-utils 버전 8.25에는 이러한 기능이 있지만 8.4에는 없습니다. 어떤 버전을 사용하고 있습니까?
디지털 외상

1
저는 coreutils 8.13
phuclv를

@ StéphaneChazelas 영리한 paste/ printf트릭-감사합니다. 당신의 대답은 이제 더 빠릅니다.
디지털 외상

17

매우 높은 품질의 임의성이 필요하지 않고 균일 한 분포가 충분하면 특히 SSE2 또는 AVX2가있는 x86과 같은 효율적인 SIMD 정수 벡터가있는 최신 CPU에서 매우 빠르게 진행할 수 있습니다 .

우리 둘 다 같은 생각을 가지고 있지만 x86에 대해 수동으로 벡터화 되었으므로 이것은 @NominalAnimal의 대답 과 같습니다 . (그리고 더 나쁜 품질의 난수를 가지고 있지만 여전히 많은 유스 케이스에 충분할 것입니다.) 이것은 2.5GHz Intel Haswell에서 ~ 13GB / s의 ASCII 출력에서 ​​@Nominal의 코드보다 약 15 배 또는 30 배 빠릅니다. AVX2가있는 CPU. 그것은 여전히 ​​이론적 인 최대 주 메모리 대역폭 (듀얼 채널 DDR3-1600은 약 25.6GB / s)보다 적지 만 / dev / null에 쓰는 타이밍이어서 실제로 캐시에 뜨겁게 유지되는 버퍼를 다시 작성합니다. Skylake는 Haswell보다이 코드를 훨씬 빠르게 실행해야합니다 (이 답변의 맨 아래 참조).

실제로 디스크에 대한 I / O 병목 현상이나 어딘가에 파이프를 연결한다고 가정하면 빠른 구현은 CPU가 유휴 상태보다 더 높은 클럭을 필요로하지 않음을 의미합니다. 총 에너지를 훨씬 적게 사용하여 결과를 생성합니다. (배터리 수명 / 열 / 지구 온난화.)

이것은 너무 빠르기 때문에 디스크에 기록하고 싶지 않을 것입니다. 필요에 따라 다시 생성 하십시오 (같은 데이터를 다시 원할 경우 동일한 시드에서). 모든 CPU를 사용할 수있는 다중 스레드 프로세스에 피드를 제공하려는 경우에도 데이터를 파이프하기 위해이를 실행하면 L3 캐시 (및이를 작성한 코어의 L2 캐시)에서 매우 뜨겁게 유지됩니다. 적은 CPU 시간. (그러나 파이핑은에 비해 많은 오버 헤드를 기록한다는 점에 유의하십시오 /dev/null. Skylake i7-6700k에서 파이핑 wc -c또는 입력을 읽거나 버리는 다른 프로그램에 파이핑하는 것은 쓰기보다 약 8 배 느리고/dev/null 70 % 만 사용합니다. CPU 그러나 3.9GHz CPU에서는 여전히 4.0GB / s입니다.

빠른 PCIe 연결 SSD에서도 다시 읽는 것보다 속도가 더 빠르지 만 IDK가 더 효율적인 경우 IDK (벡터 정수 곱셈기는 매우 바쁘고 다른 AVX2와 함께 전력이 많이 소모 될 수 있음) 256b 벡터 ALU). OTOH, 디스크에서 읽은 CPU 시간이이 입력을 처리하는 모든 코어를 최대한으로 사용하는 것에서 얼마나 많은 시간이 걸리는지 알 수 없습니다. 128k 청크에서 다시 생성되는 컨텍스트 스위치는 파일 시스템 / pagecache 코드를 실행하고 디스크에서 데이터를 읽도록 페이지를 할당하는 것과 경쟁이 치열합니다. 물론 페이지 캐시에서 이미 뜨겁다면 기본적으로 memcpy입니다. OTOH, 우리는 이미 memcpy만큼 빨리 글을 씁니다! (읽기와 쓰기 사이에 주 메모리 대역폭을 분리해야 함). (또한 메모리에 쓰는 것은rep movsb( Andy Glew의 P6 (Pentium Pro) 구현) 때문에 RFO를 피하는 마이크로 코드로 memcpy 및 memset을 최적화 했습니다 ).


지금까지 이것은 개념 증명 일 뿐이며 줄 바꿈 처리는 거의 정확합니다. 2의 제곱 버퍼 끝에서 잘못되었습니다. 더 많은 개발 시간. 공간을 출력하는 것보다 최소한 오버 헤드로 정확하게 올바른 줄 바꿈을 삽입하는보다 효율적인 방법을 찾을 수 있다고 확신합니다. 나는 이것이 10 ~ 20 %와 같다고 생각합니다. 나는 우리가이 버전을 얼마나 빨리 만들 수 있는지 아는 데 관심이 있습니다. 실제로 그 버전을 다듬지 않고서, 그 부분을 독자를위한 연습으로 남겨 두겠습니다.


DDR3-1600MHz RAM을 사용하는 2.5GHz 최대 터보의 Haswell i5에서 100GiB를 생산하는 데 시간이 걸리지 만 축소되었습니다. (gcc5.4로 Win10의 cygwin64에서 시간이 설정되었습니다. 빌린 랩톱에서 적절한 타이밍을 실행하기에 충분한 시간이 걸리기 -O3 -march=native때문에 생략 -funroll-loops되었습니다. USB로 Linux를 부팅했을 것입니다).

달리 지정하지 않는 한 / dev / null에 쓰기

  • 제임스 홀리스 : (테스트되지 않음)
  • 공칭의 fwrite 버전 : ~ 2.21s
  • 이 (SSE2) : ~ 0.142s (비 스케일 시간 = real = 14.232s, user = 13.999s, sys = 0.187s).
  • 이 (AVX-128) : ~ 0.140s
  • 이 (AVX2) : ~ 0.073s (비 스케일 : real = 0m7.291s, user = 0m7.125s, sys = 0m0.155s).
  • wc -c128kiB 버퍼 크기 의이 (AVX2) cygwin 배관 : 2.38GHz (최대 듀얼 코어 터보)에서 CPU 사용시 0.32 초. (크기 조정되지 않은 시간 : real = 32.466s user = 11.468s sys = 41.092s (이와를 모두 포함 wc)) 그러나 바보 같은 프로그램은 쓰기가 전체 버퍼를 수행한다고 가정하기 때문에 실제로 절반의 데이터 만 복사되었습니다. 심지어는 아니지만 cygwin write ()는 파이프 호출 당 64k 만 수행합니다.

SSE2를 사용하면 @Nominal Animal의 스칼라 코드보다 약 15 배 빠릅니다. AVX2를 사용하면 약 30 배 더 빠릅니다. 나는 write()대신 대신 사용하는 Nominal 코드 버전을 시도하지 fwrite()않았지만 아마도 큰 버퍼의 경우 stdio는 대부분 방해가되지 않습니다. 데이터를 복사하는 경우 많은 속도 저하가 발생합니다.


64 비트 Linux 4.2 (Ubuntu 15.10) 에서 Core2Duo E6600 (Merom 2.4GHz, 32kiB 개인 L1, 4MiB 공유 L2 캐시), DDR2-533MHz 에서 1GB의 데이터를 생성 할 시간 입니다. 여전히 write ()에 128kiB 버퍼 크기를 사용하고 있지만 해당 차원을 탐색하지 않았습니다.

달리 지정하지 않는 한 / dev / null에 쓰기

  • (SSE2) 이것은 새로운 바이트 처리와 임의 바이트의 각 벡터에서 4 벡터의 자릿수를 사용하여 0.183s (18.3에서 100GiB 수행 시간이지만 1GiB 실행과 비슷한 결과). 사이클 당 1.85 개의 명령.
  • (SSE2) 이것으로 파이핑 wc -c: 0.593 초 (비 스케일 : real = 59.266s user = 20.148s sys = 1m6.548s, wc의 CPU 시간 포함). cygwin에서와 동일한 수의 write () 시스템 호출이지만 Linux가 모든 128k의 write ()를 파이프로 처리하므로 실제로 모든 데이터를 파이핑합니다.
  • NominalAnimal의 fwrite()버전 (gcc5.2 -O3 -march=native)은 ./decdig 100 $((1024*1024*1024/200)) > /dev/null3.19 초 +/- 0.1 %로 실행되며 사이클 당 1.40 개의 명령으로 실행됩니다. -funroll-loops는 약간의 차이를 만들었습니다. clang-3.8 -O3 -march=native: 3.42 초 +/- 0.1 %
  • 공칭 fwrite배관 wc -c: 실제 = 3.980 초 사용자 = 3.176 초 시스템 = 2.080 초
  • James Hollis의 한 번에 한 줄 버전 ( clang++-3.8 -O3 -march=native) : 22.885s +/- 0.07 %, 사이클 당 0.84 명령어. (g ++ 5.2는 약간 느 렸습니다 : 22.98s). 한 번에 한 줄만 쓰면 크게 손상 될 수 있습니다.
  • Stéphane Chazelas 's tr < /dev/urandom | ...: real = 41.430s 사용자 = 26.832s sys = 40.120s. tr커널 CPU에서 거의 모든 시간을 임의의 바이트를 생성하고 파이프에 복사하는 데 거의 모든 시간을 소비했습니다. 이 이중 코어 시스템의 다른 코어는 나머지 파이프 라인을 실행하고있었습니다.
  • time LC_ALL=C head -c512M </dev/urandom >/dev/null:하여 예 단지 읽기한다는 배관 많은 임의성 : 실제 =의 35.018s 사용자 = 0.036s에 sys =의 34.940s.
  • Lưu Vĩnh Phúc의 perl 프로그램 (Ubuntu15.10의 perl v5.20.2) :
    LANG=en_CA.UTF-8: real = 4m32.634s user = 4m3.288s sys = 0m29.364
    LC_ALL=C LANG=C: 실제 = 4m18.637s 사용자 = 3m50.324s sys = 0m29.356s. 여전히 매우 느립니다.

  • (SSE2) 이것은 개행 처리가없고 임의 바이트의 각 벡터에서 3 ~ 4 개의 자릿수 벡터 (거의 정확히 같은 속도 : dig3 = v%10단계는이 HW에서 손익 분기)입니다 : 0.166s (사이클 당 1.82 명령어) . 이것은 기본적으로 우리가 완벽하게 효율적인 줄 바꾸기 처리에 근접 할 수있는 하한입니다.

  • (SSE2) 줄 바꿈 처리가 없지만 v%10, 0.222 초 +/- 0.4 %, 사이클 당 2.12 명령어를 사용하여 uint16_t 요소 당 하나의 숫자 만 가져 오는 이전 버전입니다 . gcc5.2로 컴파일되었습니다. 언롤 -march=native -O3 -funroll-loops루프는이 하드웨어에서이 코드를 돕기 위해 발생합니다. 특히 큰 프로그램에서는 맹목적으로 사용하지 마십시오.
  • (SSE2)이 파일의 이전 버전으로 파일에 쓰기 (3 개의 고속 마그네틱 하드 드라이브의 RAID10f2에서 쓰기에 최적화되지 않음) : ~ 4 초. 커널 I / O 버퍼 설정을 조정하여 write () 블록보다 훨씬 더 더러운 데이터를 허용함으로써 더 빨라질 수 있습니다. "시스템"시간은 여전히 ​​~ 1.0 초이며 "사용자"시간보다 훨씬 높습니다. DDR2-533 RAM이 느린이 오래된 시스템에서는 커널이 데이터를 페이지 캐시에 memcpy하고 XFS 기능을 실행하는 데 루프에서 데이터를 그대로 유지하는 버퍼에서 재 작성하는 것보다 ~ 4 배 더 오래 걸립니다. 은닉처.

어떻게했는지

빠른 PRNG가 필수적입니다. xorshift128 + 를 벡터화 할 수 있으므로 SIMD 벡터 요소에 2 개 또는 4 개의 64 비트 생성기가 병렬로 있습니다. 각 단계는 무작위 바이트의 전체 벡터를 생성합니다. ( Intels 내장으로 256b AVX2 구현 ). 64 비트 벡터 정수 곱셈은 확장 정밀 기술을 사용하는 SSE2 / AVX2에서만 가능 하기 때문에 Nominal의 xorshift *를 선택했습니다 .


임의의 바이트로 구성된 벡터가 제공되면 각 16 비트 요소를 여러 개의 10 진수로자를 수 있습니다. 각각 하나의 ASCII 숫자 + ASCII 공간 인 16 비트 요소로 구성된 여러 벡터를 생성합니다 . 출력 버퍼에 직접 저장합니다.

내 원래 버전은 방금 x / 6554벡터의 모든 uint16_t 요소에서 하나의 임의의 숫자를 얻는 데 사용 되었습니다. 항상 0에서 9 사이입니다. 그것은 멀리 편향 것 9때문에, (2^16 -1 ) / 6554단지 9.99923이다. (6554 = ceil ((2 ^ 16-1) / 10), 몫이 항상 <10인지 확인합니다.)

x/6554는 "매직"상수 ( 고정 소수점 역수 )와 반값의 오른쪽 이동을 곱하여 계산할 수 있습니다 . 상수로 나누는 가장 좋은 경우입니다. 일부 제수는 더 많은 작업을 수행하고 서명 된 부서는 추가 작업을 수행합니다. x % 10비슷한 편견을 가지고 있으며 계산하기에 저렴하지 않습니다. (gcc의 asm 출력은 x - 10*(x/10)모듈 곱셈의 역수를 사용하여 나누기의 추가 곱셈과 뺄셈과 같습니다 .) 또한 xorshift128 +의 가장 낮은 비트는 품질이 높지 않으므로 높은 비트에서 엔트로피를 나누는 것이 좋습니다 ( 낮은 비트로부터 엔트로피를 얻기 위해 모듈로보다 품질뿐만 아니라 속도).

그러나 @Nominal의 digit()함수 와 같이 낮은 자릿수를 보면 각 uint16_t에서 더 많은 엔트로피를 사용할 수 있습니다 . 성능을 극대화하기 위해 낮은 3 자리 10 진수를 사용하기로 결정하고 x/6554PMULLW 및 PSUBW (그리고 아마도 일부 MOVDQA)를 4 자리의 낮은 10 진수를 사용하는 고품질 옵션과 비교하여 결정했습니다. x / 6554는 소수점 이하 3 자리 숫자에 약간 영향을 받으므로 동일한 요소의 숫자 사이에 약간의 상관 관계가 있습니다 (벡터 너비에 따라 ASCII 출력에서 ​​8 자리 또는 16 자리 분리).

gcc가 10으로 연속적으로 나누는 더 긴 체인이 아니라 100과 1000으로 나뉘어 있다고 생각하므로 각 PRNG 출력에서 ​​4 개의 결과를 생성하는 비 루프 전달 종속성 체인의 길이가 크게 단축되지는 않을 것입니다. port0 (벡터 곱셈 및 이동)은 모듈 식 곱하기 역수와 xorshift +의 이동으로 인한 병목 현상이므로 벡터 곱셈을 저장하는 것이 확실히 유용합니다.

xorshift +는 매우 빠르기 때문에 16 비트마다 ~ 3.3 비트의 임의성 (즉, 20 % 효율) 만 사용하더라도 여러 십진수로 자르는 것보다 훨씬 느리지 않습니다. 이 답변은 품질이 나쁘지 않은 한 속도에 중점을두기 때문에 균일 분포에 근접합니다.

가변적 인 수의 요소를 유지하는 모든 종류의 조건부 동작은 훨씬 더 많은 작업이 필요합니다. 그러나 SIMD 왼쪽 패키징 기술을 사용하여 다소 효율적으로 수행 할 수는 있지만 작은 요소 크기의 경우 효율성이 떨어지고 거대한 셔플 마스크 조회 테이블을 사용할 수 없으며 32-보다 작은 AVX2 차선 교차 셔플이 없습니다. 비트 요소 128b PSHUFB 버전은 BMI2 PEXT / PDEP사용하여 마스크 를 계속 생성 할 수 있습니다. 큰 요소가있는 AVX2의 경우처럼 64 비트 정수는 8 바이트 만 포함하기 때문에 까다 롭습니다. 그 대답에는 더 많은 요소 수에 작동 할 수있는 코드가 있습니다.)


RNG의 대기 시간이 병목 현상이면 두 벡터의 발전기를 병렬로 실행하여 사용하는 것을 교대로 사용하면 훨씬 더 빨라질 수 있습니다. 컴파일러는 언롤 된 루프에서 레지스터의 모든 항목을 쉽게 유지할 수 있으므로 두 개의 종속 체인이 병렬로 실행될 수 있습니다.

현재 버전에서 PRNG의 출력을 줄이면 실제로 PRNG 대기 시간이 아닌 포트 0 처리량에 병목 현상이 발생하므로 그럴 필요가 없습니다.


코드 : AVX2 버전

Godbolt 컴파일러 탐색기에 대한 자세한 설명 이있는 정식 버전 .

매우 정돈되지는 않습니다. 죄송합니다. 잠을 자고이 게시물을 게시하고 싶습니다.

SSE2 버전을 얻기에 s/_mm256/_mm, s/256/128/, s/v16u/v8u/, 및 변경 vector_size(32)도 4 * 16-4 * 8에서 개행 증분 변화 (16). (말했듯이 코드가 지저분하고 두 가지 버전을 컴파일하기에 적합하지 않습니다. 원래 AVX2 버전을 만들 계획은 없었지만 액세스 할 수있는 Haswell CPU에서 테스트하고 싶었습니다.)

#include <immintrin.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
//#include <string.h>

// This would work equally fast 128b or 256b at a time (AVX2):
// https://stackoverflow.com/questions/24001930/avx-sse-version-of-xorshift128
struct rngstate256 {
    __m256i state0;
    __m256i state1;
};

static inline __m256i xorshift128plus_avx2(struct rngstate256 *sp)
{
    __m256i s1 = sp->state0;
    const __m256i s0 = sp->state1;
    sp->state0 = s0;
    s1 = _mm256_xor_si256(s1, _mm256_slli_epi64(s1, 23));
    __m256i state1new = _mm256_xor_si256(_mm256_xor_si256(_mm256_xor_si256(s1, s0),
                            _mm256_srli_epi64(s1, 18)),
                      _mm256_srli_epi64(s0, 5));
    sp->state1 = state1new;
    return _mm256_add_epi64(state1new, s0);
}



// GNU C native vectors let us get the compiler to do stuff like %10 each element
typedef unsigned short v16u __attribute__((vector_size(32)));

__m256i* vec_store_digit_and_space(__m256i vec, __m256i *restrict p)
{
    v16u v = (v16u)vec;
    v16u ten = (v16u)_mm256_set1_epi16(10);

    v16u divisor = (v16u)_mm256_set1_epi16(6554);  // ceil((2^16-1) / 10.0)
    v16u div6554 = v / divisor;      // Basically the entropy from the upper two decimal digits: 0..65.
    // Probably some correlation with the modulo-based values, especially dig3, but we do this instead of
    // dig4 for more ILP and fewer instructions total.

    v16u dig1 = v % ten;
    v /= ten;
    v16u dig2 = v % ten;
    v /= ten;
    v16u dig3 = v % ten;
    //  dig4 would overlap much of the randomness that div6554 gets

    const v16u ascii_digitspace = (v16u)_mm256_set1_epi16( (' '<<8) | '0');

    v16u *vecbuf = (v16u*)p;
    vecbuf[0] = div6554 | ascii_digitspace;
    vecbuf[1] = dig1    | ascii_digitspace;
    vecbuf[2] = dig2    | ascii_digitspace;
    vecbuf[3] = dig3    | ascii_digitspace;
    return p + 4;  // always a constant number of full vectors
}


void random_decimal_fill_buffer(char *restrict buf, size_t len, struct rngstate256 *restrict rngstate)
{
    buf = __builtin_assume_aligned(buf, 32);

    // copy to a local so clang can keep state in register, even in the non-inline version
    // restrict works for gcc, but apparently clang still thinks that *buf might alias *rngstate
    struct rngstate256 rng_local = *rngstate;

    __m256i *restrict p = (__m256i*restrict)buf;
    __m256i *restrict endbuf = (__m256i*)(buf+len);
    static unsigned newline_pos = 0;
    do {
        __m256i rvec = xorshift128plus_avx2(&rng_local);
        p = vec_store_digit_and_space(rvec, p);  // stores multiple ASCII vectors from the entropy in rvec

#if 1
        // this is buggy at the end or start of a power-of-2 buffer:
        // usually there's a too-short line, sometimes a too-long line
        const unsigned ncols = 100;
        newline_pos += 4*16;
        if (newline_pos >= ncols) {
            newline_pos -= ncols;
            char *cur_pos = (char*)p;
            *(cur_pos - newline_pos*2 - 1) = '\n';
        }
#endif
        // Turning every 100th space into a newline.
        // 1) With an overlapping 1B store to a location selected by a counter.  A down-counter would be more efficient
        // 2) Or by using a different constant for ascii_digitspace to put a newline in one element

        // lcm(200, 16) is 400 bytes, so unrolling the loop enough to produce two full lines makes a pattern of full vectors repeat
        // lcm(200, 32) is 800 bytes
        // a power-of-2 buffer size doesn't hold a whole number of lines :/
        // I'm pretty sure this can be solved with low overhead, like maybe 10% at worst.
    } while(p <= endbuf-3);

    *rngstate = rng_local;
}



#define BUFFER_SIZE (128 * 1024)
const static size_t bufsz = BUFFER_SIZE;
__attribute__((aligned(64))) static char static_buf[BUFFER_SIZE];

int main(int argc, char *argv[])
{
    // TODO: choose a seed properly.  (Doesn't affect the speed)
    struct rngstate256 xorshift_state = {
      _mm256_set_epi64x(123, 456, 0x123, 0x456),
      _mm256_set_epi64x(789, 101112, 0x789, 0x101112)
    };

    for (int i=0; i < 1024ULL*1024*1024 / bufsz * 100; i++) {
        random_decimal_fill_buffer(static_buf, bufsz, &xorshift_state);
        size_t written = write(1, static_buf, bufsz);
        (void)written;
        //fprintf(stderr, "wrote %#lx of %#lx\n", written, bufsz);
    }

}

gcc, clang 또는 ICC (또는 C99의 GNU C 언어와 인텔 내장 함수를 이해하는 다른 컴파일러)로 컴파일하십시오. GNU C 벡터 확장은 컴파일러가 모듈 식 곱셈 역수를 사용하여 나눗셈 / 모듈로의 마법 번호를 생성하는 데 매우 편리하며 가끔 __attribute__유용합니다.

이것은 이식 가능하게 작성 될 수 있지만 더 많은 코드가 필요합니다.


성능 메모 :

줄 바꿈을 삽입하는 중복 저장소는 배치 위치를 결정하는 데 상당한 오버 헤드가 있지만 (Core2의 브랜치 오류 및 프런트 엔드 병목 현상) 저장소 자체는 성능에 영향을 미치지 않습니다. 컴파일러의 asm에있는 해당 저장 명령을 주석 처리하면 (모든 분기를 동일하게 유지) Core2의 성능은 변경되지 않고 반복 실행으로 동일한 시간이 +/- 1 % 미만이됩니다. 그래서 스토어 버퍼 / 캐시가 잘 처리한다고 결론을 내립니다.

그럼에도 불구 ascii_digitspace하고 카운터 / 브랜칭이 사라질 정도로 충분히 풀면 줄 바꿈이있는 하나의 요소와 함께 일종의 회전 창을 사용하는 것이 훨씬 빠를 수 있습니다.


/ dev / null에 쓰는 것은 기본적으로 no-op이므로 버퍼는 L2 캐시에서 핫 상태를 유지할 수 있습니다 (Haswell의 코어 당 256kiB). 128b 벡터에서 256b 벡터로의 완벽한 속도 향상이 예상됩니다. 추가 명령이 없으며 모든 상점 (저장소 포함)이 폭의 두 배로 발생합니다. 그러나 개행 삽입 분기는 두 번 자주 수행됩니다. 불행히도 Haswell Cygwin 설정에 시간이 들지 않았습니다 #ifdef.

2.5GHz * 32B / 13.7GB / s = Haswell의 AVX2 저장소 당 5.84 회. 꽤 좋지만 더 빠를 수 있습니다. 어쩌면 cygwin 시스템 호출에 내가 생각했던 것보다 약간의 오버 헤드가있을 수 있습니다. 컴파일러의 asm 출력에서 ​​주석 처리를 시도하지 않았습니다 (아무것도 최적화하지 못하게합니다).

L1 캐시는 클럭 당 하나의 32B 저장소를 유지할 수 있으며 L2는 대역폭이 훨씬 낮지 않습니다 (대기 시간이 길수록).

몇 가지 버전 전에 IACA를 살펴봤을 때 (개행을위한 분기는 없지만 RNG 벡터 당 하나의 ASCII 벡터 만 얻음) 4 또는 5 클럭 당 하나의 32B 벡터 저장소와 같은 것을 예측하고있었습니다.

Agner Fog의 가이드SO x86 태그 위키 에서 링크를 추가 한 기타 최적화 리소스를 고려하여 asm 자신을보고 기반으로 각 RNG 결과에서 더 많은 데이터를 추출하여 더 많은 속도를 얻으려고했습니다 .)

아마도 Skylake 에서는 벡터 정수 곱셈 및 시프트가 Haswell (p0 전용)에 비해 두 배 많은 포트 (p0 / p1)에서 실행될 수 있습니다. xorshift와 숫자 추출은 모두 많은 이동과 곱셈을 사용합니다. ( 업데이트 : Skylake는 3.02 IPC에서이를 실행하여 32 바이트 AVX2 저장소 당 3.77 사이클을 제공하고 1GB 반복 당 0.030 초로 시간을 정하고 i7-6700k의 /dev/nullLinux 4.15에서 3.9GHz로 작성합니다.


제대로 작동하기 위해 64 비트 모드가 필요하지 않습니다 . SSE2 버전은 -m32매우 많은 벡터 레지스터를 필요로하지 않기 때문에로 컴파일 할 때와 마찬가지로 빠르며 모든 64 비트 연산은 범용 레지스터가 아닌 벡터로 수행됩니다.

비교 / 분기 매크로 융합은 32 비트 모드에서만 작동하므로 비 순차적 코어 (18.3s (1.85 명령어 당))에 대한 UOP가 더 적기 때문에 실제로 Core2의 32 비트 모드에서 약간 빠릅니다. 16.9s (2.0 IPC)). REX 프리픽스가 없어 코드 크기가 작을수록 Core2의 디코더에도 도움이됩니다.

또한 일부 reg-reg 벡터 이동은 더 이상 벡터 regs의 모든 상수가 수정되지 않기 때문에 하중으로 대체됩니다. L1 캐시의로드 처리량은 병목 현상이 아니므로 실제로 도움이됩니다. (예를 들면 일정한 벡터 곱하여 set1(10): movdqa xmm0, xmm10/은 pmullw xmm0, xmm1변신 movdqa xmm0, [constant]/ pmullw xmm0, xmm1.) REG-레지 MOVDQA 때문에 것은 ALU 포트 요구는 실제 작업이 수행되는 경쟁하지만 MOVDQA 하중은 프런트 엔드 디코드 대역폭 경쟁. (많은 명령어 안에 4 바이트 주소가 있으면 REX 접두사를 저장하면 많은 이점을 얻을 수 없습니다.

프론트 엔드가 평균 2.0 IPC를 잘 유지해야하기 때문에 ALU MOVDQA uops를 저장하는 것이 실제 이익을 얻는 곳인 경우 놀라지 않을 것입니다.

루프백 버퍼가 아닌 경우 디코딩 된 uup 캐시에서 전체를 실행해야하는 Haswell에서는 이러한 모든 차이점이 사라집니다. Ahale + branch macro-fusion은 Nehalem 이후 두 모드에서 모두 작동합니다.


6
난 그냥 당신이 주제로 "짐승 모드" 에 갔다 어떻게 사랑 해요 ! :) 더 중요한 것은, 실제 하드웨어에 대한 매우 낮은 수준의 지식을 활용하여 최대 성능을 실제로 필요로하거나 짜 내려는 경우 어떤 종류의 이득을 얻을 수 있는지에 대한 훌륭한 예입니다. 또한 여기서는 하나의 스레드 만 사용합니다. 대부분의 최신 데스크탑 및 서버 인텔 / AMD 프로세서 (및 경량 태블릿 및 SBC의 ARM 프로세서)에는 여러 개의 코어가 있으므로 여전히 실제 보다 더 빠른 속도로 사용할 수 있습니다. 마지막으로, 엄청난 노력으로 인해 "가장 빠른" 질문이 얼마나 실용적이지 않은가 .
공칭 동물

1
@NominalAnimal : 예, 느린 ARM 쿼드 또는 옥토 코어조차도 64 비트 정수 SIMD 추가 및 시프트가있는 경우 NEON과 동일한 작업을 수행하여 주 메모리 대역폭을 쉽게 포화시킬 수 있습니다 (빠른 이중 채널 DDR3에 연결되어 있어도). . NEON에는 오디오 작업에 16 비트 요소 크기 곱셈이 있다고 가정합니다. 루프 스케줄링 종속성 체인 (xorshift128 +)의 각 반복은 독립적 인 종속성 체인을 공급하여 메모리에 저장하는 몇 가지 독립적 인 종속성 체인을 제공하기 때문에 명령어 스케줄링은 순차적 ARM의 경우 훨씬 더 많은 작업이 될 것입니다.
Peter Cordes

... 비 순차적 실행은 아침 식사를 위해 그것을 먹습니다. 왜냐하면 모든 것이 ROB (HSW IIRC에서 192 uops)에 맞을 정도로 짧기 때문입니다. (즉, 비 순차적 실행으로 보이는 명령어의 "윈도우"에는 여러 반복이 포함됩니다). 따라서 CPU는 현재 반복의 시작 부분에서 시작하면서 2 또는 3 회의 반복 전에 최종 저장소를 완료 할 수 있습니다. 이것은 독립 체인의 대기 시간을 숨기므로 처리량 만 중요합니다. 주문형 코어의 경우 소프트웨어 파이프 라이닝이 필요합니다.
Peter Cordes

... intrinsics (또는 GNU C 네이티브 벡터 구문, 내가 처음부터해야했던 것처럼)로 작성하면 좋은 ARM 컴파일러가 그 중 일부를 수행해야합니다. 나는 실제로 그것을하는 데 경험이 없으므로 루프를 마사지하고 소스에서 수동 언 롤링 / 소프트웨어 파이프 라이닝을 수행하여 좋은 asm을 얻을 수 있습니다. (고급 전화기에는 비 순차적 ARM 코어가 있지만 Haswell만큼 비 순차적 창은 없지만 OTOH는 피크 처리량이 낮으므로 더 적습니다. 더 많은 ILP를 찾아서 얻는 것).
Peter Cordes

1
@NominalAnimal : 또한 질문의 어리 석음에 동의했습니다. 임의의 품질에 대한 제한이없는 "가장 빠름"은 어리석은 일입니다 ... BTRFS를 사용하면 디스크의 동일한 데이터가 여러 번 파일의 일부가 될 수 있습니다 ( 4.2의 EXTENT_SAME 참조 ). 따라서 임의의 4kiB 또는 1MB를 생성하고 반복 할 수 있습니다. 짧은 기간의 임의성이지만 여전히 임의적이며 메타 데이터 I / O 비용 만 발생합니다. (실제로, 당신은 개행으로 끝나는 블록이 필요합니다. lcm (4096, 4096 * 200) = 4096 * 200 = 819200 = 800kiB, 반복 블록은 그것의 배수입니다.)
Peter Cordes

14

이해하기 쉬운 해결책은 다음과 같습니다.

od -An -x /dev/urandom | tr -dc 0-9 | fold -w100 | awk NF=NF FS= | head -c1G
  • od에서 16 진수로 된 균일 한 스트림을 만듭니다 /dev/random.
  • tr0-9숫자를 유지하면서 문자를 제거합니다.
  • fold 한 줄에 100 자리 숫자가 있는지 확인
  • awk 줄 안에 공백을 삽입
  • head 입력을 1 기가 바이트로 자릅니다.

2
그것은 균일 한 분포를 유지하면서 바이트 당 / dev / random 바이트 이상으로 두 자리 이상의 숫자를 생성하는 좋은 대안입니다. 평균 256 바이트 당 / dev / urandom마다 320 자리를 생성합니다 (바이트 <200 모듈로 변환 할 때보 다 적음) 100에서 10 진수로 256 바이트마다 400 자리 숫자를 제공합니다).
Stéphane Chazelas

6

jot이 명령을 사용할 수 있습니다 :

jot -r 50000000 0 9 | fmt -w 200 > output.txt

1
@DigitalTrauma 내 버전에 fmt목표 너비 옵션이 없습니다. 어쨌든 모든 숫자가 정확히 하나의 열을 차지하기 때문에 정확합니다!
gardenhead

기록을 위해 내 fmt버전은 fmt (GNU coreutils) 8.25(우분투 16.04)
Digital Trauma

2
반 GB의 올바른 숫자는 다음과 같습니다. 1024 * 1024 * 1024 / 2 =536870912
Olivier Dulac

1
@OlivierDulac 당신이 말하는 "기가 바이트"에 따라 다릅니다. 일부 사람들은 기술적으로 잘못되었지만 2 ^ 30 대신 10 ^ 9를 의미하기 위해 1Gb를 사용합니다. 게다가 나는 좋은 둥근 숫자를 좋아한다 :)
gardenhead

6
@gardenhead, 이제 점점 더 많은 사람들이 IEC 표준 정의이므로 Gigabyte == 1e9 및 Gibibyte == 2 ^ 30으로 이동하는 경향이 있습니다. Wikipedia를 참조하십시오 . 기가 자체가 아니라 Giga- 될 것이라고합니다 비트 .
Stéphane Chazelas

6

이것은 Stéphane Chazelas의 방법과 유사하지만 성능을 향상시키기 위해 한 번에 64 비트를 읽습니다. 분포는 여전히 균일하지만 이전과 같이 가장 좋은 경우에는 8 바이트 대신 8 바이트 당 19 자리를 얻습니다.

perl -nle 'BEGIN{$/=\8; $,=" "}
           $n = unpack("Q");
           next if $n >= 10000000000000000000;
           $s = sprintf("%019u", $n);
           push @a, (split //, $s);
           if (@a >= 100) {print (splice @a, 0, 100);}' < /dev/urandom | head -c1G

32 비트 플랫폼에서는 매번 19 자리 대신 9 자리를 읽습니다.


시스템이 64 비트 정수를 지원하지 않거나 perl쿼드 지원으로 컴파일 되지 않은 경우 예외가 발생할 수 있습니다 .
cuonglm

@cuonglm 그렇다. 만약 내가 perl이 그 시스템에서 64 비트가 아니라면 프로그램은 next if $n >= 1000000000; $s = sprintf("%09u", $n);9 자리만을 갖도록 변경되어야한다
phuclv

$n = unpack("Q")쿼드가 지원되지 않으면 프로그램이 중단 됩니다.
cuonglm

1
@cuonglm 변화 BEGIN{$/=\4; $,=" "} $n = unpack("L");
phuclv

1
죄송하지만 8 바이트 입력에서 19 자리 는 시간의 54.2 %에 불과 하고 나머지는 입력 바이트 당 평균 1.29 자리입니다. Stephane을 더 많이 사용 <16e18하고 16으로 나누면 1.95dpB에 대해 18 자리 86.7 %가됩니다. 32 비트를 사용하면 <4e9 /42.10dpB에서 9 자리 93.1 %를 얻습니다. 그러나 5 바이트 (16 진수 (H10)) <1e12는 2.18 dpB에 12 자리 90.9 %를 제공하거나 16 진수를 반으로 나누고 각 반을 수행 <1e6 하면 2.29 dpB에 6 자리 95.4 %를 제공합니다. 이것은 log_10 (256) = 2.41의 한계에 접근합니다.
dave_thompson_085

3

속도가 필요한 경우 컴파일 된 프로그래밍 언어를 사용하는 공칭 동물에 동의합니다. 그러나 C로 자신의 RNG 코드를 작성할 필요는 없습니다. C ++ 11은 표준 라이브러리의 일부로 탁월한 Mersenne Twister를 제공합니다.

#include <time.h>
#include <random>
#include <iostream>
using namespace std;

int main() {
    mt19937 gen(time(0)); 
    uniform_int_distribution<> dist(0,9);

    for(int j=0; j<5000000; j++){
        for (int i = 0; i < 99; i++) {  
            cout << dist(gen) << " ";
        }  
        cout << dist(gen) << endl;
    }
    return 0;
}

위의 코드는 상당히 간단하며 출력을 파일로 파이프하는 데 약 1 분이 걸립니다. 100 자리수만큼 큰 문자열을 만들고 숫자를 해킹하면 훨씬 빠르게 갈 수 있습니다. 이를 통해 모든 숫자가 아닌 모든 줄에 cout을 호출 할 수 있습니다.

#include <time.h>
#include <random>
#include <iostream>
using namespace std;

int main() {
    mt19937 gen(time(0)); 
    uniform_int_distribution<> dist(0,9);

    char line[201];
    for(int i=1; i<199; i++)
        line[i] = ' ';
    line[199] = '\n';
    line[200] = 0;

    for(int j=0; j<5000000; j++){
        for (int i = 0; i < 199; i += 2) {  
            line[i] = dist(gen)+'0';
        }  
        cout << line;
    }
    return 0;
}

이 코드는 약 6 초 정도 걸립니다. 표준 출력임을 기억하여 파일로 파이프하십시오.

면책 조항이 몇 개 있습니다. 먼저, 나는 이것을 Windows PC에서 쓰고 있습니다. 나는 라이브러리가 모두 Linux에 있다고 생각하지만 내가 틀렸다면 지적해야합니다.

또한 실제로 정확히 50 억 공백으로 구분 된 숫자를 출력합니다. 기술적으로 기가 바이트이지만 정확하게 원하는 것은 아닙니다. 5 백만 줄, 한 줄에 100 자리를 출력합니다. 차이가 중요한 경우 줄 수를 늘릴 수 있습니다. 내 Windows 상자에서 파일은 10 ^ 9 바이트보다 약간 더 큰 것 같습니다. 줄 바꿈 문자와 관련이 있다고 생각합니다.


2
이봐, 비판은 정말 공평하지 않아! :) 내 프로그램의 대부분은 명령 줄 매개 변수 구문 분석입니다. 거의 - 나는 또한 의견, 오류 검사를 생략하고, 열 및 라인 출력의 수를 하드 경우에, 나는 그것을 덜 코드의 두 배 크기보다 수 monstruous . :) 키딩 제외 : 예, 대부분의 Linux 배포판에서 라이브러리를 사용할 수 있습니다. 랩톱에서 한 번에 한 줄은 약 14 초가 걸리지 만 한 번에 한 줄은 버전은 1.3 초 밖에 걸리지 않습니다. 차이점은 PRNG 때문입니다. Mersenne Twister는 Xorshift64 *보다 훨씬 느립니다.
공칭 동물

1
당신이 놓친 점을 지적하고 싶은 한 가지 실용적인 것이 있지만, 당신이 그것을 부정으로 생각하지 말고 단지 생각할 것이기를 바랍니다. 내 대답에서 언급했듯이 원샷 프로그램은 거의 가치가 없습니다. 그들이 쓰는 데 걸린 시간. 그렇기 때문에 명령 줄 구문 분석과 도움말 사용법 텍스트를 추가하는 것이 거의 항상 가치있는 이유입니다. 나는 그러한 유틸리티 프로그램의 큰 세트를 가지고 있고, 그들의 소스를 찾아서 그들 각각이 무엇을하는지 알아 내기보다는 그것들을 실행하기 만하면 그들이 내게 말해 줄 것이다. 둘 이상의 필요에 맞게 동작을 수정할 수 있습니다. 개발 비용 절감
명목 동물

@NominalAnimal 또 다른 중요한 점은 /dev/null실제 파일에 쓰는 것보다 훨씬 빠른 출력을 파이프한다는 것입니다.
phuclv

@ LưuVĩnhPhúc : 글쎄요. 이 lappy는 500MB / s의 순차 읽기 및 쓰기를 지원하는 Samsung 128GB SSD를 가지고 있습니다. Linux 소프트웨어 -RAID0 구성에 4 개를 넣으면 이러한 큰 데이터 세트를 생성 할 때 초당 기가 바이트 이상 읽고 쓸 수 있습니다 (약 1.75TB / s로 추정). 1GB / s는 몇 년 전에 Linux sw-RAID0을 사용하는 12 개의 SATA 드라이브 (SSD가 아닌 회전 플래터)로 도달했습니다. (참고 : / s의,하지 비트 / s의 바이트.) 물론, 그것은 "정상적인"기계 바보 같은 소리지만, 대규모 데이터 세트 플레이 사람들이 그 가치를 찾을 수 - 당신은 시간 면도 모든 (대규모 데이터 세트로) 당신이를 그런 식으로.
공칭 동물

1
@NominalAnimal 및 Lu'u : 더 중요한 것은 RAM이 충분하면 데이터가 디스크에 모두 들어가기 전에 프로그램이 종료 될 수 있다는 것입니다. 대규모 write()시스템 호출 에서 대부분의 작업 은 pagecache에 대한 memcpy이며, 이는 더 많은 버퍼 공간을 할당하는 대신 커널이이를 수행하기로 결정한 경우에만 차단합니다. 이 프로그램은 메모리가 부족하거나 O_DIRECT를 사용하여 페이지 캐시를 우회 한 경우에만 디스크 I / O에서 병목 현상이 발생합니다. write()캐시 크기보다 작은 청크를 사용하는 경우 데이터가 기본 메모리로 한 번만 들어가고 다시 작성된 버퍼가 L2 또는 L3 캐시에서 뜨겁게 유지되기를 바랍니다.
Peter Cordes

1

"무작위"의 정의에 따라 다릅니다. 암호로 무작위를 의미하는 경우 좋은 라이브러리를 가져와 총알을 물고 실행되기를 기다려야합니다.

매우 무작위로 보이는 것이 필요한 경우 쉬운 방법은 다음과 같습니다.

  1. 몇 Gb 길이의 파일을 가져옵니다. 좋아하는 영화가 좋을 것입니다.
  2. 반복되는 패턴을 짜내는 쉬운 방법 인 Gzip it
  3. 파일을 한 번에 nybble (1/2 바이트)로 이동하십시오. 각 값은 0과 15 사이입니다. 1보다 작거나 10보다 큰 값을 버리십시오. 10 억 생존자 각각에서 1을 빼고 숫자로 쓰십시오.

느린 시스템에서 실행하는 데 1 시간이 걸릴 수 있습니다. 대부분의 목적을 위해 충분히 빠르고 임의적입니다.


9
/dev/urandomgzip속도와 임의성 모두에서 보다 낫습니다 .
Stig Hemmer

Get a file that is several Gb long1GB 파일을 얻으려면 ** 8GB 이상이어야합니다.
phuclv

1
#!/bin/bash
FILE_CREAT='/tmp/testfile'
MAX_SIZE=$(( 1 * 1024 * 1024 ))
rm -rf ${FILE_CREAT}
while true
do
    STRING=''
    for (( i = 0 ; i < 100 ; i++ ))
    do
        NUM_RAN=$(cat /dev/urandom | tr -dc 0-9 | head -c 1)
        if [ $i -eq 0 ]
        then
            STRING=${NUM_RAN}
        else
            STRING=${STRING}' '${NUM_RAN}
        fi
    done
    echo ${STRING} >> $FILE_CREAT
    FILE_SIZE=$(du -s ${FILE_CREAT} | awk '{print $1}')
    if [ ${FILE_SIZE} -ge ${MAX_SIZE} ]
    then
        break
    fi
done
exit $1

1
사이트에 오신 것을 환영합니다! 내 프로필 페이지의 링크를 참조하십시오. 쉘 스크립트에서 거의 보편적으로 볼 수있는 많은 문제가 있지만, 그렇습니다.
와일드 카드

2
@Wildcard : cat file | tr당신이 할 수 없을 때 결코 tr <file. IIRC, 당신도 할 수 있습니다 <file tr. du | awk모든 쉘이 크기를 확인하고 루프 외부로 리디렉션하는 대신 모든 라인을 추가하기 위해 파일을 다시 열 때 처럼이 쉘 스크립트에 대해 말끔하고 느리게 보이는 것에 대해 이야기하고 있다고 생각했습니다 .
Peter Cordes

2
@PeterCordes입니다. 왜 쉘 루프를 사용하여 텍스트를 처리하는 것이 좋지 않은 것으로 간주됩니까? 이 스크립트는 Bash가 C와 같은 프로그래밍 언어라는 아이디어를 기반으로합니다. 그러나 \ @NamNT, 나는 당신이 매우 논리적 인 마음을 가지고 있기 때문에이 사이트를 고수하기를 바랍니다. :)
와일드 카드

4
@PeterCordes cat /dev/urandom | busy-cmd는 프로세서간에 임의 생성과 사용중인 cmd를 분할 할 수 있으므로 이례적인 경우 중 하나입니다. tr에는 그다지 중요하지 않지만 Sam의 od경우 에는 차이가 있습니다.
Stéphane Chazelas

1
@ StéphaneChazelas : 아 맞아 !! 예, read () 시스템 호출은 RNG CPU 시간이 소비되는 곳입니다.
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.