Cách nhanh nhất để tạo tệp văn bản 1 GB chứa các chữ số ngẫu nhiên là gì?


52

Tôi đã thử một tập lệnh bash, nhưng mất quá nhiều thời gian để tạo một tệp 1 MB đơn giản. Tôi nghĩ rằng câu trả lời nằm ở việc sử dụng /dev/randomhoặc /dev/urandom, nhưng các bài đăng khác ở đây chỉ cho thấy cách thêm tất cả các loại dữ liệu vào một tệp bằng những thứ này, nhưng tôi muốn chỉ thêm số.

Vì vậy, có một lệnh mà tôi có thể sử dụng để tạo một tệp ngẫu nhiên có kích thước 1 GB chỉ chứa các số từ 0 đến 9 không?

Chỉnh sửa: Tôi muốn đầu ra là một cái gì đó như thế này

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

Phạm vi là 0 - 9 chỉ có nghĩa là các số 0, 1, 2, 3, 4, 5, 6, 7, 8 và 9. Ngoài ra tôi cần chúng được phân tách không gian và 100 trên mỗi dòng, tối đa nsố dòng. Đây là điều tôi không quan tâm, tôi muốn kích thước cuối cùng của mình là 1 GB.

Chỉnh sửa: Tôi đang sử dụng Ubuntu 16.04 LTS



21
Bạn có lẽ nên nói những gì bạn có nghĩa là "ngẫu nhiên" - cường độ mã hóa ngẫu nhiên, hoặc là một chuỗi giả ngẫu nhiên đầy đủ?
Toby Speight

4
@posixKing: Lưu ý rằng mặc dù câu trả lời của tôi chắc chắn là nói trực tiếp - Tôi thực sự không gợi ý viết chương trình C cho một nhiệm vụ như vậy! -, nếu bạn thường xuyên tạo các bộ dữ liệu khổng lồ như vậy hoặc bạn tạo chúng thường xuyên, cách tiếp cận có thể giúp bạn tiết kiệm thời gian. (Trên máy tính xách tay của tôi, nó tạo ra 1GB chữ số được phân tách bằng dấu cách trong khoảng mười giây.) Tuy nhiên, nếu đây là một lần, đừng nghĩ đến việc viết chương trình C cho việc này (trừ khi bạn thích lập trình và xem xét thực hành hoặc như vậy); các lệnh shell và tiện ích đạt được nhiệm vụ trong tổng thời gian và công sức bỏ ra ít hơn.
Động vật danh nghĩa

7
Điều này khá nhanh và tuân thủ RFC 1149.5:yes 4 | tr '\n' ' ' | fold -w 200 | head -c1G
Matthew Crumley

Câu trả lời:


38

Đây là một phần câu trả lời trực tiếp, vì tiêu đề của câu hỏi.

Khi bạn tìm kiếm "cách nhanh nhất để ..." , câu trả lời hầu như luôn là một số công cụ chuyên dụng. "Câu trả lời" này hiển thị một công cụ như vậy, để bạn có thể thử nghiệm.

Đây không phải là một câu trả lời nghiêm túc, bởi vì bạn không nên tìm kiếm các công cụ chuyên dụng cho các công việc bạn chỉ làm một lần, hoặc rất hiếm khi. Bạn thấy đấy, cuối cùng bạn sẽ dành nhiều thời gian hơn để tìm kiếm các công cụ và tìm hiểu về chúng, hơn là thực sự làm công cụ. Shell và các tiện ích như bashawkkhông phải là nhanh nhất, nhưng bạn thường có thể viết một lớp lót để đạt được công việc, chỉ mất vài giây. Các ngôn ngữ kịch bản tốt hơn perlcũng có thể được sử dụng, mặc dù đường cong học tập perlrất dốc và tôi ngần ngại giới thiệu nó cho các mục đích như vậy, bởi vì tôi đã bị chấn thương bởi các dự án perl khủng khiếp. pythonmặt khác thì hơi bị khuyết tật bởi I / O khá chậm; Tuy nhiên, đó chỉ là vấn đề khi bạn lọc hoặc tạo ra hàng gigabyte dữ liệu.

Trong mọi trường hợp, chương trình ví dụ C89 sau (chỉ sử dụng POSIX.1 cho đồng hồ có độ chính xác cao hơn nếu có) sẽ đạt tốc độ tạo khoảng 100 MB / giây (được thử nghiệm trên Linux trên máy tính xách tay với bộ xử lý Intel i5-4200U, dẫn đầu ra đến /dev/null), sử dụng một trình tạo số giả ngẫu nhiên khá tốt. (Đầu ra phải vượt qua tất cả các thử nghiệm BigCrunch, ngoại trừ thử nghiệm MatrixRank, vì mã sử dụng xorshift64 * và phương pháp loại trừ để tránh sai lệch các chữ số.)

chữ số thập phân.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;
}

Chúng ta có thể làm cho nó nhanh hơn rất nhiều, nếu chúng ta chuyển sang bộ đệm dòng và fwrite()nó một lần thay vì xuất ra từng chữ số một lần. Lưu ý rằng chúng tôi vẫn giữ luồng được đệm hoàn toàn, để tránh ghi một phần (không phải nguồn hai) nếu đầu ra là một thiết bị khối.

#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;
}

Lưu ý: cả hai ví dụ được chỉnh sửa vào ngày 2016-11-18 để đảm bảo phân phối chữ số thống nhất (không loại trừ; xem ví dụ ở đây để so sánh và chi tiết về các trình tạo số giả ngẫu nhiên khác nhau).

Biên dịch bằng cách sử dụng ví dụ

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

và tùy chọn cài đặt toàn hệ thống để /usr/binsử dụng

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

Nó lấy số chữ số trên mỗi dòng và số dòng. Bởi vì 1000000000 / 100 / 2 = 5000000(năm triệu; tổng số byte chia cho các cột chia cho 2), bạn có thể sử dụng

./decimal-digits 100 5000000 > digits.txt

để tạo ra kích thước gigabyte digits.txtnhư mong muốn của OP.

Lưu ý rằng bản thân chương trình được viết nhiều hơn với khả năng đọc hơn là hiệu quả trong tâm trí. Mục đích của tôi ở đây không phải là để thể hiện tính hiệu quả của mã - dù sao tôi cũng sẽ sử dụng POSIX.1 và I / O cấp thấp, thay vì giao diện C chung - nhưng để cho bạn dễ dàng thấy loại cân bằng nào có được với nỗ lực đã bỏ ra trong việc phát triển các công cụ chuyên dụng so với hiệu suất của chúng, so với các tập lệnh một lớp hoặc lớp vỏ ngắn hoặc awk.

Sử dụng thư viện GNU C, việc gọi fputc()hàm cho mỗi đầu ra ký tự phải chịu một chi phí rất nhỏ (của một cuộc gọi hàm gián tiếp hoặc điều kiện - FILEgiao diện thực sự khá phức tạp và linh hoạt, bạn thấy). Trên máy tính xách tay Intel Core i5-4200U đặc biệt này, việc chuyển hướng đầu ra sang /dev/null, phiên bản (fputc) đầu tiên mất khoảng 11 giây, trong khi phiên bản trực tuyến chỉ mất 1,3 giây.

Tôi tình cờ thường viết các chương trình và trình tạo như vậy chỉ vì tôi thích chơi với các bộ dữ liệu khổng lồ. Tôi thật kỳ lạ theo cách đó. Ví dụ, tôi đã từng viết một chương trình để in tất cả các giá trị dấu phẩy động IEEE-754 hữu hạn vào một tệp văn bản, với độ chính xác đủ để mang lại giá trị chính xác khi phân tích cú pháp. Tệp có kích thước vài gigabyte (có thể là 4G hoặc hơn); không có nhiều floats tích cực hữu hạn như người ta có thể nghĩ. Tôi đã sử dụng điều này để so sánh các triển khai đọc và phân tích dữ liệu đó.

Đối với các trường hợp sử dụng thông thường, như OP đang có, các kịch bản lệnh shell và scriptlets và một lớp lót là cách tiếp cận tốt hơn. Ít thời gian dành để hoàn thành nhiệm vụ tổng thể. (Ngoại trừ nếu họ cần một tệp khác nhau mỗi ngày hoặc lâu hơn, hoặc có nhiều người cần một tệp khác, trong đó - hiếm - trường hợp, một công cụ chuyên dụng như trên, có thể đảm bảo nỗ lực đã bỏ ra.)


Vâng, có lẽ mmap()là con đường dễ nhất đến tốc độ I / O tốt nhất - nhưng điểm chuẩn trước khi đưa ra bất kỳ khiếu nại nào!
Toby Speight

@TobySpeight: Trong Linux, I / O cấp thấp, tức là sử dụng write(), thường nhanh hơn mmap(). fwrite()không chậm hơn nhiều Có, tôi đã điểm chuẩn rằng (chỉ không cho ví dụ cụ thể này); write()trong các khối lớn (262144, 524288 hoặc 1048576 byte) có xu hướng vượt trội so với các phương thức khác. Phiên bản được fputc()triển khai trong thư viện GNU C (mà tôi cũng đã điểm chuẩn rộng rãi) chậm vì một số lý do; đặc biệt, việc thực hiện phải thực hiện các bước nhảy có điều kiện hoặc các cuộc gọi gián tiếp cho mỗi ký tự được thêm vào; mà chi phí phát sinh nhẹ nên thường cộng lại.
Động vật danh nghĩa

Chỉ cần quan tâm - bạn đã thực hiện một so sánh hiệu suất với các câu trả lời khác?
Chấn thương kỹ thuật số

2
@DigitalTrauma: Tôi vừa chạy chúng cho bạn, chuyển hướng đầu ra /dev/null. Kịch bản của Stéphane Chazelas mất khoảng 52 giây; đoạn trích perl (bao gồm cả headbộ lọc) khoảng 58 giây; shufđoạn trích của bạn (với thời gian chính xác; bạn chỉ đo thời gian shuf, giả sử dán sẽ không mất nhiều thời gian nữa) mất khoảng 69 giây. Chương trình C ++ 11 của James Hollis một chương trình trực tuyến mất 14 giây. Chương trình trên mất 10 giây.
Động vật danh nghĩa

3
(Xin lỗi, tôi đã mất đi ý nghĩ ở trên, xin lỗi.) Điểm là, chọn đúng thuật toán - PRNG đủ ngẫu nhiên nhưng rất nhanh ở đây -, mang lại tốc độ tăng gần như 10 bậc (10 ×). (Phiên bản sau của các chương trình của tôi nhanh hơn khoảng 40 lần so với đoạn mã shell hoặc perl.) Đây là điển hình. Có lẽ tôi nên nhấn mạnh việc chọn thuật toán phù hợp khi viết chương trình nhiều hơn trong câu trả lời của tôi ở trên? (Mặt khác, đây không phải là một câu hỏi lập trình, mà là một câu hỏi Unix / Linux, về cách tạo ra nhiều chữ số.)
Động vật danh nghĩa

81

Điều này:

 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

(giả sử headviệc triển khai hỗ trợ -c) dường như khá nhanh trên hệ thống của tôi.

trdịch toàn bộ phạm vi byte (0 thành 255, 0 thành 0377 theo số bát phân): 25 byte đầu tiên là 0, 25 byte tiếp theo là 1 ... sau đó 25 9 phần còn lại (250 đến 255) thành "x" mà sau đó chúng tôi loại bỏ (với tr -d x) như chúng ta muốn phân phối đồng đều (giả sử /dev/urandomcó phân phối đồng đều) và do đó không đưa ra sai lệch cho một số chữ số.

Điều đó tạo ra một chữ số cho 97% số byte của /dev/urandom. fold -w 1làm cho nó một chữ số trên mỗi dòng. paste -sđược gọi với một danh sách các dấu phân cách bao gồm 99 ký tự khoảng trắng và một ký tự dòng mới, do đó, để có 100 chữ số được phân tách bằng dấu cách trên mỗi dòng.

head -c1Gsẽ nhận được GiB đầu tiên (2 30 ). Lưu ý rằng dòng cuối cùng sẽ bị cắt ngắn và không bị giới hạn. Bạn có thể cắt ngắn thành 2 30 -1 và thêm dòng mới bị thiếu bằng tay hoặc cắt ngắn thành 10 9 byte thay vào đó là 50 triệu trong số 200 dòng đó ( head -n 50000000cũng sẽ biến nó thành lệnh tiêu chuẩn / di động).

Các định thời gian này (có được zshtrên hệ thống lõi tứ), đưa ra dấu hiệu về thời gian CPU được sử dụng:

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

Đầu tiên trlà cổ chai, phần lớn thời gian dành cho kernel (tôi cho rằng việc tạo số ngẫu nhiên). Thời gian gần đúng với tốc độ tôi có thể nhận được byte từ /dev/uramdom(khoảng 19MiB / s và ở đây chúng tôi tạo ra 2 byte cho mỗi 0,97 byte / dev / urandom với tốc độ 32MiB / s). folddường như đang tiêu tốn một lượng thời gian CPU không hợp lý (15 giây) chỉ để chèn một ký tự dòng mới sau mỗi byte nhưng điều đó không ảnh hưởng đến thời gian chung vì nó hoạt động trên một CPU khác trong trường hợp của tôi (thêm -btùy chọn này làm cho nó hơi nhiều hơn một chút hiệu quả, dd cbs=1 conv=unblockcó vẻ như là một thay thế tốt hơn).

Bạn có thể loại bỏ head -c1Gvà tắt một vài giây bằng cách đặt giới hạn về kích thước tệp ( limit filesize 1024mzshhoặc ulimit -f "$((1024*1024))"với hầu hết các shell khác (bao gồm zsh)) thay vào đó trong một khung con.

Điều đó có thể được cải thiện nếu chúng tôi trích xuất 2 chữ số cho mỗi byte, nhưng chúng tôi sẽ cần một cách tiếp cận khác cho điều đó. Ở trên là rất hiệu quả vì trchỉ cần tìm kiếm từng byte trong một mảng 256 byte. Nó không thể làm điều đó cho 2 byte cùng một lúc và sử dụng những thứ như thế hexdump -e '1/1 "%02u"'sẽ tính toán biểu diễn văn bản của một byte bằng các thuật toán phức tạp hơn sẽ tốn kém hơn so với việc tạo số ngẫu nhiên. Tuy nhiên, nếu như trong trường hợp của tôi, bạn có lõi CPU có thời gian rảnh, nó vẫn có thể tắt được vài giây:

Với:

< /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

Tôi nhận được (tuy nhiên lưu ý rằng ở đây là 1.000.000.000 byte so với 1.073.741.824):

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

Tổng thời gian CPU nhiều hơn, nhưng phân phối tốt hơn giữa 4 lõi CPU của tôi, do đó cuối cùng nó sẽ mất ít thời gian hơn. Nút thắt bây giờ hexdump.

Nếu chúng ta sử dụng ddthay vì dựa trên dòng fold, chúng ta thực sự có thể giảm lượng công việc hexdumpcần làm và cải thiện sự cân bằng công việc giữa các 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')" -

(ở đây giả sử GNU ddcho nó iflag=fullblockstatus=none) cung cấp:

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

Quay lại việc tạo số ngẫu nhiên là nút cổ chai.

Bây giờ, như được chỉ ra bởi @OleTange, nếu bạn có openssltiện ích, bạn có thể sử dụng nó để có được một bộ xử lý giả ngẫu nhiên nhanh hơn (đặc biệt là trên các bộ xử lý có hướng dẫn AES).

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

trên hệ thống của tôi phun ra số lượng byte nhiều hơn 15 lần mỗi giây /dev/urandom. (Tôi không thể nhận xét về cách so sánh về nguồn ngẫu nhiên được bảo mật bằng mật mã nếu điều đó áp dụng cho trường hợp sử dụng của bạn).

</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')" -

Bây giờ cho:

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

trở lại hexdumplà nút cổ chai.

Vì tôi vẫn còn CPU để dự phòng, tôi có thể chạy hexdumpsong song 3 trong số đó .

</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')" -

( <&3cần thiết cho các shell khác với zshlệnh stdin on / dev / null khi chạy trong nền).

Bây giờ xuống còn 6,2 giây và CPU của tôi gần như được sử dụng đầy đủ.


3
Tôi chỉ xóa câu trả lời trước đó của tôi và bỏ phiếu cho câu hỏi này. Tôi đã không nhận được một số yêu cầu. Đẹp trả lời btw.
Marcelo

3
Tại sao bạn không tạo nhiều chữ số cho mỗi lần vượt qua? Ngay cả khi bạn đọc theo từng byte, bạn vẫn có thể tạo ra 2 chữ số mỗi lần
phuclv

@ LưuViênPhúc, dù sao tôi cũng đã xóa perlbiến thể chậm hơn đáng kể. Tôi không thể có được 2 chữ số trên mỗi byte với cách tiếp cận tr | Fold | paste đó.
Stéphane Chazelas

Tôi đang cố gắng hoặc tự mình thử điều này, nhưng bạn có thể thử chuyển đổi 42 byte cùng một lúc thành 100-102 chữ số bằng cách sử dụng bc(sau đó bỏ 0, 1 hoặc 2 chữ số có nghĩa nhất).
Tháp Eric

gitlab.com/ole.tange/tangetools/tree/master/rand tạo ra giả danh 1-4 GB mỗi giây (chất lượng AES).
Ole Tange

23

Nếu bạn có shufsẵn (coreutils GNU gần đây), bạn có thể làm điều này:

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

Trên máy ảo của tôi, điều này bây giờ chậm hơn một chút so với câu trả lời của Stéphane khoảng 3: 4.


shuftrên máy tính của công ty tôi không có -r, fmtkhông có -gquá
phuclv

2
@ LưuV TừPhúc Yep - YMMV. Tôi đã tìm thấy phiên bản lõi-utils 8.25 có những thứ này nhưng 8.4 thì không. Phiên bản nào bạn đang sử dụng?
Chấn thương kỹ thuật số

1
Tôi đang sử dụng coreutils 8.13
phuclv

@ StéphaneChazelas thông minh paste/ printfmẹo - cảm ơn. Câu trả lời của bạn bây giờ rõ ràng nhanh hơn.
Chấn thương kỹ thuật số

17

Nếu bạn không cần tính ngẫu nhiên chất lượng rất cao và phân phối gần đồng đều đủ tốt, bạn có thể thực hiện rất nhanh, đặc biệt là trên CPU hiện đại có vectơ số nguyên SIMD hiệu quả như x86 với SSE2 hoặc AVX2.

Điều này giống như câu trả lời của @ NominalAnimal vì cả hai chúng tôi đều có cùng một ý tưởng, nhưng được vector hóa thủ công cho x86. (Và với các số ngẫu nhiên có chất lượng kém hơn, nhưng vẫn có thể đủ tốt cho nhiều trường hợp sử dụng.) Điều này chạy nhanh hơn khoảng 15 hoặc 30 lần so với mã của @ Nominal, ở mức ~ 13GB / giây đầu ra ASCII trên Intel Haswell 2,5 GHz CPU với AVX2. Con số đó vẫn thấp hơn băng thông bộ nhớ chính tối đa theo lý thuyết (DDR3-1600 kênh đôi khoảng 25,6GB / giây), nhưng tôi đã định thời gian ghi thành / dev / null để nó thực sự chỉ viết lại bộ đệm vẫn còn nóng trong bộ đệm. Skylake nên chạy cùng mã này nhanh hơn đáng kể so với Haswell (xem phần dưới của câu trả lời này).

Giả sử bạn thực sự tắc nghẽn trên I / O vào đĩa hoặc đường ống này ở đâu đó, việc triển khai nhanh có nghĩa là CPU của bạn thậm chí không phải xung nhịp cao hơn không hoạt động. Nó sử dụng tổng năng lượng ít hơn nhiều để tạo ra kết quả. (Tuổi thọ pin / nhiệt / nóng lên toàn cầu.)

Tốc độ này nhanh đến mức bạn có thể không muốn ghi nó vào đĩa. Chỉ cần tạo lại khi cần thiết (từ cùng một hạt giống nếu bạn muốn lại cùng một dữ liệu). Ngay cả khi bạn muốn đưa nó vào một quy trình đa luồng có thể sử dụng tất cả các CPU, thì việc chạy này để dẫn dữ liệu đến nó sẽ khiến nó nóng lên trong bộ đệm L3 (và bộ đệm L2 trên lõi đã viết nó) và sử dụng rất nhiều thời gian CPU ít. (Nhưng lưu ý rằng đường ống thêm rất nhiều chi phí so với ghi vào /dev/null. Trên Skylake i7-6700k, đường ống đến wc -choặc một chương trình khác chỉ đọc + loại bỏ đầu vào của nó, nó chậm hơn khoảng 8 lần so với ghi/dev/null và chỉ sử dụng 70% CPU. Nhưng đó vẫn là 4.0GB / giây trên CPU 3.9GHz.

Việc tạo lại nó nhanh hơn đọc lại ngay cả từ SSD kết nối PCIe nhanh, nhưng IDK nếu nó hiệu quả hơn về năng lượng (hệ số nhân vectơ được giữ khá bận rộn và có lẽ nó khá ngốn điện, cùng với AVX2 khác ALU vector 256b). OTOH, tôi không biết việc đọc từ đĩa sẽ mất bao nhiêu thời gian từ thứ gì đó đã tối đa hóa tất cả các lõi xử lý đầu vào này. Tôi đoán rằng một chuyển đổi ngữ cảnh để tạo lại trong khối 128k có thể cạnh tranh với việc chạy mã hệ thống tập tin / pagecache và phân bổ các trang để đọc dữ liệu từ đĩa. Tất nhiên, nếu nó đã nóng trong pagecache, thì về cơ bản nó chỉ là memcpy. OTOH, chúng tôi đã viết về nhanh như memcpy! (phải phân chia băng thông bộ nhớ chính giữa đọc và ghi). (Cũng lưu ý rằng viết vào bộ nhớ đó 'rep movsb(tối ưu hóa memcpy và memset trong microcode, tránh RFO, do Andy Glew triển khai nó trong P6 (Pentium Pro) )).


Cho đến nay, đây chỉ là một bằng chứng về khái niệm và việc xử lý dòng mới chỉ gần đúng. Đó là sai xung quanh các đầu của bộ đệm power-of-2. Với thời gian phát triển hơn. Tôi tự tin rằng tôi có thể tìm ra một cách hiệu quả hơn để chèn các dòng mới cũng chính xác, với chi phí ít nhất là thấp như thế này (so với chỉ xuất ra các khoảng trắng). Tôi nghĩ rằng đây là một cái gì đó giống như 10 đến 20%. Tôi chỉ muốn biết chúng ta có thể chạy nó nhanh như thế nào, chứ không thực sự có một phiên bản bóng bẩy của nó, vì vậy tôi sẽ để phần đó làm bài tập cho người đọc, với các bình luận mô tả một số ý tưởng.


Trên Haswell i5 ở tốc độ tối đa 2,5 GHz, với RAM DDR3-1600 MHz, được định thời tạo ra 100GiB nhưng thu nhỏ lại. (Timed trên cygwin64 trên Win10 với gcc5.4 -O3 -march=native, bỏ qua -funroll-loopskể từ khi tôi đã có đủ thời gian khó khăn nhận được thời gian chạy đàng hoàng trên laptop mượn này. Nếu vừa khởi động Linux trên USB).

ghi vào / dev / null trừ khi có quy định khác.

  • James Hollis's: (chưa được thử nghiệm)
  • Phiên bản fwrite danh nghĩa: ~ 2.21s
  • this (SSE2): ~ 0.142s (thời gian không tính toán = real = 14.232s, user = 13.999s, sys = 0.187s).
  • này (AVX-128): ~ 0.140s
  • this (AVX2): ~ 0,073s (unscaled: real = 0m7.291s, user = 0m7.125s, sys = 0m0.155s).
  • cygwin này (AVX2) wc -c, với kích thước bộ đệm 128kiB: 0,32s với CPU ở tốc độ 2,38GHz (turbo lõi kép tối đa). (thời gian không tính toán: real = 32.466s user = 11.468s sys = 41.092s, bao gồm cả điều này và wc). Tuy nhiên, chỉ có một nửa dữ liệu thực sự được sao chép, bởi vì chương trình ngớ ngẩn của tôi cho rằng ghi đó có bộ đệm đầy đủ, mặc dù đó không phải là trường hợp và cygwin write () chỉ thực hiện 64k mỗi cuộc gọi vào một đường ống.

Vì vậy, với SSE2, tốc độ này nhanh hơn khoảng 15 lần so với mã vô hướng của @Nominal Animal. Với AVX2, nó nhanh hơn khoảng 30 lần. Tôi đã không thử một phiên bản mã Nominal chỉ sử dụng write()thay thế fwrite(), nhưng có lẽ đối với các bộ đệm lớn, stdio hầu như không sử dụng được. Nếu nó đang sao chép dữ liệu, điều đó sẽ gây ra rất nhiều chậm lại.


Lần để tạo 1GB dữ liệu trên Core2Duo E6600 (Merom 2.4GHz, 32kiB private L1, 4MiB chia sẻ bộ nhớ L2), DDR2-533MHz trong Linux 4.2 bit 64 bit (Ubuntu 15.10). Vẫn sử dụng kích thước bộ đệm 128kiB cho write (), chưa khám phá kích thước đó.

ghi vào / dev / null trừ khi có quy định khác.

  • (SSE2) điều này với việc xử lý dòng mới và 4 vectơ chữ số từ mỗi vectơ ngẫu nhiên: 0.183s (tính thời gian thực hiện 100GiB trong 18.3 giây, nhưng kết quả tương tự cho các lần chạy 1GiB). 1,85 hướng dẫn mỗi chu kỳ.
  • (SSE2) này, đường ống tới wc -c: 0,593s (unscaled: real = 59.266s user = 20.148s sys = 1m6.548s, bao gồm cả thời gian CPU của wc). Cùng một số lần gọi hệ thống write () như với cygwin, nhưng thực tế là tất cả các dữ liệu vì Linux xử lý tất cả 128k của một write () cho một đường ống.
  • fwrite()Phiên bản của NominalAnimal (gcc5.2 -O3 -march=native), chạy với ./decdig 100 $((1024*1024*1024/200)) > /dev/null: 3.19s +/- 0.1%, với hướng dẫn 1.40 mỗi chu kỳ. -funroll-loops có thể là một sự khác biệt nhỏ. clang-3.8 -O3 -march=native: 3,42 giây +/- 0,1%
  • Danh nghĩa- fwriteđường ống đến wc -c: real = 3.980s user = 3.176s sys = 2.080s
  • Phiên bản trực tuyến của James Hollis ( clang++-3.8 -O3 -march=native): 22.885 giây +/- 0,07%, với 0,84 hướng dẫn mỗi chu kỳ. (g ++ 5.2 chậm hơn một chút: 22,98s). Chỉ viết một dòng tại một thời điểm có lẽ bị tổn thương đáng kể.
  • Stéphane Chazelas's tr < /dev/urandom | ...: real = 41.430s user = 26.832s sys = 40.120s. trhầu hết thời gian đã nhận được tất cả lõi CPU, dành gần như toàn bộ thời gian cho trình điều khiển hạt nhân tạo ra các byte ngẫu nhiên và sao chép chúng vào một đường ống. Lõi khác trên máy lõi kép này đang chạy phần còn lại của đường ống.
  • time LC_ALL=C head -c512M </dev/urandom >/dev/null: tức là chỉ đọc nhiều ngẫu nhiên mà không có đường ống: real = 35.018s user = 0.036s sys = 34.940s.
  • Chương trình perl của Lưu Vĩnh Phúc (perl v5.20.2 từ Ubuntu15.10)
    LANG=en_CA.UTF-8:: real = 4m32.634s user = 4m3.288s sys = 0m29.364.
    LC_ALL=C LANG=C: real = 4m18.637s người dùng = 3m50.324s sys = 0m29.356s. Vẫn rất chậm.

  • (SSE2) này không có xử lý xuống dòng , và 3 hoặc 4 vectơ của các chữ số từ mỗi vector của byte ngẫu nhiên (gần như chính xác với tốc độ tương tự: các dig3 = v%10bước là về hòa vốn trên HW này): 0.166s (1,82 lệnh trong một chu kỳ) . Về cơ bản, đây là giới hạn thấp hơn cho những gì chúng ta có thể tiến gần đến với việc xử lý dòng mới hoàn toàn hiệu quả.

  • (SSE2) Phiên bản cũ của phiên bản này không xử lý dòng mới, nhưng chỉ nhận được một chữ số cho mỗi phần tử uint16_t bằng cách sử dụng v%10, 0,222 giây +/- 0,4%, 2,12 hướng dẫn mỗi chu kỳ. (Được biên dịch với gcc5.2 , -march=native -O3 -funroll-loops. Các vòng lặp không kiểm soát sẽ xảy ra để trợ giúp cho mã này trên phần cứng này. Đừng sử dụng nó một cách mù quáng, đặc biệt là cho các chương trình lớn).
  • (SSE2) Phiên bản cũ này, ghi vào một tệp (trên RAID10f2 gồm 3 ổ cứng từ tính nhanh, không được tối ưu hóa cho việc ghi): ~ 4 giây. Có thể đi nhanh hơn bằng cách điều chỉnh cài đặt bộ đệm I / O kernel để cho phép nhiều dữ liệu bẩn hơn trước các khối write (). Thời gian "hệ thống" vẫn là ~ 1,0 giây, cao hơn nhiều so với thời gian "người dùng". Trên hệ thống cũ này có RAM DDR2-533 chậm, hạt nhân sẽ mất nhiều thời gian hơn ~ 4 lần để ghi nhớ dữ liệu vào pagecache và chạy các chức năng XFS so với vòng lặp của tôi để tiếp tục viết lại tại chỗ trong bộ đệm vẫn còn nóng bộ nhớ cache.

Làm thế nào nó được thực hiện

Một PRNG nhanh là rõ ràng cần thiết. xorshift128 + có thể được vector hóa, do đó bạn có hai hoặc bốn bộ tạo 64 bit song song, trong các phần tử của vectơ SIMD. Mỗi bước tạo ra một vectơ đầy đủ của các byte ngẫu nhiên. ( 256b triển khai AVX2 tại đây với nội tại Intel ). Tôi đã chọn nó qua lựa chọn xorshift * của Nominal, bởi vì phép nhân số nguyên vector 64 bit chỉ có thể có trong SSE2 / AVX2 với các kỹ thuật có độ chính xác mở rộng .


Cho một vectơ byte ngẫu nhiên, chúng ta có thể chia từng phần tử 16 bit thành nhiều chữ số thập phân. Chúng tôi tạo ra nhiều vectơ của các phần tử 16 bit là mỗi một không gian ASCII chữ số + ASCII . Chúng tôi lưu trữ trực tiếp vào bộ đệm đầu ra của chúng tôi.

Phiên bản gốc của tôi chỉ được sử dụng x / 6554để lấy một chữ số ngẫu nhiên từ mọi phần tử uint16_t của một vectơ. Nó luôn nằm trong khoảng từ 0 đến 9, bao gồm. Đó là thiên vị từ 9, bởi vì (2^16 -1 ) / 6554chỉ 9,9923. (6554 = ceil ((2 ^ 16-1) / 10), đảm bảo rằng thương số luôn luôn <10.)

x/6554có thể được tính với một nhân với hằng số "ma thuật" ( đối ứng điểm cố định ) và dịch chuyển đúng của kết quả nửa cao. Đây là trường hợp tốt nhất để chia theo hằng số; một số ước số có nhiều hoạt động hơn và bộ phận đã ký có thêm công việc. x % 10có thành kiến ​​tương tự và không rẻ để tính toán. (đầu ra asm gcc là tương đương với x - 10*(x/10), tức là thêm nhân và trừ trên đầu trang của các bộ phận sử dụng một nghịch đảo mô-đun.) Ngoài ra, các bit thấp nhất của xorshift128 + không phải là chất lượng cao , vì vậy chia để có entropy từ bit cao là tốt hơn ( cho chất lượng cũng như tốc độ) hơn modulo để lấy entropy từ các bit thấp.

Tuy nhiên, chúng ta có thể sử dụng nhiều entropy hơn trong mỗi uint16_t bằng cách xem các chữ số thập phân thấp, như hàm @ Nominal's digit(). Để có hiệu suất tối đa, tôi quyết định lấy 3 chữ số thập phân thấp và x/6554, để lưu một PMULLW và PSUBW (và có thể là một số MOVDQA) so với tùy chọn chất lượng cao hơn là lấy 4 chữ số thập phân thấp. x / 6554 bị ảnh hưởng đôi chút bởi 3 chữ số thập phân thấp, do đó, có một số mối tương quan giữa các chữ số từ cùng một phần tử (tách 8 hoặc 16 chữ số trong đầu ra ASCII, tùy thuộc vào độ rộng của vectơ).

Tôi nghĩ gcc đang chia cho 100 và 1000, thay vì chuỗi dài hơn chia liên tiếp cho 10, vì vậy có lẽ nó không rút ngắn đáng kể độ dài của chuỗi phụ thuộc không mang theo vòng lặp tạo ra 4 kết quả từ mỗi đầu ra PRNG. port0 (vectơ nhân và dịch chuyển) là nút cổ chai do các phép nghịch đảo nhân mô-đun và các dịch chuyển trong xorshift +, do đó, chắc chắn rất hữu ích để lưu bội nhân vectơ.

xorshift + nhanh đến mức thậm chí chỉ sử dụng ~ 3,3 bit ngẫu nhiên từ mỗi 16 (tức là hiệu suất 20%) không chậm hơn nhiều so với việc cắt nó thành nhiều chữ số thập phân. Chúng tôi chỉ ước tính phân phối đồng đều, vì câu trả lời này tập trung vào tốc độ miễn là chất lượng không quá tệ.

Bất kỳ loại hành vi có điều kiện nào giữ một số lượng phần tử thay đổi sẽ tốn nhiều công sức hơn. (Nhưng có thể vẫn có thể được thực hiện một cách hiệu quả bằng cách sử dụng các kỹ thuật đóng gói trái SIMD . Tuy nhiên, điều đó kém hiệu quả hơn đối với kích thước phần tử nhỏ; các bảng tra cứu mặt nạ xáo trộn khổng lồ là không thể thực hiện được và không có xáo trộn làn đường AVX2 với nhỏ hơn 32- Các phần tử bit. Phiên bản PSHUFB 128b vẫn có thể tạo mặt nạ khi đang di chuyển với BMI2 PEXT / PDEP, giống như bạn có thể cho AVX2 với các phần tử lớn hơn , nhưng thật khó khăn vì số nguyên 64 bit chỉ chứa 8 byte. trên câu trả lời đó có một số mã có thể hoạt động cho số phần tử cao hơn.)


Nếu độ trễ của RNG là một nút cổ chai, chúng ta có thể đi nhanh hơn nữa bằng cách chạy song song hai vectơ máy phát, xen kẽ cái nào chúng ta sử dụng. Trình biên dịch vẫn có thể dễ dàng giữ mọi thứ trong các thanh ghi trong một vòng lặp không được kiểm soát và điều đó cho phép hai chuỗi phụ thuộc chạy song song.

Trong phiên bản hiện tại, cắt giảm đầu ra của PRNG, chúng tôi thực sự bị tắc nghẽn về thông lượng cổng 0, không phải độ trễ PRNG, do đó không cần điều đó.


Mã: phiên bản AVX2

Phiên bản đầy đủ với nhiều bình luận hơn về trình thám hiểm trình biên dịch Godbolt .

Không gọn gàng lắm, xin lỗi tôi phải đi ngủ và muốn đăng cái này lên.

Để có được phiên bản SSE2, s/_mm256/_mm, s/256/128/, s/v16u/v8u/, và thay đổi vector_size(32)đến 16. Ngoài ra thay đổi thặng dư xuống dòng từ 4 * 16-4 * 8. (Như tôi đã nói, mã rất lộn xộn và không được thiết lập tốt để biên dịch hai phiên bản. Ban đầu, tôi không có kế hoạch tạo phiên bản AVX2, nhưng sau đó tôi thực sự muốn thử nghiệm CPU Haswell mà tôi có quyền truy cập.)

#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);
    }

}

Biên dịch với gcc, clang hoặc ICC (hoặc hy vọng bất kỳ trình biên dịch nào khác hiểu phương ngữ GNU C của C99 và nội tại của Intel). Các phần mở rộng vectơ GNU C rất thuận tiện để có được trình biên dịch tạo ra các số ma thuật cho phép chia / modulo bằng cách sử dụng các phép nghịch đảo mô đun, và thỉnh thoảng __attribute__s rất hữu ích.

Điều này có thể được viết một cách hợp lý, nhưng nó sẽ mất nhiều mã hơn.


Ghi chú hiệu suất:

Cửa hàng chồng chéo để chèn dòng mới có chi phí đáng kể để quyết định vị trí đặt nó (dự đoán sai chi nhánh và tắc nghẽn giao diện trên Core2), nhưng bản thân cửa hàng không ảnh hưởng đến hiệu suất. Chỉ nhận xét rằng hướng dẫn lưu trữ trong asm của trình biên dịch (để tất cả các nhánh giống nhau) khiến hiệu suất trên Core2 hoàn toàn không thay đổi, với các lần chạy lặp lại cho cùng thời gian tới +/- dưới 1%. Vì vậy, tôi kết luận rằng bộ đệm / bộ đệm lưu trữ xử lý nó tốt.

Tuy nhiên, sử dụng một số loại cửa sổ xoay ascii_digitspacevới một yếu tố có dòng mới có thể còn nhanh hơn nữa, nếu chúng ta không kiểm soát đủ để bất kỳ bộ đếm / phân nhánh nào biến mất.


Ghi vào / dev / null về cơ bản là không có op, vì vậy bộ đệm có thể vẫn nóng trong bộ đệm L2 (256kiB trên mỗi lõi trên Haswell). Việc tăng tốc hoàn hảo từ vectơ 128b lên vectơ 256b được mong đợi: không có hướng dẫn thêm và mọi thứ (bao gồm các cửa hàng) xảy ra với chiều rộng gấp đôi. Tuy nhiên, nhánh chèn dòng mới được thực hiện gấp đôi. Tôi không may đã không có thời gian trên thiết lập cygwin Haswell của tôi với phần đó được chỉnh sửa #ifdef.

2,5 GHz * 32B / 13,7GB / s = 5,84 chu kỳ trên mỗi cửa hàng AVX2 trên Haswell. Điều đó khá tốt, nhưng có thể nhanh hơn. Có lẽ có một số chi phí trong hệ thống cygwin gọi hơn tôi nghĩ. Tôi đã không thử nhận xét những điều đó trong đầu ra asm của trình biên dịch (điều này sẽ đảm bảo rằng không có gì được tối ưu hóa.)

Bộ nhớ cache L1 có thể duy trì một cửa hàng 32B mỗi đồng hồ và L2 không phải là băng thông thấp hơn nhiều (tuy nhiên độ trễ cao hơn).

Khi tôi xem IACA một vài phiên bản trước đây (không phân nhánh cho dòng mới, nhưng chỉ nhận được một vectơ ASCII trên mỗi vectơ RNG), nó đã dự đoán một cái gì đó giống như một cửa hàng véc tơ 32B trên 4 hoặc 5 đồng hồ.

Tôi đã hy vọng sẽ nhận được nhiều sự tăng tốc hơn từ việc trích xuất thêm dữ liệu từ mỗi kết quả RNG, dựa trên việc xem xét bản thân, xem xét các hướng dẫn của Agner Fog và các tài nguyên tối ưu hóa khác mà tôi đã thêm các liên kết trong wiki thẻ SO x86 .)

Có khả năng nó sẽ nhanh hơn đáng kể trên Skylake , trong đó nhân số nguyên và dịch chuyển có thể chạy trên hai lần số cổng (p0 / p1) gấp đôi so với Haswell (chỉ p0). xorshift và trích xuất chữ số đều sử dụng rất nhiều ca và nhân. ( Cập nhật: Skylake chạy nó ở 3.02 IPC, cung cấp cho chúng tôi 3,77 chu kỳ trên mỗi cửa hàng AVX2 32 byte , thời gian là 0,030 giây trên mỗi lần lặp 1GB, ghi /dev/nullvào Linux 4.15 trên i7-6700k ở tốc độ 3,9 GHz.


Nó không yêu cầu chế độ 64 bit để hoạt động tốt . Phiên bản SSE2 chỉ nhanh khi được biên dịch -m32, vì nó không cần nhiều thanh ghi vectơ và tất cả toán học 64 bit được thực hiện trong các vectơ, không phải các thanh ghi mục đích chung.

Nó thực sự nhanh hơn một chút trong chế độ 32 bit trên Core2, bởi vì phản ứng tổng hợp macro / nhánh chỉ hoạt động ở chế độ 32 bit, do đó, có ít lỗi hơn cho lõi không theo thứ tự (18.3s (1.85 Hướng dẫn trên mỗi đồng hồ) so với 16,9 giây (2.0 IPC)). Kích thước mã nhỏ hơn do không có tiền tố REX cũng giúp bộ giải mã của Core2.

Ngoài ra, một số di chuyển vector reg-reg được thay thế bằng tải, vì không phải tất cả các hằng số sửa trong regs vector nữa. Vì thông lượng tải từ bộ đệm L1 không phải là nút cổ chai, điều này thực sự có ích. (ví dụ: nhân với một vectơ không đổi là set1(10): movdqa xmm0, xmm10/ pmullw xmm0, xmm1biến thành movdqa xmm0, [constant]/ pmullw xmm0, xmm1.) Vì reg-reg MOVDQA yêu cầu cổng ALU, nó cạnh tranh với công việc thực tế đang được thực hiện, nhưng tải MOVDQA chỉ cạnh tranh về băng thông giải mã mặt trước. (Có một địa chỉ 4 byte bên trong nhiều hướng dẫn sẽ loại bỏ rất nhiều lợi ích từ việc lưu tiền tố REX.

Tôi sẽ không ngạc nhiên nếu việc lưu các ALU MOVDQA là nơi thu được lợi nhuận thực sự, vì tiền tuyến phải theo kịp với mức trung bình 2.0 IPC khá tốt.

Tất cả những khác biệt này biến mất trên Haswell, nơi toàn bộ mọi thứ sẽ chạy từ bộ đệm được giải mã, nếu không phải là bộ đệm loopback. ALU + hợp nhất vĩ mô nhánh hoạt động ở cả hai chế độ kể từ Nehalem.


6
Tôi chỉ thích cách bạn đi "chế độ con thú" vào chủ đề! :) Quan trọng hơn, đó là một ví dụ tuyệt vời về loại lợi ích có sẵn, nếu bạn thực sự cần hoặc muốn đạt được hiệu suất tối đa, sử dụng kiến ​​thức rất thấp về phần cứng trong tay. Thêm vào đó, chúng tôi chỉ sử dụng một chủ đề ở đây; hầu hết các bộ xử lý Intel / AMD dành cho máy tính để bàn và máy chủ hiện tại (và thậm chí cả ARM trong máy tính bảng nhẹ và SBC) đều có nhiều lõi, do đó vẫn có nhiều bộ tăng tốc theo thời gian thực hơn. Và cuối cùng, làm thế nào các câu hỏi "cách nhanh nhất" không thực tế , do nỗ lực tuyệt đối liên quan.
Động vật danh nghĩa

1
@NominalAnimal: Vâng, ngay cả lõi tứ hoặc octo ARM chậm cũng có thể dễ dàng bão hòa băng thông bộ nhớ chính làm điều tương tự với NEON (ngay cả khi chúng được nối với DDR3 kênh kép nhanh), nếu nó có thêm và thay đổi SIMD 64 bit . Tôi giả sử NEON có bội số kích thước phần tử 16 bit cho công việc âm thanh. Lập lịch hướng dẫn sẽ làm việc nhiều hơn cho ARM theo thứ tự, bởi vì mỗi lần lặp của chuỗi phụ thuộc mang theo vòng lặp (xorshift128 +) cung cấp một vài chuỗi phụ thuộc độc lập để cắt và lưu trữ vào bộ nhớ ...
Peter Dây

... Việc thực hiện không theo thứ tự ăn vào bữa sáng, vì toàn bộ điều này đủ ngắn để một số lần lặp phù hợp với ROB (192 uops trên HSW IIRC). (tức là "cửa sổ" của các hướng dẫn mà việc thực hiện không theo thứ tự nhìn thấy bao gồm nhiều lần lặp). Vì vậy, CPU có thể hoàn thành lưu trữ cuối cùng cho 2 hoặc 3 lần lặp trước trong khi cũng bắt đầu vào đầu của lần lặp hiện tại. Điều này che giấu độ trễ của các chuỗi độc lập, vì vậy chỉ có vấn đề thông lượng. Trên lõi theo thứ tự, điều này sẽ yêu cầu đường ống phần mềm ...
Peter Cordes

... Một trình biên dịch ARM tốt sẽ thực hiện một số điều đó cho bạn nếu bạn viết nó bằng nội tại (hoặc cú pháp vectơ gốc GNU C cho toàn bộ, như tôi nên làm ở vị trí đầu tiên). Tôi không có bất kỳ kinh nghiệm nào trong việc thực hiện điều đó, vì vậy bạn có thể cần phải xoa bóp vòng lặp của mình và có thể thực hiện một số thao tác hủy đăng ký / phần mềm thủ công trong nguồn để có được asm tốt. (Có một số lõi ARM không theo thứ tự, được tìm thấy trong các điện thoại cao cấp hơn, nhưng chúng không có cửa sổ không có thứ tự lớn như Haswell. để đạt được từ việc tìm thêm ILP).
Peter Cordes

1
@NominalAnimal: cũng vậy, đồng ý về tính hay ho của câu hỏi. "Nhanh nhất" không có giới hạn về chất lượng ngẫu nhiên là ngớ ngẩn ... Với BTRFS, cùng một dữ liệu trên đĩa có thể là một phần của tệp nhiều lần (xem EXTENT_SAME trong 4.2 ). Vì vậy, bạn có thể tạo ngẫu nhiên 4kiB hoặc 1MB và lặp lại nó. Đó là sự ngẫu nhiên với một khoảng thời gian ngắn, nhưng nó vẫn là ngẫu nhiên và chỉ tốn I / O siêu dữ liệu. (Trên thực tế, bạn cần khối để kết thúc với một dòng mới lcm (4096, 4096 * 200) = 4096 * 200 = 819200 = 800kiB, vì vậy khối lặp lại của bạn là bất kỳ nhiều về điều đó..)
Peter Cordes

14

Đây là một giải pháp tôi hy vọng là đơn giản để hiểu:

od -An -x /dev/urandom | tr -dc 0-9 | fold -w100 | awk NF=NF FS= | head -c1G
  • odtạo ra một dòng thống nhất các chữ số thập lục phân từ /dev/random.
  • trthoát khỏi các chữ cái, chỉ giữ các 0-9chữ số
  • fold đảm bảo có 100 chữ số trên mỗi dòng
  • awk chèn khoảng trắng vào trong dòng
  • head cắt ngắn đầu vào thành 1 gigabyte

2
Đó là một cách thay thế tốt để tạo ra nhiều hơn một chữ số theo byte / dev / ngẫu nhiên trong khi vẫn có phân phối đồng đều, tạo ra 320 chữ số cho mỗi 256 byte / dev / urandom trung bình (ít hơn khi bạn chuyển đổi byte <200 modulo 100 đến thập phân cung cấp cho bạn 400 chữ số cho mỗi 256 byte).
Stéphane Chazelas

6

Bạn có thể sử dụng jotlệnh cho việc này:

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

1
@DigitalTrauma Phiên bản của fmttôi không có tùy chọn độ rộng mục tiêu. Dù sao, nó sẽ chính xác bởi vì tất cả các chữ số chiếm chính xác một cột!
vườn

Đối với bản ghi, fmtphiên bản của tôi là fmt (GNU coreutils) 8.25(Ubuntu 16.04)
Chấn thương kỹ thuật số

2
số đúng cho nửa gb là: 1024 * 1024 * 1024/2 =536870912
Olivier Dulac

1
@OlivierDulac Phụ thuộc vào "gigabyte" mà bạn đang nói đến. Một số người sử dụng 1 Gb có nghĩa là 10 ^ 9 thay vì 2 ^ 30, mặc dù về mặt kỹ thuật không chính xác. Thêm vào đó tôi thích số tròn đẹp :)
vườn

6
@gardenhead, ngày càng có nhiều người có xu hướng chuyển sang Gigabyte == 1e9 và Gibibyte == 2 ^ 30 vì đó là định nghĩa tiêu chuẩn IEC. Xem Wikipedia . Lưu ý rằng bản thân Gb sẽ là Giga -bit .
Stéphane Chazelas

6

Điều này tương tự như phương pháp của Stéphane Chazelas, tuy nhiên tôi đọc 64 bit cùng một lúc để cải thiện hiệu suất. Phân phối vẫn thống nhất nhưng bây giờ bạn nhận được 19 chữ số cho mỗi 8 byte thay vì chỉ 8 trong trường hợp tốt nhất như trước đây

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

Trên nền tảng 32 bit, 9 chữ số sẽ được đọc mỗi lần thay vì 19.


Điều này có thể tăng ngoại lệ nếu hệ thống của bạn không hỗ trợ số nguyên 64 bit hoặc perlkhông được biên dịch với hỗ trợ quad.
cuonglm

@cuonglm đúng như tôi đã nói nếu perl không phải là 64 bit trên hệ thống đó thì chương trình phải được thay đổi để next if $n >= 1000000000; $s = sprintf("%09u", $n);chỉ có 9 chữ số
phuclv

Bạn không thể, chương trình sẽ sập $n = unpack("Q")khi quad không được hỗ trợ.
cuonglm

1
@cuonglm đổi thành BEGIN{$/=\4; $,=" "} $n = unpack("L");cũng
phuclv

1
Xin lỗi, nhưng điều này nhận được 19 chữ số từ 8 byte đầu vào chỉ khoảng 54,2% thời gian và không có phần còn lại, trung bình 1,29 chữ số cho mỗi byte đầu vào. Nếu giống như Stephane bạn sử dụng <16e18và chia cho 16, bạn nhận được 18 chữ số 86,7% cho 1,95 dpB. Với 32 bit, <4e9 /4nhận được 9 chữ số 93,1% cho 2,10 dpB. Nhưng 5 byte (dưới dạng hex (H10)) <1e12cung cấp 12 chữ số 90,9% cho 2,18 dpB hoặc chia hex thành một nửa và thực hiện mỗi nửa <1e6 cho 6 chữ số 95,4% cho 2,29 dpB; điều này đạt đến giới hạn của log_10 (256) = 2,41.
dave_thndry_085

3

Tôi đồng ý với Nominal Animal trong việc sử dụng ngôn ngữ lập trình được biên dịch nếu bạn cần tốc độ. Tuy nhiên, bạn không phải viết mã RNG của riêng mình bằng C. C ++ 11 cung cấp Mersenne Twister tuyệt vời như một phần của thư viện chuẩn.

#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;
}

Đoạn mã trên khá đơn giản và mất khoảng một phút khi tôi chuyển đầu ra thành một tệp. Chúng ta có thể đi nhanh hơn rất nhiều bằng cách tạo một chuỗi đủ lớn cho 100 chữ số và hack các chữ số vào đó. Điều này cho phép chúng tôi gọi cout mỗi dòng chứ không phải mỗi chữ số.

#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;
}

Mã này mất máy của tôi khoảng sáu giây. Hãy nhớ rằng đó là đầu ra tiêu chuẩn, vì vậy hãy đưa nó vào một tệp.

Tôi có một vài lời từ chối. Đầu tiên, tôi đang viết cái này trên PC Windows. Tôi nghĩ rằng tất cả các thư viện đều có mặt trên Linux, nhưng nếu tôi sai, hãy chắc chắn chỉ ra nó.

Ngoài ra, nó thực sự xuất ra chính xác nửa tỷ chữ số được phân tách bằng không gian, về mặt kỹ thuật là một gigabyte nhưng có thể không chính xác như những gì bạn muốn. Nó xuất ra 5 triệu dòng, 100 chữ số trên mỗi dòng. Nếu sự khác biệt là quan trọng, bạn có thể tăng số lượng dòng. Trên hộp Windows của tôi, tệp dường như lớn hơn 10 ^ 9 byte, mà tôi nghĩ là phải làm gì đó với các ký tự dòng mới bổ sung.


2
Này, những lời chỉ trích không thực sự công bằng! :) Hầu hết chương trình của tôi là phân tích tham số dòng lệnh. Nếu tôi cũng bình luận omit, kiểm tra lỗi, và hardcode số cột và dòng đầu ra, tôi có thể làm cho nó ít hơn gấp đôi so với kích thước của mã của bạn - hầu như không monstruous . :) Bỏ qua một bên: Có, các thư viện có sẵn trong hầu hết các bản phân phối Linux. Trên máy tính xách tay của tôi, dòng thời gian của bạn mất khoảng 14 giây, trong khi phiên bản dòng của tôi chỉ mất 1,3 giây. Sự khác biệt chỉ là do PRNG: Mersenne Twister chậm hơn nhiều so với Xorshift64 *.
Động vật danh nghĩa

1
Có một điều thực tế tôi muốn chỉ ra rằng bạn đã bỏ lỡ, nhưng tôi hy vọng bạn không coi đó là sự tiêu cực, chỉ là một điều cần suy nghĩ: Như tôi đã đề cập trong câu trả lời của tôi, các chương trình one-shot hiếm khi đáng giá thời gian họ dành để viết. Đó là lý do tại sao việc thêm phân tích cú pháp dòng lệnh và văn bản sử dụng trợ giúp hầu như luôn luôn đáng giá. Tôi có một bộ lớn các chương trình tiện ích như vậy, và thay vì săn các nguồn của chúng để tìm hiểu xem mỗi chương trình đó làm gì, tôi chỉ chạy chúng, vì vậy chúng sẽ cho tôi biết; và tôi có thể sửa đổi hành vi của họ đủ để phù hợp với nhiều hơn một nhu cầu. Khấu hao chi phí phát triển.
Động vật danh nghĩa

@NominalAnimal Một điều quan trọng khác là bạn đã đưa ra kết quả đầu ra /dev/nullnhanh hơn nhiều so với việc ghi vào một tệp thực
phuclv

@ LưuViênPhúc: À, không hẳn. Lappy này có ổ SSD 128 GB của Samsung, với tốc độ đọc và ghi tuần tự ~ 500 MB / giây. Đặt bốn cấu hình Linux-RAID0 và bạn sẽ có được một gigabyte một giây đọc và ghi khi tạo các bộ dữ liệu lớn như vậy (tôi ước tính ~ 1,75 TB / s). 1GB / s đã đạt được cách đây nhiều năm với 12 ổ đĩa SATA (quay đĩa, thậm chí không phải SSD) với Linux sw-RAID0. (Lưu ý:. Byte / s, không bit / s) Chắc chắn, nó có vẻ ngớ ngẩn đối với một máy tính "bình thường", nhưng những người chơi với các tập dữ liệu lớn, thấy rằng đáng giá - bạn cạo thời gian tắt tất cả những gì bạn làm (với các tập dữ liệu lớn) theo cách đó
Động vật danh nghĩa

1
@NominalAnimal và Lu'u: Quan trọng hơn, nếu bạn có đủ RAM, chương trình có thể thoát tốt trước khi tất cả dữ liệu trên đĩa. Hầu hết các công việc trong một write()cuộc gọi hệ thống lớn là một memcpy vào pagecache, nó chỉ chặn nếu kernel quyết định làm điều đó thay vì phân bổ thêm không gian bộ đệm. Chương trình này chỉ nên tắc nghẽn trên I / O của đĩa khi bộ nhớ bị thắt hoặc nếu bạn đã sử dụng O_DIRECT để bỏ qua pagecache. Nếu bạn write()trong các khối nhỏ hơn kích thước bộ đệm, hy vọng dữ liệu của bạn chỉ đi vào bộ nhớ chính một lần và bộ đệm được viết lại tại chỗ vẫn nóng trong bộ đệm L2 hoặc L3.
Peter Cordes

1

Nó phụ thuộc vào định nghĩa của bạn về "ngẫu nhiên". Nếu bạn có nghĩa là ngẫu nhiên về mật mã, bạn chỉ cần có một thư viện tốt và cắn viên đạn, chờ cho nó chạy.

Nếu bạn chỉ cần một cái gì đó trông khá ngẫu nhiên, đây là một cách dễ dàng:

  1. Nhận một tập tin dài vài Gb. Bộ phim yêu thích của bạn sẽ tốt
  2. Gzip nó, một cách dễ dàng để loại bỏ các mẫu lặp đi lặp lại
  3. Đi qua tệp một nybble (nửa byte) tại một thời điểm. Mỗi giá trị sẽ nằm trong khoảng từ 0 đến 15. Vứt bỏ bất kỳ ít hơn 1 hoặc lớn hơn 10. Trừ 1 từ mỗi tỷ người sống sót đầu tiên và viết nó ra dưới dạng một chữ số.

Có thể mất một giờ để chạy trên một máy chậm; đủ nhanh và đủ ngẫu nhiên cho hầu hết các mục đích.


9
/dev/urandomcó khả năng tốt hơn gzip, cả về tốc độ và tính ngẫu nhiên.
Stig Hemmer

Get a file that is several Gb longbạn sẽ cần một tệp ** ít nhất 8Gb` để có được tệp 1GB
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
Chào mừng đến với trang web! Xem các liên kết trên trang hồ sơ của tôi. Có rất nhiều vấn đề ở đây mà tôi thấy hầu như phổ biến trong các kịch bản shell, nhưng điều đó không làm cho chúng đúng.
tự đại diện

2
@Wildcard: không bao giờ cat file | trkhi bạn có thể tr <file. IIRC, bạn thậm chí có thể <file tr. Tôi nghĩ rằng bạn chỉ đang nói về kịch bản shell này trông có vẻ lộn xộn và chậm chạp, như du | awksau mỗi dòng để kiểm tra kích thước và mở lại tệp để nối thêm mỗi dòng thay vì chuyển hướng ra ngoài vòng lặp.
Peter Cordes

2
@PeterCordes, vâng. Tại sao sử dụng vòng lặp shell để xử lý văn bản được coi là thực tiễn xấu? đặc biệt có liên quan, tập lệnh này dựa trên ý tưởng rằng Bash là ngôn ngữ lập trình như C, nhưng không phải vậy. Nhưng, \ @NamNT, tôi hy vọng bạn sẽ tìm hiểu thêm về trang web này bởi vì rõ ràng rằng bạn có đầu óc rất logic. :)
tự đại diện

4
@PeterCordes cat /dev/urandom | busy-cmdlà một trong những trường hợp hiếm hoi mà nó có thể có ý nghĩa vì nó có thể phân chia thế hệ ngẫu nhiên và cmd bận rộn giữa các bộ xử lý. Không quá nhiều cho tr nhưng làm cho một sự khác biệt cho Sam odchẳng hạn.
Stéphane Chazelas

1
@ StéphaneChazelas: ồ đúng rồi !! Vâng, cuộc gọi hệ thống read () là thời gian dành cho CPU RNG.
Peter Cordes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.