วิธีที่เร็วที่สุดในการสร้างไฟล์ข้อความขนาด 1 GB มีตัวเลขแบบสุ่มคืออะไร


52

ฉันลองสคริปต์ทุบตี แต่ใช้เวลานานเกินไปในการสร้างไฟล์ 1 MB แบบง่าย ฉันคิดว่าคำตอบอยู่ที่การใช้/dev/randomหรือ/dev/urandomแต่โพสต์อื่น ๆ ที่นี่แสดงให้เห็นว่าจะเพิ่มข้อมูลทุกชนิดลงในไฟล์โดยใช้สิ่งเหล่านี้ได้อย่างไร แต่ฉันต้องการเพิ่มตัวเลขเท่านั้น

ดังนั้นมีคำสั่งที่ฉันสามารถใช้เพื่อสร้างไฟล์สุ่มขนาด 1 GB ที่มีตัวเลขเพียงระหว่าง 0 และ 9 หรือไม่

แก้ไข: ฉันต้องการให้ผลลัพธ์เป็นแบบนี้

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จำนวนบรรทัด นี่คือสิ่งที่ฉันไม่สนใจฉันต้องการขนาดสุดท้ายของฉันเป็น 1 GB

แก้ไข: ฉันใช้ Ubuntu 16.04 LTS



21
คุณอาจจะพูดในสิ่งที่คุณหมายถึงโดย "สุ่ม" - ความแรงของการเข้ารหัสแบบสุ่มหรือเป็นลำดับหลอกแบบสุ่มเพียงพอหรือไม่
Toby Speight

4
@posixKing: โปรดทราบว่าแม้ว่าคำตอบของฉันจะเป็นคำพูดที่แก้ม - ฉันไม่แนะนำให้เขียนโปรแกรม C สำหรับงานดังกล่าว! - หากคุณสร้างชุดข้อมูลขนาดใหญ่เช่นนี้เป็นประจำหรือสร้างบ่อยครั้งวิธีการนี้อาจช่วยคุณประหยัดเวลา (บนแล็ปท็อปของฉันมันสร้าง 1GB ของตัวเลขคั่นด้วยช่องว่างในเวลาประมาณสิบวินาที) อย่างไรก็ตามถ้านี่เป็นครั้งเดียวไม่ต้องคิดเกี่ยวกับการเขียนโปรแกรม C สำหรับเรื่องนี้ (เว้นแต่คุณจะชอบการเขียนโปรแกรมและพิจารณาสิ่งนี้ การปฏิบัติหรือเช่นนั้น); คำสั่งเชลล์และยูทิลิตีบรรลุผลสำเร็จโดยใช้เวลาและความพยายามน้อยลง
สัตว์ที่กำหนด

7
มันค่อนข้างเร็วและเป็นไปตามมาตรฐาน RFC 1149.5:yes 4 | tr '\n' ' ' | fold -w 200 | head -c1G
Matthew Crumley

คำตอบ:


38

นี่เป็นส่วนหนึ่งของคำตอบแบบปากต่อปากเนื่องจากชื่อคำถาม

เมื่อคุณมองหา"วิธีที่เร็วที่สุดในการ ... "คำตอบนั้นมักเป็นเครื่องมือพิเศษเสมอ "คำตอบ" นี้แสดงเครื่องมือหนึ่งดังกล่าวเพียงเพื่อให้คุณสามารถทดสอบ

นี่ไม่ใช่คำตอบที่จริงจังเพราะคุณไม่ควรมองหาเครื่องมือพิเศษสำหรับงานที่คุณทำเพียงครั้งเดียวหรือน้อยมาก คุณจะเห็นว่าคุณจะใช้เวลามากขึ้นในการค้นหาเครื่องมือและเรียนรู้เกี่ยวกับสิ่งเหล่านี้มากกว่าทำสิ่งต่างๆ เชลล์และยูทิลิตี้ชอบbashและawkไม่เร็วที่สุด แต่โดยปกติคุณสามารถเขียนหนึ่งซับเพื่อทำงานให้สำเร็จใช้เวลาเพียงไม่กี่วินาที ภาษาสคริปต์ที่ดีขึ้นเช่นperlสามารถใช้แม้ว่าเส้นโค้งการเรียนรู้สำหรับperlสูงชันและฉันลังเลที่จะแนะนำสำหรับวัตถุประสงค์ดังกล่าวเพราะฉันได้รับบาดเจ็บจากโครงการ perl อันยิ่งใหญ่ pythonในทางตรงกันข้ามคนพิการเล็กน้อยโดย I / O ค่อนข้างช้า เป็นเพียงปัญหาเมื่อคุณกรองหรือสร้างกิกะไบต์ของข้อมูล

ไม่ว่าในกรณีใดโปรแกรมตัวอย่าง C89 ต่อไปนี้ (ซึ่งใช้ POSIX.1 สำหรับนาฬิกาที่มีความแม่นยำสูงกว่าถ้ามี) ควรบรรลุอัตราการสร้างประมาณ 100 MB / s (ทดสอบใน Linux บนแล็ปท็อปที่มีโปรเซสเซอร์ Intel i5-4200U ถึง/dev/null) โดยใช้ตัวสร้างตัวเลขสุ่มหลอกที่ดี (ผลลัพธ์ควรผ่านการทดสอบ BigCrunch ทั้งหมดยกเว้นการทดสอบ MatrixRank เนื่องจากรหัสใช้xorshift64 *และวิธีการยกเว้นเพื่อหลีกเลี่ยงการทำให้ตัวเลขเป็นตัวเลข)

ทศนิยม digits.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()มันครั้งเดียวแทนที่จะส่งออกแต่ละหลักในแต่ละครั้ง โปรดทราบว่าเรายังคงรักษากระแสข้อมูลบัฟเฟอร์ทั้งหมดเพื่อหลีกเลี่ยงการเขียนบางส่วน (ไม่ใช่พลังงานของสอง) หากเอาต์พุตเป็นอุปกรณ์บล็อก

#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 เพื่อให้แน่ใจว่ามีการแจกแจงตัวเลขอย่างสม่ำเสมอ (ไม่รวมศูนย์โปรดดูตัวอย่างที่นี่สำหรับการเปรียบเทียบและรายละเอียดเกี่ยวกับเครื่องกำเนิดตัวเลขสุ่มหลอกต่างๆ)

รวบรวมโดยใช้ตัวอย่างเช่น

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.txtตามที่ต้องการโดย OP

โปรดทราบว่าโปรแกรมนั้นเขียนด้วยความสามารถในการอ่านได้มากกว่าประสิทธิภาพ ความตั้งใจของฉันที่นี่ไม่ใช่เพื่อแสดงประสิทธิภาพของรหัส - ฉันใช้ POSIX.1 และ I / O ระดับต่ำอยู่แล้วแทนที่จะใช้อินเตอร์เฟส C ทั่วไป - แต่เพื่อให้คุณเห็นว่ามีความสมดุลแบบใดที่ใช้ไปด้วยความพยายาม ในการพัฒนาเครื่องมือเฉพาะเทียบกับประสิทธิภาพของพวกเขาเมื่อเทียบกับหนึ่ง liners หรือสั้น shell หรือ awk scriptlets

การใช้ไลบรารี GNU C การเรียกใช้fputc()ฟังก์ชั่นสำหรับเอาต์พุตตัวละครทุกตัวนั้นมีค่าใช้จ่ายน้อยมาก (จากการเรียกใช้ฟังก์ชันทางอ้อมหรือเงื่อนไข - FILEอินเทอร์เฟซนั้นค่อนข้างซับซ้อนและใช้งานได้จริง) ในแล็ปท็อป Intel Core i5-4200U นี้โดยเฉพาะการเปลี่ยนเส้นทางการส่งออกไป/dev/nullเป็นรุ่นแรก (fputc) ใช้เวลาประมาณ 11 วินาทีในขณะที่รุ่น line-at-a-time ใช้เวลาเพียง 1.3 วินาที

ฉันมักจะเขียนโปรแกรมและเครื่องกำเนิดไฟฟ้าเช่นนี้บ่อยๆเพราะฉันชอบเล่นกับชุดข้อมูลขนาดใหญ่ ฉันประหลาดแบบนั้น ตัวอย่างเช่นฉันเคยเขียนโปรแกรมเพื่อพิมพ์ค่าทศนิยมลอยตัวบวก IEEE-754 ทั้งหมดลงในไฟล์ข้อความที่มีความแม่นยำเพียงพอที่จะให้ค่าเดียวกันแน่นอนเมื่อแยกวิเคราะห์ ไฟล์มีขนาดไม่กี่กิกะไบต์ (อาจเป็น 4G หรือมากกว่านั้น); มีไม่มากที่เป็นบวกแน่นอนอย่างfloatที่เราคิด ฉันใช้สิ่งนี้เพื่อเปรียบเทียบการใช้งานที่อ่านและแยกวิเคราะห์ข้อมูลดังกล่าว

สำหรับกรณีการใช้งานปกติเช่น OP กำลังมีอยู่เชลล์สคริปต์และ scriptlets และ one-liners เป็นวิธีที่ดีกว่า ใช้เวลาน้อยลงในการทำงานให้สำเร็จ (ยกเว้นถ้าพวกเขาต้องการไฟล์ที่แตกต่างกันทุกวันหรือมากกว่านั้นหรือมีคนจำนวนมากที่ต้องการไฟล์ที่แตกต่างกันซึ่งในกรณีที่เป็นของหายากเครื่องมือเฉพาะอย่างเช่นด้านบนอาจรับประกันความพยายามที่ใช้ไป)


ใช่อาจmmap()เป็นเส้นทางที่ง่ายที่สุดในการใช้ความเร็ว I / O ที่ดีที่สุด - เป็นเกณฑ์มาตรฐานก่อนที่จะทำการเรียกร้องใด ๆ !
Toby Speight

@TobySpeight: ในลินุกซ์ระดับต่ำ I / O เช่นใช้เป็นมักจะเร็วกว่าwrite() ไม่ช้ามาก ใช่ฉันเคยเปรียบเทียบว่า (ไม่ใช่สำหรับตัวอย่างเฉพาะนี้); ในกลุ่มก้อนขนาดใหญ่ (262144, 524288 หรือ 1048576 ไบต์) มีแนวโน้มที่จะดีกว่าวิธีอื่น ๆ เวอร์ชันของการนำไปใช้ในไลบรารี GNU C (ซึ่งฉันได้ทำการเปรียบเทียบอย่างกว้างขวาง) นั้นช้าด้วยเหตุผลหลายประการ; โดยเฉพาะอย่างยิ่งการนำไปปฏิบัติต้องทำการกระโดดตามเงื่อนไขหรือการโทรทางอ้อมสำหรับอักขระทุกตัวที่เพิ่มเข้ามา ค่าโสหุ้ยเล็กน้อยที่เกิดขึ้นจึงมักจะเพิ่มขึ้น mmap()fwrite()write()fputc()
สัตว์ที่กำหนด

เพิ่งเริ่มสนใจ - คุณทำการเปรียบเทียบประสิทธิภาพกับคำตอบอื่น ๆ หรือไม่?
Digital Trauma

2
@DigitalTrauma: /dev/nullฉันเพียงแค่ขับรถให้คุณเปลี่ยนเส้นทางออกไป สคริปต์ของStéphane Chazelasใช้เวลาประมาณ 52 วินาที ตัวอย่างโค้ด (รวมถึงตัวheadกรอง) ประมาณ 58 วินาที shufตัวอย่างของคุณ(ด้วยการกำหนดเวลาที่ถูกต้องคุณวัดเฉพาะเวลา shuf สมมติว่าการวางไม่ใช้เวลานาน) ใช้เวลาประมาณ 69 วินาที James Hollis ' C ++ 11 โปรแกรม line-at-a-time ใช้เวลา 14 วินาที โปรแกรมด้านบนใช้เวลา 10 วินาที
สัตว์ที่กำหนด

3
(ลืมความคิดข้างต้นของฉันไปเสียแล้ว) จุดคือเลือกอัลกอริธึมที่เหมาะสม - PRNG แบบสุ่ม แต่เร็วพอที่นี่ - ให้ผลเพิ่มความเร็วเกือบ 10 เท่า (10 ×) (โปรแกรมรุ่นหลังของฉันเร็วกว่าเชลล์หรือตัวอย่างโค้ดประมาณ 40 เท่า) นี่เป็นเรื่องปกติ บางทีฉันควรเน้นการเลือกอัลกอริทึมที่ถูกต้องเมื่อเขียนโปรแกรมเพิ่มเติมในคำตอบของฉันข้างต้น? (ในทางกลับกันนี่ไม่ใช่คำถามการเขียนโปรแกรม แต่เป็นคำถาม Unix / Linux เกี่ยวกับวิธีการสร้างตัวเลขจำนวนมาก)
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 ในฐานแปด): 25 ไบต์แรกเป็น 0, 25 คนต่อไปเป็น 1 ... แล้ว 25 9 ส่วนที่เหลือ (250 ถึง 255) ถึง "x" ซึ่งเราแล้ว ทิ้ง (พร้อมtr -d x) ตามที่เราต้องการการแจกแจงแบบสม่ำเสมอ (สมมติว่า/dev/urandomมีการแจกแจงแบบสม่ำเสมอ) และไม่ให้อคติกับตัวเลขบางตัว

ที่ผลิตหนึ่งหลักสำหรับ 97% /dev/urandomของไบต์ของ fold -w 1ทำให้เป็นหนึ่งหลักต่อบรรทัด paste -sถูกเรียกด้วยรายการตัวคั่นที่ประกอบด้วยอักขระ 99 ช่องว่างและอักขระขึ้นบรรทัดใหม่หนึ่งรายการดังนั้นเพื่อให้มี 100 หลักคั่นด้วยช่องว่างในแต่ละบรรทัด

head -c1Gจะได้รับ GiB แรก (2 30 ) จากนั้น โปรดทราบว่าบรรทัดสุดท้ายจะถูกปัดเศษและไม่ถูกคั่น คุณสามารถตัดเหลือ 2 30 -1 และเพิ่มบรรทัดใหม่ที่ขาดหายไปด้วยมือหรือตัดให้เหลือ 10 9ไบต์แทนซึ่งคือ 50 ล้านจาก 200 ไบต์ของบรรทัดเหล่านั้น ( head -n 50000000จะทำให้มันเป็นคำสั่งมาตรฐาน / แบบพกพา)

การกำหนดเวลาเหล่านี้ (ที่ได้รับจากzshระบบ quad-core) ให้การบ่งชี้เวลาที่ใช้ 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 และที่นี่เราผลิต 2 ไบต์สำหรับแต่ละ 0.97 ไบต์ของ / dev / urandom ที่อัตรา 32MiB / s) foldดูเหมือนว่าจะใช้เวลาจำนวน CPU ที่ไม่สมเหตุสมผล (15s) เพียงเพื่อแทรกอักขระขึ้นบรรทัดใหม่หลังจากทุกไบต์ แต่นั่นไม่ได้ส่งผลกระทบต่อเวลาโดยรวมเนื่องจากมันทำงานบน CPU ที่แตกต่างกันในกรณีของฉัน (การเพิ่ม-bตัวเลือก มีประสิทธิภาพdd cbs=1 conv=unblockดูเหมือนเป็นทางเลือกที่ดีกว่า)

คุณสามารถทำได้โดยใช้head -c1Gและโกนทิ้งไปสองสามวินาทีโดยตั้งค่าขีด จำกัด ขนาดไฟล์ ( limit filesize 1024mด้วยzshหรือulimit -f "$((1024*1024))"กับเชลล์อื่น ๆ ส่วนใหญ่ (รวมถึงzsh)) แทนใน subshell

นั่นอาจปรับปรุงได้ถ้าเราแยก 2 หลักสำหรับแต่ละไบต์ แต่เราต้องการวิธีที่แตกต่างกัน ด้านบนมีประสิทธิภาพมากเพราะtrค้นหาแต่ละไบต์ในอาร์เรย์ขนาด 256 ไบต์ ไม่สามารถทำได้ทีละ 2 ไบต์และการใช้สิ่งต่าง ๆ เช่นhexdump -e '1/1 "%02u"'ที่คำนวณการแสดงข้อความของไบต์โดยใช้อัลกอริทึมที่ซับซ้อนจะมีราคาแพงกว่าการสร้างตัวเลขสุ่ม ถึงกระนั้นถ้าในกรณีของฉันคุณมีคอร์ซีพียูที่มีเวลาว่างมันอาจจะยังสามารถโกนหนวดได้ในไม่กี่วินาที:

ด้วย:

< /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,000,000,000 ไบต์ซึ่งต่างจาก 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

เวลา CPU โดยรวมเพิ่มขึ้น แต่กระจายได้ดีกว่าระหว่างคอร์ซีพียู 4 คอร์ของฉันดังนั้นมันจึงใช้เวลาน้อยกว่านาฬิกาแขวน hexdumpคอขวดอยู่ในขณะนี้

ถ้าเราใช้ddแทน line-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=fullblockและstatus=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

ในระบบของฉัน spews 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เป็นคอขวด

ในขณะที่ฉันยังมีซีพียูว่างฉันก็สามารถรันได้ 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')" -

( <&3จำเป็นสำหรับเชลล์ที่ไม่ใช่zshstdin ของคำสั่ง close บน / dev / null เมื่อรันในโหมดแบ็กกราวน์)

ตอนนี้ลดลงเหลือ 6.2 วินาทีและซีพียูของฉันเกือบจะใช้อย่างเต็มที่แล้ว


3
ฉันเพิ่งลบคำตอบก่อนหน้าของฉันและโหวตให้กับคำตอบนี้ ฉันไม่ได้รับข้อกำหนดบางอย่าง คำตอบที่ดี btw
Marcelo

3
ทำไมคุณไม่สร้างตัวเลขหลายหลักในแต่ละพาส แม้ว่าคุณจะอ่านเป็นไบท์ทีละไบท์คุณก็ยังสามารถผลิตได้ 2 หลักในแต่ละครั้ง
phuclv

@ LưuVĩnhPhúcฉันได้ลบperlตัวแปรที่ช้าลงอย่างมากแล้ว ฉันไม่สามารถรับ 2 หลักต่อไบต์ด้วยวิธีการ tr | fold | paste
Stéphane Chazelas

ฉัน afk หรือฉันจะลองด้วยตัวเอง แต่คุณสามารถลองแปลง 42 ไบต์ต่อครั้งเป็น 100-102 หลักโดยใช้bc(จากนั้นปล่อย 0, 1, หรือ 2 หลักที่สำคัญที่สุด)
Eric Towers

gitlab.com/ole.tange/tangetools/tree/master/randสร้าง pseudorandom 1-4 GB ต่อวินาที (คุณภาพ AES)
Ole Tange

23

หากคุณมีshuf(coreutils GNU ล่าสุด) คุณสามารถทำได้

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

บน VM ของฉันตอนนี้ช้ากว่าคำตอบของStéphaneประมาณ 3: 4


shufในเครื่องคอมพิวเตอร์ของ บริษัท ของฉันไม่ได้-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

หากคุณไม่จำเป็นต้องมีการสุ่มที่มีคุณภาพสูงมากและใกล้ชิดกับเครื่องแบบกระจายเป็นสิ่งที่ดีพอที่คุณสามารถไปจริงๆอย่างรวดเร็วโดยเฉพาะใน CPU ที่ทันสมัยด้วยประสิทธิภาพ SIMD เวกเตอร์จำนวนเต็มเช่น 86 x กับ SSE2 หรือ AVX2

นี่เป็นเหมือนคำตอบของ @ NominalAnimalเนื่องจากเราทั้งคู่มีความคิดเดียวกัน แต่ vectorized ด้วยตนเองสำหรับ x86 (และด้วยตัวเลขสุ่มที่มีคุณภาพแย่กว่านั้น แต่ก็ยังดีพอสำหรับกรณีใช้งานจำนวนมาก) สิ่งนี้ทำงานได้เร็วกว่าโค้ดของ @ Nominal ประมาณ 15 หรือ 30 เท่าที่เอาต์พุต ASCII ~ 13GB / s บน 2.5GHz Intel Haswell CPU ที่มี AVX2 นั่นยังคงน้อยกว่าแบนด์วิดท์หน่วยความจำหลักสูงสุดทางทฤษฎี (ดูอัลแชนแนล DDR3-1600 ประมาณ 25.6GB / s) แต่ฉันเขียนเวลาไปที่ / dev / null ดังนั้นจริงๆแล้วมันแค่เขียนบัฟเฟอร์ที่ร้อนในแคช Skylake ควรเรียกใช้รหัสเดียวกันนี้เร็วกว่า Haswell (ดูที่ด้านล่างของคำตอบนี้)

สมมติว่าคุณมีปัญหาคอขวดใน I / O กับดิสก์หรือการวางท่อนี้ที่ใดที่หนึ่งการใช้งานที่รวดเร็วหมายความว่า CPU ของคุณไม่จำเป็นต้องตั้งนาฬิกาให้สูงกว่าที่ไม่ได้ใช้งาน ใช้พลังงานทั้งหมดน้อยกว่ามากในการสร้างผลลัพธ์ (อายุการใช้งานแบตเตอรี่ / ความร้อน / ภาวะโลกร้อน)

เร็วมากจนคุณไม่อยากเขียนมันลงดิสก์ เพียงสร้างใหม่ตามต้องการ (จากเมล็ดเดียวกันหากคุณต้องการข้อมูลเดิมอีกครั้ง) แม้ว่าคุณต้องการป้อนเข้าสู่กระบวนการแบบมัลติเธรดที่สามารถใช้ CPU ทั้งหมดการเรียกใช้งานนี้เพื่อไพพ์ข้อมูลจะทำให้ร้อนในแคช L3 (และแคช L2 บนแกนที่เขียน) และใช้มาก เวลา CPU เล็กน้อย (แต่โปรดทราบว่า piping เพิ่มค่าใช้จ่ายจำนวนมากเมื่อเทียบกับการเขียน/dev/nullบน Skylake i7-6700k การไพพ์ไปยังwc -cหรือโปรแกรมอื่นที่เพิ่งอ่าน + ทิ้งอินพุตมันประมาณ 8 เท่าช้ากว่าการเขียน/dev/nullและใช้ 70% ของ a เท่านั้น CPU แต่ก็ยังคง 4.0GB / s ในซีพียู 3.9GHz

การสร้างใหม่นั้นเร็วกว่าการอ่านอีกครั้งแม้จะมาจาก SSD ที่เชื่อมต่อ PCIe อย่างรวดเร็ว แต่ IDK ถ้ามันมีประสิทธิภาพการใช้พลังงานมากกว่า (ตัวคูณทวีคูณแบบเวกเตอร์จำนวนมากยังคงยุ่งอยู่และอาจหิวมากพร้อมกับ AVX2 อื่น ๆ 256b ALU เวกเตอร์) OTOH ฉันไม่รู้ว่าเวลาในการอ่าน CPU ของดิสก์จะถูกนำออกไปจากสิ่งที่เพิ่มจำนวนคอร์ทั้งหมดที่ประมวลผลอินพุตนี้ ฉันเดาว่าการสลับบริบทเพื่อสร้างชิ้นส่วนอีกครั้งใน 128k อาจแข่งขันกับการเรียกใช้รหัสระบบไฟล์ / pagecache และจัดสรรหน้าเพื่ออ่านข้อมูลจากดิสก์ แน่นอนถ้ามันร้อนใน pagecache มันก็แค่ memcpy OTOH เราเขียนเร็วพอ ๆ กับ memcpy! (ซึ่งต้องแยกแบนด์วิธหน่วยความจำหลักระหว่างการอ่านและการเขียน) (โปรดทราบว่าการเขียนไปยังหน่วยความจำที่ 'rep movsb(เพิ่มประสิทธิภาพ memcpy และ memset ในไมโครโค้ดซึ่งหลีกเลี่ยง RFO เนื่องจากการดำเนินการของ Andy Glew ใน P6 (Pentium Pro ))


จนถึงตอนนี้เป็นเพียงการพิสูจน์แนวคิดและการจัดการขึ้นบรรทัดใหม่มีความถูกต้องโดยประมาณเท่านั้น มันผิดที่ส่วนท้ายของบัฟเฟอร์ power-of-2 ด้วยเวลาในการพัฒนาที่มากขึ้น ฉันมั่นใจว่าฉันสามารถหาวิธีที่มีประสิทธิภาพมากขึ้นในการแทรกบรรทัดใหม่ที่ถูกต้องอย่างแน่นอนด้วยค่าใช้จ่ายอย่างน้อยที่สุดเท่าที่จะทำได้ (เทียบกับการแสดงผลเฉพาะที่ว่าง) ฉันคิดว่านี่เป็นสิ่งที่ชอบ 10 ถึง 20% ฉันแค่สนใจที่จะรู้ว่าเราสามารถวิ่งได้เร็วแค่ไหนไม่ได้มีเวอร์ชั่นที่ขัดมันจริง ๆ ดังนั้นฉันจะปล่อยให้ส่วนนั้นเป็นแบบฝึกหัดสำหรับผู้อ่านพร้อมความคิดเห็นที่อธิบายแนวคิดบางอย่าง


บน Haswell i5 ที่เทอร์โบสูงสุด 2.5GHz พร้อม DDR3-1600MHz RAMหมดเวลาผลิต 100GiB แต่ลดขนาดลง (หมดเวลาใน cygwin64 บน Win10 ด้วย gcc5.4 -O3 -march=nativeถูกละเว้น-funroll-loopsเนื่องจากฉันมีเวลามากพอที่จะได้รับเวลาที่เหมาะสมบนแล็ปท็อปที่ยืมมานี้ควรเพิ่งบูต Linux บน USB)

เขียนถึง / dev / null เว้นแต่จะระบุไว้เป็นอย่างอื่น

  • James Hollis's: (ไม่ผ่านการทดสอบ)
  • รุ่น fwrite ที่กำหนด: ~ 2.21s
  • สิ่งนี้ (SSE2): ~ 0.142 วินาที (ไม่ จำกัด ขนาด = จริง = 14.232 วินาที, ผู้ใช้ = 13.999s, sys = 0.187s)
  • สิ่งนี้ (AVX-128): ~ 0.140 วินาที
  • สิ่งนี้ (AVX2): ~ 0.073 วินาที (ไม่ระบุมาตราส่วน : จริง = 0m7.291s, ผู้ใช้ = 0m7.125s, sys = 0m0.155s)
  • (AVX2) cygwin ไปป์wc -cไลน์ด้วยขนาดบัฟเฟอร์ 128kiB: 0.32 วินาทีพร้อม CPU ที่ 2.38GHz (เทอร์โบ dual-core สูงสุด) (เวลาที่ไม่ถูกปรับสัดส่วน: จริง = 32.466s ผู้ใช้ = 11.468s sys = 41.092s รวมทั้งสิ่งนี้และwc) แต่มีเพียงครึ่งหนึ่งที่คัดลอกข้อมูลจริง ๆ เพราะโปรแกรมโง่ ๆ ของฉันสมมติว่าการเขียนทำบัฟเฟอร์เต็มแม้ว่าไม่ใช่กรณีและ cygwin write () ทำได้ 64k ต่อการโทรหนึ่งครั้งเท่านั้น

ดังนั้นด้วย SSE2 นี่จะเร็วกว่าโค้ดสเกลาร์ของ @Nominal Animal ประมาณ 15 เท่า ด้วย AVX2 จะเร็วกว่าประมาณ 30 เท่า ฉันไม่ได้ลองใช้รหัสของ Nominal รุ่นที่เพิ่งใช้write()แทนfwrite()แต่น่าจะเป็นเพราะบัฟเฟอร์ขนาดใหญ่ stdio ส่วนใหญ่ยังคงอยู่นอกเส้นทาง หากเป็นการคัดลอกข้อมูลนั่นจะทำให้เกิดการชะลอตัวเป็นจำนวนมาก


เวลาในการผลิตข้อมูล 1GB บน Core2Duo E6600 (Merom 2.4GHz, 32kiB ส่วนตัว L1, 4MiB แคช L2 ที่ใช้ร่วมกัน 4MiB), DDR2-533MHzใน 64-bit Linux 4.2 (Ubuntu 15.10) ยังคงใช้ขนาดบัฟเฟอร์ 128kiB สำหรับการเขียน () ไม่ได้สำรวจมิตินั้น

เขียนถึง / dev / null เว้นแต่จะระบุไว้เป็นอย่างอื่น

  • (SSE2) สิ่งนี้พร้อมกับการจัดการบรรทัดใหม่และ 4 เวกเตอร์ของตัวเลขจากแต่ละเวกเตอร์ของการสุ่มไบต์: 0.183s (หมดเวลาทำ 100GiB ใน 18.3s แต่ผลลัพธ์ที่คล้ายกันสำหรับการวิ่ง 1GiB) 1.85 คำแนะนำต่อรอบ
  • (SSE2) สิ่งนี้ส่งไปที่wc -c: 0.593 วินาที (ไม่ถูกปรับสัดส่วน : จริง = 59.266s ผู้ใช้ = 20.148s sys = 1m6.548s รวมถึงเวลา CPU ของ wc) จำนวนการเขียน () ระบบที่เรียกเช่นเดียวกับ cygwin แต่จริง ๆ แล้วการไพพ์ข้อมูลทั้งหมดเนื่องจาก Linux จัดการ 128k ทั้งหมดของการเขียน () ไปยังไพพ์
  • fwrite()เวอร์ชันของ NominalAnimal (gcc5.2 -O3 -march=native) ทำงานด้วย./decdig 100 $((1024*1024*1024/200)) > /dev/null: 3.19s +/- 0.1% พร้อมคำแนะนำ 1.40 ต่อรอบ -Funroll-loops อาจจะแตกต่างกันเล็กน้อย clang-3.8 -O3 -march=native: 3.42 วินาที +/- 0.1%
  • Nomine- fwritepiping wc -c: real = 3.980s user = 3.176s sys = 2.080s
  • เวอร์ชั่น line-at-a-time ของ James Hollis ( clang++-3.8 -O3 -march=native): 22.885s +/- 0.07% โดยมี 0.84 คำสั่งต่อรอบ (g ++ 5.2 ช้ากว่าเล็กน้อย: 22.98 วินาที) การเขียนเพียงครั้งละหนึ่งบรรทัดอาจเจ็บอย่างมาก
  • 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
  • โปรแกรม perl ของLưuVĩnhPhúc (perl v5.20.2 จาก Ubuntu15.10)
    LANG=en_CA.UTF-8:: จริง = 4m32.634s ผู้ใช้ = 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) รุ่นเก่านี้กับการจัดการการขึ้นบรรทัดใหม่ไม่ แต่เพียงการหนึ่งหลักต่อองค์ประกอบ uint16_t ใช้v%10, 0.222 วินาที +/- 0.4%, 2.12 คำแนะนำต่อวงจร (คอมไพล์ด้วย gcc5.2,. -march=native -O3 -funroll-loopsเกิดลูปการคลี่คลายเพื่อช่วยให้โค้ดนี้กับฮาร์ดแวร์นี้อย่าใช้อย่างสุ่มสี่สุ่มห้าโดยเฉพาะอย่างยิ่งสำหรับโปรแกรมขนาดใหญ่)
  • (SSE2) เวอร์ชันเก่านี้เขียนลงไฟล์ (บน RAID10f2 ของฮาร์ดไดรฟ์แม่เหล็กเร็ว 3 ตัวซึ่งไม่เหมาะสำหรับการเขียน): ~ 4 วินาที สามารถทำงานได้เร็วขึ้นโดยปรับแต่งการตั้งค่าบัฟเฟอร์เคอร์เนล I / O เพื่อให้ข้อมูลสกปรกมากขึ้นก่อนการเขียนบล็อก () เวลา "ระบบ" ยังคงอยู่ ~ 1.0 วินาทีสูงกว่าเวลา "ผู้ใช้" มาก ในระบบเก่านี้ที่มี DDR2-533 RAM ช้ามันจะใช้เวลานานขึ้น ~ 4x สำหรับเคอร์เนลในการบันทึกข้อมูลลงใน pagecache และเรียกใช้ฟังก์ชั่น XFS มากกว่าที่จะให้ลูปของฉันทำการเขียนใหม่ในบัฟเฟอร์ที่ยังร้อนอยู่ ขุมทรัพย์

มันเป็นอย่างไร

PRNG ที่รวดเร็วนั้นสำคัญมาก xorshift128 +สามารถแปลงเป็นเวกเตอร์ได้ดังนั้นคุณจึงมีเครื่องกำเนิด 64 บิตสองหรือสี่ตัวขนานในองค์ประกอบของเวกเตอร์ SIMD แต่ละขั้นตอนสร้างเวกเตอร์เต็มจำนวนแบบสุ่มไบต์ ( การใช้งาน 256b AVX2 ที่นี่พร้อมกับ Intel ที่แท้จริง ) ฉันเลือกมันมากกว่าทางเลือกที่กำหนดของ xorshift * เพราะ 64 บิตเวกเตอร์จำนวนเต็มคูณเป็นไปได้เฉพาะใน SSE2 / AVX2 กับเทคนิคการขยายความแม่นยำ


ด้วยเวกเตอร์สุ่มจำนวนไบต์เราสามารถสับองค์ประกอบ 16 บิตแต่ละรายการเป็นเลขทศนิยมหลายหลัก เราผลิตหลายเวกเตอร์ขององค์ประกอบ 16 บิตที่มีแต่ละคน ASCII พื้นที่หลัก เราเก็บสิ่งนั้นไว้ในบัฟเฟอร์ผลลัพธ์โดยตรง

เวอร์ชันเดิมของฉันเพิ่งใช้x / 6554เพื่อรับหนึ่งตัวเลขแบบสุ่มจากทุกองค์ประกอบ uint16_t ของเวกเตอร์ มันมักจะอยู่ระหว่าง 0 และ 9 รวม มันเอนเอียงไป9เพราะ(2^16 -1 ) / 6554เพียง 9.99923 (6554 = ceil ((2 ^ 16-1) / 10) ซึ่งรับรองว่าผลหารเสมอ <10)

x/6554สามารถคำนวณด้วยการคูณด้วยค่าคงที่ "เวทย์มนตร์" ( การกำหนดจุดตายตัว ) และการเลื่อนขวาของผลลัพธ์ครึ่งสูง นี่เป็นกรณีที่ดีที่สุดสำหรับการหารโดยค่าคงที่; ตัวหารบางตัวใช้เวลาในการดำเนินการมากขึ้น x % 10มีอคติที่คล้ายกันและไม่ถูกคำนวณ (gm's asm output เทียบเท่ากับx - 10*(x/10)คือการเพิ่มทวีคูณและการลบที่ด้านบนของส่วนโดยใช้การคูณแบบแยกส่วนแบบแยกส่วน) นอกจากนี้บิตต่ำสุดของ xorshift128 + นั้นไม่ได้คุณภาพสูงดังนั้นการหารเพื่อนำเอนโทรปีจากบิตสูงนั้นดีกว่า ( สำหรับคุณภาพและความเร็ว) กว่าโมดูโลที่ใช้เอนโทรปีจากบิตต่ำ

อย่างไรก็ตามเราสามารถใช้เอนโทรปีได้มากขึ้นในแต่ละ uint16_t โดยดูที่ตัวเลขทศนิยมต่ำเช่นdigit()ฟังก์ชันของ @ Nominal เพื่อประสิทธิภาพสูงสุดฉันตัดสินใจที่จะใช้ทศนิยม 3 หลักต่ำและx/6554เพื่อบันทึกหนึ่ง PMULLW และ PSUBW (และอาจเป็น MOVDQA บางส่วน) เทียบกับตัวเลือกคุณภาพที่สูงขึ้นของการใช้ตัวเลขทศนิยมต่ำ 4 หลัก x / 6554 ได้รับผลกระทบเล็กน้อยจากตัวเลขทศนิยม 3 หลักที่ต่ำดังนั้นจึงมีความสัมพันธ์ระหว่างตัวเลขจากองค์ประกอบเดียวกัน (8 หรือ 16 หลักในการแยกเอาต์พุต ASCII ขึ้นอยู่กับความกว้างของเวกเตอร์)

ฉันคิดว่า gcc จะหารด้วย 100 และ 1,000 แทนที่จะเป็นโซ่ที่ยาวกว่าซึ่งหารด้วย 10 อย่างต่อเนื่องดังนั้นมันอาจไม่สั้นลงอย่างมีนัยสำคัญถึงความยาวของห่วงโซ่อ้างอิงที่ไม่ใช่แบบพกพาซึ่งผลิต 4 ผลลัพธ์จากแต่ละ PRNG port0 (vector ทวีคูณและกะ) เป็นคอขวดเนื่องจากการคูณแบบแยกส่วนโมดุลและการเลื่อนใน xorshift + ดังนั้นมันจึงมีประโยชน์อย่างมากในการบันทึกเวกเตอร์ทวีคูณ

xorshift + นั้นเร็วมากแม้การใช้สุ่มเพียง 3.3 บิตจากทุก ๆ 16 (เช่นประสิทธิภาพ 20%) นั้นไม่ช้ากว่าการสับเป็นทศนิยมหลายหลัก เราประมาณการกระจายตัวแบบสม่ำเสมอเท่านั้นเพราะคำตอบนี้เน้นที่ความเร็วตราบใดที่คุณภาพไม่เลวร้ายนัก

พฤติกรรมตามเงื่อนไขใด ๆ ที่เก็บหมายเลของค์ประกอบของตัวแปรจะทำงานได้มากขึ้น (แต่อาจจะทำได้ค่อนข้างมีประสิทธิภาพโดยใช้เทคนิคการบรรจุหีบห่อ SIMDอย่างไรก็ตามประสิทธิภาพที่ลดลงสำหรับองค์ประกอบขนาดเล็กตารางการค้นหาหน้ากากแบบสุ่มขนาดใหญ่ไม่สามารถใช้งานได้และไม่มีการสับเปลี่ยนเลน AVX2 ที่มีขนาดเล็กกว่า 32- องค์ประกอบบิต. รุ่น 128b PSHUFB ยังอาจจะสามารถที่จะสร้างหน้ากากในการบินด้วย BMI2 PEXT / PDEP เช่นคุณสามารถทำได้สำหรับ AVX2 มีองค์ประกอบที่มีขนาดใหญ่แต่ก็ยุ่งยากเพราะ 64 บิตจำนวนเต็มเท่านั้นถือ 8 ไบต์. การเชื่อมโยง godbolt ในคำตอบนั้นมีรหัสบางอย่างที่อาจใช้กับการนับองค์ประกอบที่สูงกว่า)


หากเวลาแฝงของ RNG เป็นปัญหาคอขวดเราสามารถทำงานได้เร็วขึ้นด้วยการเรียกใช้เครื่องกำเนิดไฟฟ้าสองเวกเตอร์ในแบบขนานสลับกันเป็นแบบที่เราใช้ คอมไพเลอร์ยังคงสามารถเก็บทุกอย่างได้อย่างง่ายดายในการลงทะเบียนในการวนซ้ำที่ไม่ได้ควบคุมและนั่นทำให้ทั้งสองเครือข่ายสามารถพึ่งพากันในแบบคู่ขนาน

ในเวอร์ชันปัจจุบันการตัดการส่งออกของ PRNG เราจริง ๆ แล้วคอขวดบนพอร์ต 0 ทรูพอร์ตไม่ใช่ PRNG latency ดังนั้นจึงไม่จำเป็นต้องทำเช่นนั้น


รหัส: รุ่น AVX2

เวอร์ชันเต็มที่มีความคิดเห็นเพิ่มเติมเกี่ยวกับคอมไพเลอร์สำรวจ Godbolt

ไม่เป็นระเบียบมากขอโทษฉันต้องนอนและต้องการโพสต์สิ่งนี้

รับรุ่น SSE2, s/_mm256/_mm, s/256/128/, s/v16u/v8u/และการเปลี่ยนแปลงvector_size(32)ถึง 16 นอกจากนี้ยังมีการเปลี่ยนแปลงเพิ่มขึ้นบรรทัดใหม่จาก 4 * 16-4 * 8 (อย่างที่ฉันบอกว่ารหัสยุ่งและไม่ดีสำหรับการคอมไพล์สองเวอร์ชัน แต่เดิมไม่ได้วางแผนที่จะสร้างเวอร์ชั่น 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 (หรือหวังว่าคอมไพเลอร์อื่น ๆ ที่เข้าใจภาษา GNU C ของ C99 และ Intrinsics ของ Intel) ส่วนขยายเวกเตอร์ของ GNU C นั้นสะดวกในการรวบรวมคอมไพเลอร์เพื่อสร้างหมายเลขเวทย์มนตร์สำหรับการหาร / โมดูโลโดยใช้ตัวคูณแบบแยกส่วนแบบแยกส่วนและบางครั้ง__attribute__มีประโยชน์

สิ่งนี้สามารถเขียนได้แบบพกพา แต่จะใช้รหัสมากกว่านี้


หมายเหตุประสิทธิภาพ:

ร้านค้าที่ทับซ้อนกันเพื่อแทรกบรรทัดใหม่มีค่าใช้จ่ายจำนวนมากในการตัดสินใจว่าจะวางที่ใด (การคาดคะเนสาขาและคอขวดส่วนหน้าบน Core2) แต่ตัวร้านค้าเองไม่มีผลกระทบต่อประสิทธิภาพการทำงาน แสดงความคิดเห็นเพียงคำสั่งที่เก็บไว้ใน asm ของคอมไพเลอร์ (ปล่อยให้สาขาทั้งหมดเหมือนเดิม) ออกจากประสิทธิภาพการทำงานใน Core2 ไม่เปลี่ยนแปลงอย่างสมบูรณ์กับการทำงานซ้ำให้เวลาเดียวกันถึง +/- น้อยกว่า 1% ดังนั้นฉันสรุปได้ว่าบัฟเฟอร์การจัดเก็บ / แคชจัดการได้ดี

ถึงกระนั้นการใช้หน้าต่างหมุนได้บางส่วนascii_digitspaceกับองค์ประกอบหนึ่งที่มีการขึ้นบรรทัดใหม่อาจจะเร็วกว่านี้ถ้าเราเปิดใช้งานมากพอที่เคาน์เตอร์ / สาขาใด ๆ หายไป


การเขียนไปยัง / dev / null นั้นเป็นแบบไม่ใช้ดังนั้นบัฟเฟอร์อาจยังคงร้อนอยู่ในแคช L2 (256kiB ต่อคอร์ใน Haswell) คาดว่าความเร็วเวกเตอร์ที่สมบูรณ์แบบจาก 128b ไปเป็น 256b นั้นไม่มีคำแนะนำเพิ่มเติมและทุกอย่าง (รวมถึงร้านค้า) ก็เกิดขึ้นด้วยความกว้างสองเท่า แม้ว่าสาขาที่แทรกขึ้นบรรทัดใหม่จะได้รับสองครั้งบ่อยครั้ง ฉันโชคไม่ดีที่ฉันไม่ได้มีเวลาในการตั้งค่า Haswell cygwin กับส่วน#ifdefนั้น

2.5GHz * 32B / 13.7GB / s = 5.84 รอบต่อ AVX2- ร้านค้าบน Haswell ค่อนข้างดี แต่อาจเร็วกว่า อาจจะมีค่าใช้จ่ายในการเรียกระบบ cygwin มากกว่าที่ฉันคิด ฉันไม่ได้ลองแสดงความคิดเห็นเหล่านั้นในเอาต์พุต asm ของคอมไพเลอร์ (ซึ่งจะทำให้แน่ใจได้ว่าไม่มีสิ่งใดที่ดีที่สุดออกมา)

L1 cache สามารถเก็บ 32B หนึ่งที่เก็บต่อนาฬิกาและ L2 ไม่ได้มีแบนด์วิดท์ที่ต่ำกว่ามาก (เวลาแฝงที่สูงขึ้น)

เมื่อฉันดู IACA ไม่กี่รุ่นที่ผ่านมา (ไม่มีการแยกสาขาสำหรับการขึ้นบรรทัดใหม่ แต่รับเพียงหนึ่งเวกเตอร์ ASCII ต่อเวกเตอร์ RNG) มันเป็นการคาดการณ์บางอย่างเช่นร้านขายเวกเตอร์ 32B ต่อ 4 หรือ 5 นาฬิกา

ฉันหวังว่าจะได้รับข้อมูลเพิ่มขึ้นอย่างรวดเร็วจากการดึงข้อมูลเพิ่มเติมจากผลลัพธ์ RNG แต่ละรายการโดยพิจารณาจากตัวฉันเองพิจารณาคำแนะนำของ Agner Fogและแหล่งข้อมูลการเพิ่มประสิทธิภาพอื่น ๆ ซึ่งฉันได้เพิ่มลิงก์ไว้ในแท็ก SO x86 )

ดูเหมือนว่ามันจะเร็วขึ้นอย่างมากใน Skylakeที่จำนวนเต็มเวกเตอร์คูณและการเลื่อนสามารถทำงานบนพอร์ตได้มากเป็นสองเท่า (p0 / p1) เมื่อเทียบกับ Haswell (p0 เท่านั้น) xorshift และตัวแยกหลักใช้ทั้งกะและทวีคูณ ( อัปเดต: Skylake รันที่ 3.02 IPC, ให้เรา 3.77 รอบต่อ 32- ไบต์ AVX2 store , เวลา 0.030 วินาทีต่อการทำซ้ำ 1GB, เขียนลง/dev/nullบน Linux 4.15 บน i7-6700k ที่ 3.9GHz


มันไม่จำเป็นต้องโหมด 64 บิตจะทำงานได้ดี เวอร์ชั่น SSE2 นั้นเร็วมากเมื่อทำการคอมไพล์ด้วย-m32เนื่องจากมันไม่จำเป็นต้องมีการลงทะเบียนเวกเตอร์จำนวนมากและคณิตศาสตร์ 64 บิตทั้งหมดนั้นทำในเวกเตอร์ไม่ใช่การลงทะเบียนที่ใช้งานทั่วไป

จริง ๆ แล้วมันเร็วขึ้นเล็กน้อยในโหมด 32 บิตบน Core2 เนื่องจากการเปรียบเทียบ / มาโครฟิวชั่นสาขาใช้งานได้ในโหมด 32 บิตเท่านั้นดังนั้นจึงมี uops น้อยลงสำหรับคอร์ที่ล้าสมัย (18.3 วินาที (1.85 คำแนะนำต่อนาฬิกา) vs . 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-reg MOVDQA ต้องใช้พอร์ต ALU จึงแข่งขันกับงานจริงที่กำลังทำอยู่ แต่โหลด MOVDQA จะแข่งขันเฉพาะแบนด์วิดท์ถอดรหัสด้านหน้า (การมีที่อยู่ขนาด 4 ไบต์ภายในคำแนะนำจำนวนมากจะยกเลิกการได้รับประโยชน์มากมายจากการบันทึกคำนำหน้า REX

ฉันจะไม่แปลกใจถ้าการบันทึก ALU MOVDQA uops เป็นที่ซึ่งกำไรที่แท้จริงมาจากส่วนหน้าควรจะรักษาด้วย 2.0 IPC เฉลี่ยค่อนข้างดี

ความแตกต่างเหล่านี้ทั้งหมดหายไปใน Haswell ซึ่งสิ่งทั้งหมดควรรันจากแคช decoded-uop หากไม่ใช่บัฟเฟอร์ loopback มาโครฟิวชั่นสาขา ALU + ทำงานได้ทั้งสองโหมดตั้งแต่ Nehalem


6
ฉันชอบวิธีที่คุณใช้"โหมดสัตว์ร้าย"ในหัวเรื่อง! :) ที่สำคัญกว่านั้นเป็นตัวอย่างที่ยอดเยี่ยมของประเภทของกำไรที่มีอยู่หากคุณต้องการหรือต้องการที่จะบีบประสิทธิภาพสูงสุดโดยใช้ความรู้ระดับต่ำของฮาร์ดแวร์ในมือ นอกจากนี้เราใช้เพียงกระทู้เดียวที่นี่; เดสก์ท็อปและเซิร์ฟเวอร์ในปัจจุบันส่วนใหญ่โปรเซสเซอร์ Intel / AMD (และแม้แต่ ARM ในแท็บเล็ตน้ำหนักเบาและ SBCs) มีหลายคอร์ดังนั้นจึงยังมีการเพิ่มความเร็วแบบเรียลไทม์ตามเวลาของโลก และในที่สุดคำถามที่ว่า "วิธีที่เร็วที่สุด"นั้นเป็นไปไม่ได้นั้นเกิดจากความพยายามอย่างเต็มที่ที่เกี่ยวข้อง
สัตว์ที่กำหนด

1
@NominalAnimal: ใช่แม้แต่ ARM quad quad หรือ octo core ที่ช้าก็สามารถทำให้แบนด์วิดท์ของหน่วยความจำหลักได้อย่างง่ายดายด้วย NEON (แม้ว่าพวกเขาจะเชื่อมต่อกับ dual channel DDR3 ที่รวดเร็ว) หากมี SIMD 64 บิตเพิ่มและเปลี่ยน . ฉันถือว่า NEON มีขนาดตัวคูณขนาด 16 บิตสำหรับงานเสียง คำสั่งการตั้งเวลาจะทำงานมากขึ้นสำหรับ ARM ในการสั่งซื้อเพราะทวนของห่วงโซ่การพึ่งพาห่วงดำเนินการ (คน xorshift128 +) แต่ละฟีดไม่กี่โซ่พึ่งพาอิสระจากการตัดขึ้นและได้รับการเก็บไว้ในหน่วยความจำ ...
ปีเตอร์ Cordes

... การดำเนินการที่ไม่เป็นไปตามสั่งกินสำหรับอาหารเช้าเพราะทุกอย่างสั้นพอที่จะทำซ้ำหลาย ๆ อย่างใน ROB (192 uops บน HSW IIRC) (เช่น "หน้าต่าง" ของคำแนะนำที่การดำเนินการตามคำสั่งเห็นรวมถึงการทำซ้ำหลายรายการ) ดังนั้นซีพียูจึงสามารถทำการปิดร้านค้าขั้นสุดท้ายได้สำหรับการทำซ้ำ 2 หรือ 3 ครั้งที่ผ่านมาในขณะที่เริ่มต้นการทำซ้ำในปัจจุบัน วิธีนี้ซ่อนความล่าช้าของเชนอิสระดังนั้นปริมาณงานจึงมีความสำคัญเท่านั้น บนหลักในการสั่งซื้อนี้จะต้อง pipelining ซอฟแวร์ ...
ปีเตอร์ Cordes

... คอมไพเลอร์ ARM ที่ดีควรทำบางอย่างให้คุณถ้าคุณเขียนด้วย intrinsics (หรือไวยากรณ์ดั้งเดิมของเวกเตอร์ GNU C สำหรับทุกสิ่งเหมือนที่ฉันควรทำตั้งแต่แรก) ฉันไม่มีประสบการณ์ใด ๆ ในการทำสิ่งนั้นจริงดังนั้นคุณอาจต้องนวดวนรอบของคุณและอาจทำคู่มือบางอย่างที่ไม่ได้เผยแพร่ / การวางท่อในแหล่งที่มาเพื่อรับ asm ที่ดี (มี ARM ARM ที่ล้าสมัยบางอย่างที่พบในโทรศัพท์ระดับสูง แต่มีขนาดใหญ่กว่าหน้าต่างทั่วไปเช่น Haswell OTOH มีอัตราความเร็วสูงสุดต่ำกว่าเช่นกันดังนั้นจึงมีน้อย เพื่อรับประโยชน์จากการค้นหา ILP เพิ่มเติม)
Peter Cordes

1
@NominalAnimal: เห็นด้วยกับความโง่เขลาของคำถาม "เร็วที่สุด" โดยไม่มีข้อ จำกัด ด้านคุณภาพของการสุ่มคือความโง่ ... ด้วย BTRFS ข้อมูลเดียวกันบนดิสก์สามารถเป็นส่วนหนึ่งของไฟล์ได้หลายครั้ง (ดูEXTENT_SAME ใน 4.2 ) ดังนั้นคุณสามารถสร้างสุ่ม 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/dev/randomสร้างกระแสเครื่องแบบของตัวเลขฐานสิบหกจาก
  • trกำจัดตัวอักษรเพียงรักษา0-9ตัวเลข
  • fold ทำให้แน่ใจได้ว่ามี 100 หลักต่อบรรทัด
  • awk แทรกช่องว่างภายในเส้น
  • head ตัดอินพุตให้เหลือ 1 กิกะไบต์

2
นี่เป็นวิธีทางเลือกที่ดีในการผลิตมากกว่าหนึ่งหลักโดยไบต์ของ / dev / สุ่มในขณะที่ยังคงมีการกระจายสม่ำเสมอที่สร้าง 320 หลักสำหรับทุก ๆ 256 ไบต์ของ / dev / urandom โดยเฉลี่ย (น้อยกว่าเมื่อคุณแปลงไบต์ <200 โมดูโล 100 ถึงทศนิยมซึ่งให้ 400 หลักสำหรับทุก ๆ 256 ไบต์)
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(Ubuntu 16.04)
Digital Trauma

2
จำนวนที่เหมาะสมสำหรับครึ่ง gb คือ: 1024 * 1024 * 1024/2 =536870912
Olivier Dulac

1
@OlivierDulac ขึ้นอยู่กับ "กิกะไบต์" ที่คุณกำลังพูดถึง บางคนใช้ 1 Gb เพื่อหมายถึง 10 ^ 9 แทนที่จะเป็น 2 ^ 30 แม้ว่าจะไม่ถูกต้องทางเทคนิคก็ตาม พลัสผมชอบตัวเลขรอบดี :)
gardenhead

6
@gardenhead, ผู้คนจำนวนมากขึ้นตอนนี้มีแนวโน้มที่จะย้ายไป Gigabyte == 1e9 และ Gibibyte == 2 ^ 30 ตามที่เป็นคำจำกัดความมาตรฐาน IEC ดูวิกิพีเดีย โปรดทราบว่า Gb ตัวเองค่อนข้างจะ Giga- บิต
Stéphane Chazelas

6

นี่คล้ายกับวิธีของStéphane Chazelas แต่ฉันอ่าน 64 บิตพร้อมกันเพื่อปรับปรุงประสิทธิภาพ การกระจายยังคงเหมือนเดิม แต่ตอนนี้คุณได้รับ 19 หลักสำหรับแต่ละ 8 ไบต์แทนเพียง 8 ในกรณีที่ดีที่สุดเหมือนก่อน

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 บิต 9 หลักจะถูกอ่านในแต่ละครั้งแทนที่จะเป็น 19


สิ่งนี้อาจทำให้เกิดข้อยกเว้นหากระบบของคุณไม่รองรับจำนวนเต็ม 64- บิตหรือperlไม่ได้ถูกคอมไพล์ด้วยการรองรับรูปสี่เหลี่ยม
cuonglm

@cuonglm ใช่ที่ผมกล่าวว่าถ้าไม่ได้เป็น Perl 64 บิตบนระบบที่แล้วโปรแกรมจะต้องเปลี่ยนไปnext if $n >= 1000000000; $s = sprintf("%09u", $n);จะได้รับเพียง 9 หลัก
phuclv

คุณทำไม่ได้โปรแกรมจะขัดข้อง$n = unpack("Q")ถ้าไม่รองรับ quad
cuonglm

1
การเปลี่ยนแปลง @cuonglm ไปBEGIN{$/=\4; $,=" "} $n = unpack("L");ยัง
phuclv

1
ขออภัยนี้ได้รับ 19 หลักจาก 8 ไบต์อินพุตเพียงประมาณ 54.2% ของเวลาและไม่มีส่วนที่เหลือเฉลี่ย 1.29 หลักต่อไบต์อินพุต ถ้าคุณชอบ Stephane มากกว่าคุณใช้<16e18และหารด้วย 16 คุณจะได้ 18 หลัก 86.7% สำหรับ 1.95 dpB ด้วย 32 บิต<4e9 /4รับ 9 หลัก 93.1% สำหรับ 2.10 dpB แต่ 5 ไบต์ (เป็น<1e12เลขฐานสิบหก (H10)) ให้ 12 หลัก 90.9% สำหรับ 2.18 dpB หรือแบ่ง hex ในครึ่งและทำแต่ละครึ่ง<1e6 ให้ 6 หลัก 95.4% สำหรับ 2.29 dpB; นี่ใกล้ถึงขีด จำกัด ของ log_10 (256) = 2.41
dave_thompson_085

3

ฉันเห็นด้วยกับ Nominal Animal ในการใช้ภาษาโปรแกรมคอมไพล์หากคุณต้องการความเร็ว อย่างไรก็ตามคุณไม่จำเป็นต้องเขียนรหัส RNG ของคุณเองใน C. 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;
}

รหัสข้างต้นนั้นง่ายพอสมควรและใช้เวลาประมาณหนึ่งนาทีเมื่อฉันไพพ์เอาต์พุตไปยังไฟล์ เราสามารถไปได้เร็วขึ้นมากโดยการสร้างสตริงที่มีขนาดใหญ่พอสำหรับ 100 หลักและแฮ็คตัวเลขเข้าไป สิ่งนี้ทำให้เราสามารถโทรออกได้ทุกบรรทัดมากกว่าทุกหลัก

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

รหัสนี้ใช้เวลาเครื่องของฉันประมาณหกวินาที จำไว้ว่ามันเป็นเอาต์พุตมาตรฐานดังนั้นไปป์ไปที่ไฟล์

ฉันมีข้อจำกัดความรับผิดชอบสองสามข้อ ก่อนอื่นฉันเขียนมันลงบนพีซี Windows ฉันคิดว่าไลบรารี่ทั้งหมดนั้นมีอยู่บนลีนุกซ์ แต่ถ้าฉันผิดก็ขอให้ชี้มันออกมา

นอกจากนี้จริง ๆ แล้วมันส่งออกตัวเลขครึ่งพันล้านที่คั่นด้วยช่องว่างซึ่งเป็นเทคนิคกิกะไบต์ แต่อาจไม่ตรงตามที่คุณต้องการ มันส่งออก 5 ล้านเส้น 100 หลักต่อบรรทัด หากความแตกต่างสำคัญคุณสามารถเพิ่มจำนวนบรรทัดได้ ในกล่อง Windows ของฉันดูเหมือนว่าไฟล์จะมีขนาดใหญ่กว่า 10 ^ 9 ไบต์เล็กน้อยซึ่งฉันคิดว่าเป็นสิ่งที่ต้องทำกับอักขระขึ้นบรรทัดใหม่


2
เฮ้คำวิจารณ์ไม่ยุติธรรมจริงๆ! :) โปรแกรมส่วนใหญ่ของฉันคือการแยกพารามิเตอร์บรรทัดคำสั่ง ถ้าฉันยังละเว้นความเห็น, การตรวจสอบข้อผิดพลาดและ hardcode จำนวนคอลัมน์และสายออกที่ฉันสามารถทำให้มันน้อยกว่าสองเท่าของขนาดของรหัสของคุณ - แทบจะไม่monstruous :) ล้อเล่นกัน: ใช่แล้วห้องสมุดมีอยู่ในลีนุกซ์ส่วนใหญ่แล้ว บนแล็ปท็อปของฉัน line-at-a-time ใช้เวลาประมาณ 14 วินาทีในขณะที่รุ่น line-at-a-time ของฉันใช้เวลาเพียง 1.3 วินาที ความแตกต่างนั้นเกิดจาก PRNG: Mersenne Twister นั้นช้ากว่า Xorshift64 * มาก
สัตว์ที่กำหนด

1
มีสิ่งหนึ่งในทางปฏิบัติที่ฉันอยากจะชี้ให้เห็นว่าคุณพลาดไปแล้ว แต่ฉันหวังว่าคุณจะไม่คิดว่ามันเป็นการปฏิเสธ แต่ก็มีบางสิ่งที่ต้องคิดเกี่ยวกับ: เมื่อฉันพูดถึงคำตอบของฉัน เวลาที่พวกเขาเขียน นั่นคือเหตุผลที่การเพิ่มการแยกวิเคราะห์บรรทัดคำสั่งและข้อความการใช้งานช่วยเหลือมีค่าเกือบตลอดเวลา ฉันมีโปรแกรมอรรถประโยชน์ดังกล่าวจำนวนมากและแทนที่จะค้นหาแหล่งที่มาของพวกเขาเพื่อค้นหาว่าแต่ละคนทำอะไรฉันแค่เรียกใช้พวกเขาดังนั้นพวกเขาจะบอกฉัน และฉันสามารถปรับเปลี่ยนพฤติกรรมของพวกเขาได้มากพอที่จะตอบสนองความต้องการมากกว่าหนึ่งอย่าง ตัดค่าใช้จ่ายในการพัฒนา
สัตว์ที่กำหนด

@NominalAnimal อีกสิ่งที่สำคัญคือการที่คุณประปาออกไป/dev/nullซึ่งจะเป็นไกลเร็วกว่าการเขียนไปยังแฟ้มจริง
phuclv

@ LưuVĩnhPhúc: ไม่จริงหรอก Lappy นี้มี Samsung 128GB SSD ที่มีการอ่านและเขียนตามลำดับ ~ 500 MB / s ใส่สี่ในการกำหนดค่าซอฟต์แวร์ Linux-RAID0 และคุณจะได้รับมากกว่าหนึ่งกิกะไบต์อ่านและเขียนเมื่อสร้างชุดข้อมูลขนาดใหญ่ดังกล่าว (ฉันประมาณ ~ 1.75 TB / s) 1GB / s ได้มาถึงเมื่อหลายปีก่อนด้วยไดรฟ์ SATA 12 ตัว (จานหมุน, ไม่ใช่แม้แต่ SSD) ด้วย Linux sw-RAID0 (หมายเหตุ: ไบต์ / s ไม่ใช่บิต / s.) แน่นอนว่ามันฟังดูโง่สำหรับเครื่อง "ปกติ" แต่ผู้ที่เล่นกับชุดข้อมูลขนาดใหญ่พบว่าคุ้มค่า - คุณโกนเวลาทุกอย่างที่คุณทำ (ด้วยชุดข้อมูลขนาดใหญ่) ทางนั้น.
สัตว์ที่กำหนด

1
@NominalAnimal และ Lu'u: ที่สำคัญกว่านั้นคือถ้าคุณมี RAM เพียงพอโปรแกรมสามารถออกได้ดีก่อนที่ข้อมูลทั้งหมดจะอยู่ในดิสก์ งานส่วนใหญ่ในการwrite()เรียกระบบขนาดใหญ่คือ memcpy ใน pagecache ซึ่งบล็อกเฉพาะในกรณีที่เคอร์เนลตัดสินใจที่จะทำเช่นนั้นแทนที่จะจัดสรรพื้นที่บัฟเฟอร์มากขึ้น โปรแกรมนี้ควรเกิดปัญหาคอขวดบนดิสก์ I / O เมื่อหน่วยความจำไม่แน่นหรือหากคุณใช้ O_DIRECT เพื่อบายพาส pagecache หากคุณwrite()อยู่ในกลุ่มที่เล็กกว่าขนาดแคชหวังว่าข้อมูลของคุณจะเข้าสู่หน่วยความจำหลักเพียงครั้งเดียวและบัฟเฟอร์ที่เขียนใหม่ในตำแหน่งนั้นยังคงร้อนอยู่ในแคช L2 หรือ L3
Peter Cordes

1

ขึ้นอยู่กับคำจำกัดความของ "สุ่ม" ของคุณ หากคุณหมายถึงการสุ่มเข้ารหัสคุณเพียงแค่ต้องมีห้องสมุดที่ดีและกัดสัญลักษณ์แสดงหัวข้อย่อยรอให้มันทำงาน

หากคุณต้องการบางสิ่งที่ดูสุ่มสวยนี่เป็นวิธีง่าย ๆ :

  1. รับไฟล์ที่มีความยาวหลาย Gb ภาพยนตร์เรื่องโปรดของคุณจะดี
  2. Gzip มันเป็นวิธีที่ง่ายในการบีบรูปแบบซ้ำ ๆ
  3. อ่านไฟล์ nybble (ครึ่งไบต์) ทีละไฟล์ แต่ละค่าจะอยู่ระหว่าง 0 ถึง 15 ให้ทิ้งน้อยกว่า 1 หรือมากกว่า 10 ลบ 1 จากผู้รอดชีวิตพันล้านคนแรกแต่ละคนและเขียนมันออกมาเป็นตัวเลข

อาจใช้เวลาหนึ่งชั่วโมงในการรันบนเครื่องที่ช้า เร็วพอและสุ่มพอสำหรับวัตถุประสงค์ส่วนใหญ่


9
/dev/urandomมีแนวโน้มที่จะดีกว่าgzipทั้งความเร็วและการสุ่ม
เฮมเมอร์

Get a file that is several Gb longคุณจะต้องมีไฟล์ ** อย่างน้อย 8Gb` เพื่อรับไฟล์ 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
ยินดีต้อนรับสู่เว็บไซต์! ดูลิงค์ในหน้าโปรไฟล์ของฉัน มีปัญหามากมายที่นี่ที่ฉันเห็นเกือบทุกอย่างในเชลล์สคริปต์ แต่นั่นก็ไม่ได้ทำให้ถูกต้อง
Wildcard

2
@Wildcard: ไม่เคยเมื่อคุณสามารถเพียงแค่cat file | tr tr <fileIIRC <file trคุณยังสามารถ ฉันคิดว่าคุณกำลังพูดถึงเชลล์สคริปต์นี้ดู clunky และช้าเช่นdu | awkหลังจากทุกบรรทัดเพื่อตรวจสอบขนาดและเปิดไฟล์ใหม่เพื่อต่อท้ายทุกบรรทัดแทนที่จะเปลี่ยนเส้นทางนอกลูป
Peter Cordes

2
@PeterCordes ใช่ เหตุใดการใช้เชลล์ลูปเพื่อประมวลผลข้อความจึงถือว่าไม่ดี? มีความเกี่ยวข้องอย่างยิ่ง - สคริปต์นี้มีพื้นฐานจากแนวคิดที่ว่า Bash เป็นภาษาการเขียนโปรแกรมเช่น C ซึ่งไม่ใช่ แต่ \ @NNNT ฉันหวังว่าคุณจะติดกับเว็บไซต์นี้เพราะเห็นได้ชัดว่าคุณมีเหตุผลที่สมเหตุสมผล :)
Wildcard

4
@PeterCordes cat /dev/urandom | busy-cmdเป็นหนึ่งในกรณีที่หายากเหล่านั้นซึ่งมันสมเหตุสมผลดีที่สามารถแยกการสร้างแบบสุ่มและ cmd ที่ไม่ว่างระหว่างโปรเซสเซอร์ ไม่มากสำหรับ tr แต่สร้างความแตกต่างให้กับแซมodเช่น
Stéphane Chazelas

1
@ StéphaneChazelas: โอ้ถูกต้อง !! ใช่การเรียกของระบบ read () เป็นที่ที่ใช้เวลา CPU RNG
Peter Cordes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.