生成包含随机数字的1 GB文本文件的最快方法是什么?


52

我尝试了bash脚本,但是创建一个简单的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:请注意,尽管我的回答绝对是tongue舌-我实际上并不建议为此任务编写C程序!-,如果您定期生成如此庞大的数据集,或者经常生成它们,则该方法可以节省您的时间。(在我的笔记本电脑上,它将在大约十秒钟内生成1GB的空格分隔数字。)但是,如果这是一次性的,甚至不要考虑为此编写C程序(除非您喜欢编程,请考虑一下练习等);Shell命令和实用程序以更少的总时间和精力来完成任务。
名义动物

7
这非常快且符合RFC 1149.5:yes 4 | tr '\n' ' ' | fold -w 200 | head -c1G
Matthew Crumley,

Answers:


38

由于问题的标题,这部分是用舌头回答。

当您寻找“最快的方式...”时,答案几乎总是一些专门的工具。此“答案”显示了这样一种工具,以便您可以进行实验。

这不是一个认真的答案,因为您不应该针对只能执行一次或很少执行的工作使用专门的工具。您会发现,与实际工作相比,最终将花费更多的时间在寻找工具和了解工具上。贝壳像公用事业bashawk是不是最快的,但你通常可以写一行代码实现的工作,只花费秒。perl也可以使用更好的脚本语言,例如,尽管学习曲线perl很困难,但出于这种目的,我犹豫不决地推荐它,因为Perl项目让我感到不满。python另一方面,它的I / O速度较慢,因此有点受阻;但是,仅当您过滤或生成千兆字节的数据时,这才是问题。

无论如何,下面的C89示例程序(仅在可用时才使用POSIX.1以获得更高的时钟精度)应达到约100 MB / s的生成速率(在Linux上使用Intel i5-4200U处理器的笔记本电脑上进行测试,将输出进行管道传输)到/dev/null),采用了相当不错的伪随机数发生器。(输出应通过除MatrixRank测试之外的所有BigCrunch测试,因为代码使用xorshift64 *和排除方法以避免对数字产生偏见。)

十进制数字:

#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.txtOP所需的千兆字节大小。

请注意,程序本身的编写更具可读性,而不是效率。我的目的不是展示代码的效率-无论如何我将使用POSIX.1和低级I / O,而不是通用C接口-而是让您轻松地看到花了多少功夫才有平衡与单行,短壳或awk脚本相比,在开发专用工具及其性能方面。

使用GNU C库,fputc()为每个字符输出调用函数都会产生非常小的开销(间接函数调用或条件调用- FILE接口实际上非常复杂且用途广泛)。在此特定的Intel Core i5-4200U笔记本电脑上,将输出重定向到/dev/null,第一个(fputc)版本花费大约11秒,而一次生产线版本仅花费1.3秒。

我碰巧经常写这样的程序和生成器只是因为我喜欢玩巨大的数据集。我很奇怪 例如,我曾经编写过一个程序,可以将所有有限的正IEEE-754浮点正值打印到文本文件中,并具有足够的精度以在解析时产生完全相同的值。该文件的大小为几GB(也许是4G左右);float人们可能会想到的有限正数并不多。我用它来比较读取和解析此类数据的实现。

对于正常使用案例,例如OP,shell脚本,scriptlet和单行是更好的方法。花费较少的时间来完成总体任务。(除非他们每天左右需要一个不同的文件,或者有很多人需要一个不同的文件,在这种情况下,罕见的情况是,上面提到的专用工具可能需要花费很多精力。)


是的,可能mmap()是达到最佳I / O速度的最简单方法-但在提出任何主张之前先进行基准测试!
Toby Speight

@TobySpeight:在Linux中,低级I / O(即使用write())通常比快mmap()fwrite()并不慢。是的,我已经对此进行了基准测试(只是不针对此特定示例);write()大块(262144、524288或1048576字节)中的数据往往胜过其他方法。由于fputc()许多原因,在GNU C库中实现的版本(我也对其进行了广泛的基准测试)很慢。特别是,实现必须为添加的每个字符执行条件跳转或间接调用;经常产生的轻微开销加起来。
名义动物

只是出于兴趣-您是否已与其他答案进行了性能比较?
Digital Trauma'Nov

2
@DigitalTrauma:我只是为您运行它们,将输出重定向到/dev/nullStéphaneChazelas的剧本大约需要52秒;perl片段(包括head过滤)约58秒;您的shuf代码段(正确的时间;假设粘贴不再花费时间,您只能测量转换时间)大约需要69秒。James Hollis的 C ++ 11一次一行编程需要14秒。上面的程序需要10秒钟。
名义动物

3
(对不起,是我失去的思路。)要点是,选择正确的算法 -此处的随机性足够高,但速度非常快-可以使速度提高近一个数量级(10倍)。(我程序的后一版本比shell或perl片段快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”放弃(with tr -d x),因为我们想要一个均匀的分布(假设/dev/urandom本身具有一个均匀的分布),因此不会给某些数字带来偏差。

产生的97%的字节为一位/dev/urandomfold -w 1每行一位。paste -s用一个由99个空格字符和一个换行符组成的分隔符列表进行调用,因此每行上有100个空格分隔的数字。

head -c1G将获得其中的第一个GiB(2 30)。请注意,最后一行将被截断并且是无界的。您可以截断为2 30 -1并手动添加缺少的换行符,或者截断为10 9个字节,而不是这200个字节行中的5000万个(head -n 50000000这也将使其成为标准/便携式命令)。

这些计时(zsh在四核系统上获得)表明了CPU时间在哪里:

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

首先tr是瓶颈,大部分时间都花在内核中(我想用于随机数生成)。时间大致与我可以从中获取字节的速率/dev/uramdom(大约19MiB / s,在这里,我们为/ dev / urandom的每个0.97字节以32MiB / s的速率生成2个字节)。fold似乎只是在每个字节后插入一个换行符而花费了不合理的CPU时间(15s),但这并不会影响整体时间,因为在我的情况下它可以在其他CPU上工作(添加该-b选项会使它稍微增加一点)高效,dd cbs=1 conv=unblock似乎是更好的选择)。

您可以head -c1G通过设置文件大小的限制(limit filesize 1024m使用zshulimit -f "$((1024*1024))"使用大多数其他shell(包括zsh))来限制子大小,而不用删除几秒钟。

如果我们为每个字节提取2个数字,则可以改善此情况,但是我们需要采用其他方法。上面的方法非常有效,因为tr它只查找256字节数组中的每个字节。它不能一次执行2个字节的操作,并且使用类似的hexdump -e '1/1 "%02u"'方法使用更复杂的算法来计算字节的文本表示形式将比生成随机数本身更加昂贵。不过,如果像我这样,您拥有一些可节省时间的CPU内核,那么它​​仍然可以节省几秒钟:

带有:

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

我得到了(但是请注意,这里是1,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个CPU内核之间分配得更好,因此最终所需的挂钟时间更少。现在是瓶颈hexdump

如果使用dd而不是基于行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 ddiflag=fullblockstatus=none),这也是:

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

回到产生随机数的瓶颈。

现在,如@OleTange所指出的那样,如果您具有该openssl实用程序,则可以使用它来获得更快的(尤其是在具有AES指令的处理器上)字节的伪随机生成器。

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

在我的系统上每秒喷出的字节数是每秒的15倍/dev/urandom。(如果适用于您的用例,我无法评论它如何与加密安全的随机性进行比较)。

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

现在给出:

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

回到hexdump瓶颈。

由于我还有空闲的CPU,因此我可以hexdump并行运行其中的3个。

</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除了zsh/ dev / null上的close命令的stdin 以外的其他shell需要它)。

现在下降到6.2秒,我的CPU几乎被充分利用。


3
我刚刚删除了我以前的答案,并投票赞成该答案。我没有得到一些要求。好答案顺便说一句。
Marcelo

3
您为什么不每次通过都产生多个数字?即使您逐字节阅读,每次仍可以产生2位数字
phuclv

@LưuVĩnhPhúc,我已经删除了perl变慢得多的变体。使用tr | fold | paste方法,我无法获得每字节2位数字。
斯特凡Chazelas

我是afk或我自己尝试过,但是您可以尝试一次使用将42个字节转换为100-102位bc(然后将0、1或2个最高有效位)。
埃里克·塔


23

如果有shuf(最近的GNU coreutils可用),则可以执行以下操作:

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

在我的VM上,这现在比Stéphane的回答慢了约3:4倍。


shuf对我公司电脑不具备-rfmt没有-g太多
phuclv

2
@LưuVĩnhPhúc是-YMMV。我发现core-utils版本8.25确实具有这些功能,但8.4没有。您使用什么版本?
Digital Trauma

1
我正在使用coreutils 8.13
phuclv

@StéphaneChazelas聪明paste/ printf把戏-谢谢。您的答案现在明显更快。
Digital Trauma

17

如果你不需要非常高品质的随机性,并贴近均匀分布是不够好,你可以去快,尤其是在现代的CPU与86一样具有SSE2或AVX2有效的SIMD整数向量。

就像@NominalAnimal的答案一样,因为我们两个人都有相同的想法,但是为x86手动矢量化了。(尽管随机数的质量较差,但对于许多用例仍然可能足够好。)在@GHz的2.5GHz英特尔Haswell上,ASCII码输出速度约为@ 13GB / s,运行速度比@Nominal的代码快15或30倍。带AVX2的CPU。这仍然小于理论上的最大主内存带宽(双通道DDR3-1600约为25.6GB / s),但是我正在定时写入/ dev / null,因此实际上只是重写了在高速缓存中保持高温的缓冲区。Skylake应该比Haswell更快地运行相同的代码(请参阅此答案的底部)。

假设您实际上在磁盘上的I / O瓶颈或将其管道输送到某个地方,那么快速的实现意味着您的CPU甚至不必比空闲时钟高。它使用更少的总能量来产生结果。(电池寿命/热量/全球变暖。)

这是如此之快,以至于您可能不想将其写入磁盘。只需根据需要重新生成(如果再次需要相同的数据,则从相同的种子生成)。即使您希望将其提供给可以使用所有CPU的多线程进程,运行该进程以将数据通过管道传输到它也将使它在L3缓存(以及写入它的内核上的L2缓存)中保持高温。很少的CPU时间。(但请注意,与写入/dev/null相比,管道传输会增加很多开销。在Skylake i7-6700k上,管道传输至wc -c或仅读取+丢弃其输入的其他程序,比写入速度慢大约8倍/dev/null,并且仅使用70%的CPU,但在3.9GHz CPU上仍为4.0GB / s。

即使从快速的PCIe连接的固态硬盘中重新生成它也比重新读取它更快,但是如果IDK效率更高(向量整数乘法器一直很忙,并且可能很耗电,则与其他AVX2一起使用),它的IDK更高。 256b矢量ALU)。OTOH,我不知道从磁盘读取多少CPU时间会占用使所有处理该输入的所有内核最大的工作。我猜想,以128k块重新生成的上下文切换可能与运行文件系统/页面缓存代码以及分配页面以从磁盘读取数据相比具有竞争力。当然,如果它在页面缓存中已经很热,则基本上只是memcpy。OTOH,我们已经写的速度和memcpy一样快!(必须在读写之间分配主内存带宽)。(还要注意,写入内存rep movsb(在微代码中优化了memcpy和memset,避免了RFO,因为Andy Glew在P6(Pentium Pro)中实现了它))。


到目前为止,这仅是概念上的证明,而换行符的处理仅大致正确。在2的幂的缓冲区的末尾是错误的。随着更多的开发时间。我相信我可以找到一种更有效的方法来插入也完全正确的换行符,开销至少要低至此(与仅输出空格相比)。我认为这大约是10%到20%。我只是想知道我们可以运行多快,而不是真正拥有它的完善版本,因此我将把这部分留给读者作为练习,并附上一些描述一些想法的评论。


在具有2.5GHz最大睿的Haswell i5和DDR3-1600MHz RAM的情况下,定时产生100GiB的速度却缩小了。(在Win10上使用gcc5.4在cygwin64上计时-O3 -march=native,省去了,-funroll-loops因为我花了足够的时间在这台借用的笔记本电脑上获得了不错的计时。应该刚刚在USB上启动了Linux)。

除非另有说明,否则写入/ dev / null。

  • 詹姆斯·霍利斯(James Hollis):(未经测试)
  • 标称的fwrite版本:〜2.21s
  • 此(SSE2):〜0.142s(无标度时间=实数= 14.232s,用户= 13.999s,sys = 0.187s)。
  • 此(AVX-128):〜0.140s
  • 此(AVX2):〜0.073s(未缩放:real = 0m7.291s,user = 0m7.125s,sys = 0m0.155s)。
  • 此(AVX2)cygwin管道连接至wc -c,具有128kiB的缓冲区大小:0.32s,CPU频率为2.38GHz(最大双核Turbo)。(无标度的时间:real = 32.466s用户= 11.468s sys = 41.092s,包括this和wc)。不过,实际上只有一半的数据被复制了,因为我的愚蠢程序假定write可以处理整个缓冲区,即使不是这种情况,并且cygwin write()每次对管道的调用仅处理64k。

因此,使用SSE2,它比@Nominal Animal的标量代码快15倍。使用AVX2,速度提高了约30倍。我没有尝试过使用Nominal的代码来write()代替的版本fwrite(),但大概对于大型缓冲区而言,stdio通常不会出现任何问题。如果要复制数据,那将导致大量的速度下降。


在64位Linux 4.2(Ubuntu 15.10)上以DDR2-533MHzCore2Duo E6600(Merom 2.4GHz,32kiB专用L1、4MiB共享L2高速缓存)和DDR2-533MHz上产生1GB数据的时间。对于write()仍使用128kiB的缓冲区大小,尚未探讨该维度。

除非另有说明,否则写入/ dev / null。

  • (SSE2)使用换行符处理和每个随机字节向量中的4个数字向量:0.183s(定时在18.3s内执行100GiB,但对于1GiB运行而言,结果类似)。每个周期1.85条指令。
  • (SSE2),传送到wc -c:0.593s(未缩放:real = 59.266s user = 20.148s sys = 1m6.548s,包括wc的CPU时间)。与cygwin使用的write()系统调用次数相同,但是实际上是管道传输所有数据,因为Linux将所有128k的write()都处理到了管道中。
  • NominalAnimal的fwrite()版本(gcc5.2 -O3 -march=native),运行速度为./decdig 100 $((1024*1024*1024/200)) > /dev/null:3.19s +/- 0.1%,每个周期有1.40条指令。-funroll-loop可能有微小的区别。 clang-3.8 -O3 -march=native:3.42秒+/- 0.1%
  • 标称fwrite管道传递到wc -c:实际= 3.980s用户= 3.176s sys = 2.080s
  • James Hollis的一次生产线版本(clang++-3.8 -O3 -march=native):22.885s +/- 0.07%,每个周期有0.84条指令。(g ++ 5.2稍慢:22.98s)。一次只写一行可能会严重伤害。
  • 斯蒂芬·查泽拉斯(StéphaneChazelas)tr < /dev/urandom | ...:真实= 41.430s用户= 26.832s sys = 40.120s。 tr大部分时间都将CPU核心全部占用,几乎所有时间都花在内核驱动程序上,生成随机字节并将其复制到管道中。该双核计算机上的另一个核心正在运行其余的管道。
  • time LC_ALL=C head -c512M </dev/urandom >/dev/null:也就是说,无需管道就可以读取这么多的随机性:real = 35.018s user = 0.036s sys = 34.940s。
  • LĩuVĩnhPhúc的perl程序(来自Ubuntu15.10的perl v5.20.2)
    LANG=en_CA.UTF-8::real = 4m32.634s user = 4m3.288s sys = 0m29.364。
    LC_ALL=C LANG=C:真实= 4m18.637s用户= 3m50.324s sys = 0m29.356s。还是很慢。

  • (SSE2)无需换行处理,每个随机字节向量中的3或4个数字向量(几乎完全相同的速度:该dig3 = v%10步骤在该硬件上的收支平衡):0.166s(每周期1.82条指令) 。从根本上讲,这是我们可以非常高效地使用换行符接近的下限。

  • (SSE2)旧版的这种不换行处理,但只使用获得每uint16_t元素的一个数字v%100.222秒 +/- 0.4%,每个周期2.12指令。(与gcc5.2一起编译,-march=native -O3 -funroll-loops。展开循环确实会在此硬件上为该代码提供帮助。请不要盲目使用它,尤其是对于大型程序)。
  • (SSE2)的旧版本,正在写入文件(在3个快速磁硬盘驱动器的RAID10f2上,对于写入不是很优化):〜4秒。可以通过调整内核I / O缓冲区设置来加快速度,从而在write()块之前允许更多脏数据。“系统”时间仍为〜1.0秒,远高于“用户”时间。在这个具有较慢的DDR2-533 RAM的旧系统上,内核将数据存储到页面缓存并运行XFS函数所需的时间要比我的循环要长约4倍,以保持原位重写缓冲区中的数据。缓存。

怎么做

快速PRNG显然是必不可少的。 xorshift128 +可以向量化,因此在SIMD向量的元素中有两个或四个并行的64位生成器。每个步骤都会产生一个完整的随机字节向量。(此处为256b AVX2实现和Intel内部函数)。我之所以选择它,是因为Nominal选择了xorshift *,因为只有使用扩展精度技术的SSE2 / AVX2才可以使用 64位向量整数乘法。


给定一个随机字节向量,我们可以将每个16位元素切成多个十进制数字。我们产生16位元素的多个向量,每个向量都是一个ASCII数字+ ASCII空间。我们将其直接存储到输出缓冲区中。

我的原始版本只是用来x / 6554从向量的每个uint16_t元素中获得一个随机数。它始终在0到9之间(包括0和9)。它偏离9,因为(2^16 -1 ) / 6554只有9.99923。(6554 = ceil((2 ^ 16-1)/ 10),这确保商总是<10.)

x/6554可以通过乘以一个“魔术”常数(定点倒数)和上半部分结果的右移来计算。这是除以常数的最佳情况。一些除数需要更多的操作,而签名除法则需要更多的工作。 x % 10具有类似的偏见,并且计算成本并不便宜。(gcc的asm输出等效于x - 10*(x/10),即使用模乘法逆运算在除法运算的顶部进行额外的乘法和减法。)而且,xorshift128 +的最低位不是那么高品质,因此从高位取熵进行除法更好(质量和速度方面都比取模取低位的熵好。

但是,通过查看低十进制数字,例如@Nominal digit()函数,我们可以在每个uint16_t中使用更多的熵。为了获得最佳性能,我决定使用低3位小数位数和x/6554,以节省一个PMULLW和PSUBW(可能还包含一些MOVDQA),而采用较高质量的选项则采用4位低十进制数。x / 6554受低3位小数位数的影响,因此同一元素的数字之间存在一定的相关性(ASCII输出中8或16位数字的间隔,取决于矢量宽度)。

我认为gcc会被100除以1000,而不是一个较长的链会被10除以,因此它可能不会显着缩短从每个PRNG输出产生4个结果的非循环依赖链的长度。由于模块化的乘法逆,以及xorshift +中的移位,port0(向量乘和移位)是瓶颈,因此保存向量乘绝对是有用的。

xorshift +是如此之快,以至于即使每16位仅使用〜3.3位随机性(即20%的效率),也不会比将其切成多个十进制数字慢很多。我们只估计均匀分布,因为只要质量还不错,这个答案就集中在速度上。

保留可变数量元素的任何类型的条件行为都将花费更多的工作。(但是使用SIMD左包装技术可能仍然可以在某种程度上有效地完成。但是,这对于较小的元素尺寸而言效率较低;巨型shuffle-mask查找表不可行,并且没有小于32-的AVX2交叉交叉shuffle 128b PSHUFB版本仍可以使用BMI2 PEXT / PDEP即时生成掩码,就像使用具有较大元素的AVX2一样,但是这很棘手,因为64位整数仅包含8个字节。该答案上的某些代码可能适用于更高的元素数量。)


如果RNG的延迟是一个瓶颈,那么通过并行运行两个向量生成器,交替使用我们使用的一个向量,我们可以走得更快。编译器仍然可以轻松地将所有内容保留在展开循环中的寄存器中,并使两个依赖项链并行运行。

在当前版本中,切碎PRNG的输出,实际上是瓶颈是端口0吞吐量,而不是PRNG延迟,因此没有必要。


代码:AVX2版本

完整版,带有有关Godbolt编译器资源管理器的更多注释。

不太整洁,对不起,我必须入睡并想要张贴此消息。

为了得到SSE2版本s/_mm256/_mms/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(或希望理解C99的GNU C方言和Intel内部函数的任何其他编译器)进行编译。GNU C向量扩展非常方便地使编译器使用模块化乘法逆来生成用于除法/模的幻数,偶尔使用__attribute__s很有用。

可以方便地编写,但是需要更多代码。


性能说明:

用于插入换行符的重叠式商店要决定放置位置的开销很大(分支错误预测和Core2的前端瓶颈),但是商店本身对性能没有影响。仅注释掉编译器的asm中的存储指令(使所有分支保持相同),就不会使Core2上的性能完全保持不变,重复运行可使同一时间的+/-小于1%。因此,我得出结论,存储缓冲区/缓存可以很好地处理它。

但是,ascii_digitspace如果展开足够多的计数器/分支,则使用带有一个带有换行符的元素的某种类型的旋转窗口可能会更快。


写入/ dev / null基本上是一个空操作,因此该缓冲区可能在L2高速缓存(Haswell上每个内核256kiB)中保持高温。从128b向量到256b向量的完美加速是可以预期的:没有额外的指令,并且所有内容(包括存储)都以两倍的宽度发生。但是,换行插入分支的使用频率是两倍。不幸的是,我没有在Haswell cygwin设置上放下那一部分#ifdef

Haswell上每个AVX2存储2.5GHz * 32B / 13.7GB / s = 5.84个周期。 很好,但是可能更快。cygwin系统调用中可能有一些开销比我想象的要大。我没有尝试在编译器的asm输出中注释掉那些内容(这将确保没有任何优化。)

L1高速缓存可以在每个时钟周期维持一个32B存储,而L2的带宽并不低(尽管时延较高)。

当我看过几个版本的IACA时(没有换行的分支,但是每个RNG向量只得到一个ASCII向量),它预测的是每4或5个时钟一个32B向量存储。

我希望自己能从每个RNG结果中提取更多数据,从而获得更大的提速,这是我自己查看了asm,并考虑了Agner Fog的指南和其他优化资源,这些资源已在SO x86标签Wiki中添加了链接。)

在Skylake上,向量整数的乘法和移位运算可能会比Haswell(仅p0)多两倍的端口(p0 / p1),这可能会更快。xorshift和数字提取都使用大量的移位和乘法。(更新:Skylake在3.02 IPC上运行它,为我们提供了每32字节AVX2存储3.77个周期,每1GB迭代时间为0.030s,/dev/null在3.9GHz的i7-6700k上的Linux 4.15上进行写入。


它不需要64位模式即可正常工作。使用编译时-m32,SSE2版本的速度一样快,因为它不需要太多的向量寄存器,并且所有64位数学运算都是在向量中完成的,而不是通用寄存器。

实际上,在Core2上的32位模式下,它稍快一些,因为比较/分支宏融合仅在32位模式下起作用,因此,乱序内核的uops较少(18.3s(每个时钟1.85条指令) 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运算符才是真正的收益,那我不会感到惊讶,因为前端应该很好地保持2.0 IPC的平均值。

所有这些差异在Haswell上消失了,整个事情应该从解码后的uup缓存(如果不是回送缓冲区)运行。自Nehalem以来,ALU +分支宏融合在这两种模式下均有效。


6
我只是喜欢您如何将“野兽模式”纳入主题!:)更重要的是,如果您确实需要或想利用手头的硬件的低级知识来发挥最大的性能,它就是可以获得什么样的收益的一个很好的例子。另外,我们在这里只使用一个线程;当前大多数台式机和服务器的Intel / AMD处理器(甚至是轻量级平板电脑和SBC中的ARM处理器)都具有多个内核,因此仍然有更多的实际时间加速可用。最后,由于涉及到巨大的努力,“最快的方式”提出问题是不切实际
Nominal Animal

1
@NominalAnimal:是的,即使慢速的ARM四核或octo内核也可以轻松地使主内存带宽达到与NEON相同的目的(即使它们已连接至快速双通道DDR3),即使它具有64位整数SIMD并进行了移位也是如此。 。我假设NEON在音频工作中具有16位元素大小的倍数。对于有序的ARM,指令调度将更加有效,因为循环承载的依赖链(xorshift128 +)的每次迭代都会馈送一些独立的依赖链,将其切碎并将其存储到内存中……
Peter Cordes

...乱序执行会吃掉早餐,因为整个过程很短,以至于ROB(HSW IIRC上为192 uop)可以进行多次迭代。(即乱序执行看到的指令“窗口”包括多个迭代)。因此,CPU可以在2或3次迭代之前完成最终存储,同时也可以从当前迭代的开始开始。这隐藏了独立链的延迟,因此仅吞吐量很重要。在有序内核上,这将需要软件流水线处理……
Peter Cordes

...如果您使用内在函数(或整件事的GNU C本机矢量语法,就像我首先应该做的那样)编写的话,一个好的ARM编译器应该为您完成一些工作。我没有真正做到这一点的经验,因此您可能需要按摩循环,并可能在源代码中进行一些手动展开/软件管道化以获得良好的汇编。(在高端手机中发现了一些乱序的ARM内核,但它们没有Haswell那样大的乱序窗口。OTOH,它们的峰值吞吐量也较低,因此更少从找到更多ILP中获得收益)。
彼得·科德斯

1
@NominalAnimal:也同意这个问题的愚蠢性。对随机性没有任何限制的“最快”是愚蠢的……使用BTRFS,磁盘上的同一数据可以多次成为文件的一部分(请参见4.2中的EXTENT_SAME)。因此,您可以生成一个随机的4kiB或1MB并重复进行。它是短期的随机性,但仍然是随机的,并且仅花费元数据I / O。(实际上,您需要将该块以换行符结尾。lcm(4096,4096 * 200)= 4096 * 200 = 819200 = 800kiB,因此您的重复块是其任意倍数。)
Peter Cordes

14

这是一个我希望简单理解的解决方案:

od -An -x /dev/urandom | tr -dc 0-9 | fold -w100 | awk NF=NF FS= | head -c1G
  • od从创建一个统一的十六进制数字流/dev/random
  • tr摆脱字母,只保留0-9数字
  • fold 确保每行有100位数字
  • awk 在行内插入空格
  • head 将输入截断为1 GB

2
这是一种很好的替代方法,可以按字节逐个/ dev / random产生多个位数,同时仍保持均匀分布,平均每256个/ dev / urandom字节产生320个位数(比转换小于200模的字节时要少100到十进制,但每256字节为您提供400位数字)。
斯特凡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,现在越来越多的人倾向于使用技嘉== 1e9和技嘉== 2 ^ 30,因为这是IEC标准的定义。参见维基百科。请注意,Gb本身宁愿是千兆
斯特凡Chazelas

6

这类似于StéphaneChazelas的方法,但是我一次读取了64位以提高性能。分布仍然是统一的,但是现在每8个字节可获得19位数字,而不是像以前那样在最佳情况下只有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 '16

@cuonglm是的,正如我说的那样,如果该系统上的perl不是64位,则必须将程序更改为next if $n >= 1000000000; $s = sprintf("%09u", $n);仅获得9位数字
phuclv

您不能,$n = unpack("Q")如果不支持Quad ,程序将在崩溃。
cuonglm '16

1
@cuonglm BEGIN{$/=\4; $,=" "} $n = unpack("L");也更改为
phuclv

1
抱歉,从8个字节输入中获得19位数字的时间仅占大约54.2%,其余时间都不输入,每个输入字节平均为1.29位数字。如果您更喜欢Stephane <16e18并除以16,则在1.95 dpB时将得到18位86.7%。如果使用32位元,则<4e9 /42.10 dpB的9位数字占93.1%。但是5个字节(如hex(H10))<1e12在2.18 dpB时给出12位90.9%,或者将十六进制分成两半,每半进行一次,<1e6 在2.29 dpB时给出6位95.4%;这接近log_10(256)= 2.41的限制。
dave_thompson_085 '16

3

如果您需要速度,我有点同意Nominal Animal使用编译的编程语言。但是,您不必用C编写自己的RNG代码。C++ 11在其标准库中提供了出色的Mersenne Twister。

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

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

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

上面的代码相当简单,将输出传递到文件时大约需要一分钟。通过创建一个足以容纳100位数字的字符串并将其砍入数字,我们可以快得多。这使我们可以在每一行而不是每一位调用cout。

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

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

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

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

这段代码花了我的机器大约六秒钟的时间。请记住,这是标准输出,因此请将其通过管道传输到文件。

我有几个免责声明。首先,我在Windows PC上编写此代码。我认为这些库都存在于Linux上,但是如果我错了,请务必指出。

而且,它实际上输出的空格分隔数字精确为十亿个位,从技术上来说,这是一个千兆字节,但可能并不完全符合您的要求。它输出500万行,每行100位。如果差异很重要,则可以增加行数。在我的Windows框中,该文件似乎略大于10 ^ 9字节,我认为这与多余的换行符有关。


2
嘿,批评不是很公平!:)我的大多数程序都是命令行参数解析。如果我也省略注释,错误检查以及对输出的行数和行数进行硬编码,那么我可以使其小于代码大小的两倍-几乎不令人困惑。:)开玩笑:是的,大多数Linux发行版中都提供了这些库。在我的笔记本电脑上,您的一次生产线大约需要14秒,而我的一次生产线版本只需要1.3秒。差异仅是由于PRNG:Mersenne Twister比Xorshift64 *慢得多。
名义动物

1
我想指出一件事,就是您确实错过了,但我希望您不要将其视为消极因素,而应该考虑一下:正如我在回答中提到的那样,一次性程序很少值得他们花时间写作。这就是为什么添加命令行解析和帮助用法文本几乎总是值得的。我有很多这样的实用程序,我只是运行它们,而不是寻找它们的源代码来找出它们各自的作用,所以它们会告诉我。而且我可以修改他们的行为,以适应不止一种需求。摊销开发成本。
名义动物

@NominalAnimal另一个重要的事情是您通过管道传输的输出/dev/null要比写入真实文件
快得多

@LưuVĩnhPhúc:嗯,不是真的。这台笔记本电脑有Samsung 128GB SSD,可连续读写约500 MB / s。将四个放入Linux软件RAID0配置中,当生成如此大的数据集时(我估计约为1.75 TB / s),您将获得每秒超过1 GB的读写能力。几年前,带有Linux sw-RAID0的12个SATA驱动器(旋转磁盘,甚至不是SSD)达到了1GB / s。(注意:字节/秒,而不是比特/秒。)当然,对于“普通”机器来说,这听起来很愚蠢,但是那些使用大型数据集的人会发现这很有价值-您可以节省所有工作(使用大型数据集)那样。
名义动物

1
@NominalAnimal和Lu'u:更重要的是,如果您有足够的RAM,程序可以在数据全部存储在磁盘上之前退出。大型write()系统调用中的大部分工作是进入页面缓存的memcpy,只有在内核决定这样做而不是分配更多的缓冲区空间时才会阻塞。仅当内存不足或使用O_DIRECT绕过页面缓存时,此程序才应成为磁盘I / O的瓶颈。如果您write()的块小于缓存大小,则希望您的数据仅进入主内存一次,并且在适当位置重写的缓冲区在L2或L3缓存中仍然很热。
彼得·科德斯

1

这取决于您对“随机”的定义。如果您的意思是加密随机的,则只需要获得一个好的库并忍耐不住,等待它运行即可。

如果您只需要一些看起来很随意的东西,这是一种简单的方法:

  1. 获取几Gb长的文件。您最喜欢的电影会很好。
  2. 用Gzip压缩,这是挤出重复图案的简便方法
  3. 一次遍历文件一次(半字节)。每个值都将在0到15之间。丢弃小于1或大于10的值。从前十亿个幸存者中减去1,并将其写为数字。

在慢速的计算机上运行可能需要一个小时。在大多数情况下足够快且足够随机。


9
/dev/urandomgzip在速度和随机性方面都可能比更好。
Stig Hemmer's

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
欢迎光临本站!查看我的个人资料页面上的链接。我在shell脚本中几乎可以普遍看到很多问题,但这并不能使它们正确。
通配符

2
@Wildcard:从不cat file | tr时,你可以tr <file。IIRC,您甚至可以<file tr。我以为您只是在说这个shell脚本看上去笨拙而缓慢,就像du | awk在每一行之后检查大小一样,然后重新打开文件以追加每一行,而不是在循环外重定向。
彼得·科德斯

2
@PeterCordes,是的。 为什么使用shell循环处理文本被认为是不好的做法?尤其相关-该脚本基于Bash是诸如C之类的编程语言的思想,而并非如此。但是,\ @ NamNT,我希望您能留在这个网站上,因为很显然您的想法很合理。:)
通配符

4
@PeterCordes cat /dev/urandom | busy-cmd是少数有意义的例子之一,因为它可以在处理器之间拆分随机生成和繁忙的cmd。对于tr来说不算什么,但是对于Sam来说却有所不同od
斯特凡Chazelas

1
@StéphaneChazelas:哦,对了!是的,read()系统调用是花费RNG CPU时间的地方。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.