ランダムな数字を含む1 GBのテキストファイルを生成する最も速い方法は何ですか?


52

bashスクリプトを試しましたが、単純な1 MBファイルを作成するのに時間がかかりすぎました。答えは/dev/randomまたはを使用することにあると思いますが、/dev/urandomここの他の投稿では、これらを使用してあらゆる種類のデータをファイルに追加する方法を示していますが、数字のみを追加したいと思います。

だから、0から9の間の数字だけを含むサイズ1GBのランダムファイルを作成するために使用できるコマンドはありますか?

編集:出力をこのようなものにしたい

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

範囲は0〜9です。これは、0、1、2、3、4、5、6、7、8、9の数字のみを意味します。また、行ごとにスペースで区切って100行にする必要がありますn。このnは気にしないもので、最終的なサイズは1 GBにしたいです。

編集:Ubuntu 16.04 LTSを使用しています



21
おそらく、「ランダム」という意味を言うべきです-暗号強度はランダムですか、それとも擬似ランダムシーケンスで十分ですか?
トビー・スペイト

4
@posixKing:私の答えは間違いなくおかしいですが、実際にはそのようなタスクのためにCプログラムを書くことを提案しているわけではないことに注意してください!-、そのような巨大なデータセットを定期的に生成する場合、または頻繁に生成する場合、このアプローチにより時間を節約できます。(私のラップトップでは、約10秒で1GBのスペースで区切られた数字が生成されます。)ただし、これが1回限りの場合は、Cプログラムを書くことも考えないでください(プログラミングが好きでない限り、練習など); シェルコマンドとユーティリティは、より少ない総時間と労力でタスクを達成します。
公称動物

7
これはかなり高速で、RFC 1149.5に準拠:yes 4 | tr '\n' ' ' | fold -w 200 | head -c1G
マシューCrumley

回答:


38

これは、質問のタイトルのために、部分的には冗談です。

「…への最速の方法」を探すとき、答えはほとんどの場合、特別なツールです。この「答え」は、実験できるように、そのようなツールの1つを示しています。

これは深刻な答えではありません。なぜなら、一度しかやらない、あるいはめったにしない仕事のための特別なツールを調べるべきではないからです。ご覧のとおり、実際に作業を行うよりも、ツールを探してそれらについて学ぶのに多くの時間を費やすことになります。シェルなどのユーティリティbashとはawk最速ではありませんが、あなたは通常、書くことができるワンライナーを秒しか費やして、仕事を達成するために。より良いスクリプト言語perlも使用できますが、学習曲線perlは急ですが、そのような目的のためにそれをお勧めすることをためらいます。python一方、I / Oはかなり遅いため、やや不自由です。ただし、ギガバイトのデータをフィルタリングまたは生成する場合にのみ問題になります。

いずれの場合でも、次のC89サンプルプログラム(利用可能な場合にのみ高精度のクロックにPOSIX.1を使用)は、約100 MB / sの生成レートを達成します(LinuxでIntel i5-4200Uプロセッサを搭載したラップトップでテストし、出力をパイピングto /dev/null)、かなり良い擬似乱数ジェネレーターを使用します。(コードはxorshift64 *と除外メソッドを使用して数字の偏りを防ぐため、出力はMatrixRankテストを除くすべてのBigCrunchテストに合格する必要があります。)

decimal-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()各桁を一度に出力する代わりに1回行えば、はるかに高速にできます。出力がブロックデバイスの場合、部分的な(2のべき乗以外の)書き込みを回避するために、ストリームを完全にバッファリングしたままにすることに注意してください。

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

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

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

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

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

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

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

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

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

    return result - UINT64_C(1);
}

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

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

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

    return retval;
}

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

    if (!src)
        return errno = EINVAL;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return EXIT_SUCCESS;
}

注:両方の例を2016-11-18に編集して、数字の均一な分布を確保します(ゼロは除外されます。たとえば、さまざまな擬似乱数ジェネレーターの比較と詳細については、こちらを参照してください)。

たとえばを使用してコンパイル

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(500万; 2で割った列で割った総バイト数)は、使用することができます

./decimal-digits 100 5000000 > digits.txt

digits.txtOPが望むギガバイトサイズを生成します。

プログラム自体は、効率よりも読みやすさを重視して書かれていることに注意してください。ここでの私の意図は、コードの効率を示すことではありません—汎用Cインターフェイスではなく、とにかくPOSIX.1と低レベルI / Oを使用します。ワンライナーまたは短いシェルまたはawkスクリプトレットと比較した、専用ツールの開発とパフォーマンスの比較。

GNU Cライブラリを使用して、fputc()すべての文字出力に対して関数を呼び出すと、非常に小さなオーバーヘッドが発生します(間接関数呼び出し、または条件付き- FILE実際、インターフェイスはかなり複雑で多用途です)。この特定のIntel Core i5-4200Uラップトップでは、出力をにリダイレクトしますが/dev/null、最初の(fputc)バージョンは約11秒かかりますが、1 行ごとのバージョンは1.3秒しかかかりません。

このようなプログラムやジェネレータを頻繁に書くのは、巨大なデータセットで遊ぶのが好きだからです。私はそのように奇妙です。たとえば、私はかつて、すべての有限の正のIEEE-754浮動小数点値をテキストファイルに出力するプログラムを作成しました。解析時に正確に同じ値を生成するのに十分な精度です。ファイルのサイズは数ギガバイト(おそらく4G程度)でした。float考えるほど多くの有限の正の数はありません。これを使用して、そのようなデータを読み取り、解析する実装を比較しました。

OPが持っているような通常の使用例では、シェルスクリプトとスクリプトレット、およびワンライナーがより良いアプローチです。全体的なタスクを完了するために費やす時間が短縮されます。(毎日別のファイルが必要な場合や、別のファイルを必要とする人が多い場合を除きます。まれなケースですが、上記のような専用ツールは、努力が必要な場合があります。)


ええ、おそらくmmap()最高のI / O速度への最も簡単なルートですが、主張する前のベンチマークです!
トビーSpeight

@TobySpeight:Linuxでは、低レベルのI / O、つまりを使用write()することは、通常よりも高速ですmmap()fwrite()それほど遅くありません。はい、私はそれをベンチマークしました(この特定の例ではありません)。write()大きなチャンク(262144、524288、または1048576バイト)では、他の方法よりも性能が優れている傾向があります。fputc()GNU Cライブラリに実装されているバージョン(私も広範囲にベンチマークを行っています)は、いくつかの理由で遅いです。特に、実装は、追加されたすべての文字に対して条件付きジャンプまたは間接呼び出しのいずれかを実行する必要があります。このわずかなオーバーヘッドが頻繁に発生します。
公称動物

興味がありません-他の回答とパフォーマンスを比較しましたか?
デジタル外傷

2
@DigitalTrauma:私はあなたのためにそれらを実行し、出力をにリダイレクトしました/dev/nullStéphaneChazelasのスクリプトレットは約52秒かかります。perlスニペット(headフィルタリングを含む)約58秒。あなたのshuf(正しいタイミングで、あなただけのペーストはもはや取る文句を言わないと仮定すると、SHUF時間を測定)スニペットは、69秒程度かかります。ジェームズ・ホリスの C ++ 11 1行ずつのプログラムには14秒かかります。上記のプログラムには10秒かかります。
公称動物

3
(上記の私の考えの列を失った、申し訳ありません。)ポイントは、適切なアルゴリズム(ここでは十分にランダムですが非常に高速なPRNG )を選ぶことで、ほぼ1桁(10倍)の速度向上をもたらしました。(私のプログラムの後者のバージョンは、シェルまたはperlスニペットよりも約40倍高速です。)これは典型的なものです。上記の答えでプログラムを作成するときに適切なアルゴリズム選択することを強調したほうがいいでしょうか?(一方で、これはプログラミングの質問ではなく、Unix / Linuxの質問であり、大量の数字を生成する方法に関するものです。)
公称動物

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、8進数で0から0377)を変換します:最初の25バイトを0、次の25バイトを1 ...、25 9残り(250から255)を「x」に変換しますtr -d x均一な分布(/dev/urandomそれ自体が均一な分布であると仮定します)が必要なため(を使用して)破棄し、いくつかの桁にバイアスを与えないでください。

これにより、のバイトの97%に対して1桁が生成されます/dev/urandomfold -w 1行ごとに1桁にします。paste -s99個のスペース文字と1個の改行文字で構成される区切り文字のリストを使用して呼び出されるため、各行に100個のスペースで区切られた数字が含まれます。

head -c1Gその最初のGiB(2 30)を取得します。最後の行は切り捨てられ、区切られないことに注意してください。2 30 -1に切り捨てて不足している改行を手動で追加するか、代わりにそれらの200バイト行の5,000万である10 9バイトに切り捨てることができます(head -n 50000000標準/ポータブルコマンドにもなります)。

これらのタイミング(zshクアッドコアシステムで取得)は、CPU時間の使用場所を示します。

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

1つ目trはボトルネックで、ほとんどの時間はカーネルで費やされます(乱数生成の場合)。タイミングは、私がバイトを取得できるレートとほぼ一致しています/dev/uramdom(約19MiB / sで、ここでは、32MiB / sのレートで/ dev / urandomの各0.97バイトに対して2バイトを生成します)。foldバイトごとに改行文字を挿入するために不合理な量のCPU時間(15秒)を費やしているようですが、私の場合は別のCPUで動作するため、全体の時間には影響しません(-bオプションを追加すると、効率的でdd cbs=1 conv=unblock、より良い代替手段のようです)。

あなたはと離れて行うことができますhead -c1G(ファイルサイズの制限を設定することにより、数秒を切り、ひげをそるlimit filesize 1024mzshulimit -f "$((1024*1024))"(を含むほとんどの他のシェルと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,073,741,824ではなく1,000,000,000バイトです)。

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

全体的にCPU時間は長くなりますが、4つのCPUコア間でより適切に分散されるため、結果としてウォールクロック時間が短くなります。ボトルネックは今hexdumpです。

ddline-basedの代わりに使用する場合fold、実際hexdumpに行う必要がある作業量を減らし、CPU間の作業のバランスを改善できます。

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

(ここddではiflag=fullblockandのGNU を想定しています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

私のシステムでは、毎秒15倍のバイトを吐き出します/dev/urandom。(あなたのユースケースに当てはまる場合、暗号的に安全なランダム性のソースの観点から比較する方法についてはコメントできません)。

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

今すぐ与える:

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

hexdumpボトルネックに戻ります。

CPUにはまだ余裕があるので、そのうち3つをhexdump並行して実行できます。

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

(バックグラウンドで実行する場合、/ dev / nullのcloseコマンドの標準入力<&3以外のシェルに必要ですzsh)。

6.2秒になり、CPUはほぼ完全に使用されました。


3
以前の回答を削除して、この回答に投票しました。要件の一部が得られませんでした。いい答えです。
マルセロ

3
パスごとに複数の数字を生成しないのはなぜですか?バイト単位で読んでも、毎回2桁を生成することができます
-phuclv

@LưuVĩnhPhúc、perlとにかく大幅に遅いバリアントを削除しました。そのtr | fold | pasteアプローチでは、1バイトあたり2桁を取得できません。
ステファンシャゼラス

私はafkです、または自分でこれを試してみますが、一度に42バイトを100から102桁に変換してみてくださいbc(その後、0、1、または2つの最上位桁をドロップしてください)。
エリックタワーズ

gitlab.com/ole.tange/tangetools/tree/master/randは、1秒あたり1〜4 GBの擬似ランダムを生成します(AES品質)。
オレ丹下

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私の会社のPCに持っていない-rfmt持っていない-g、あまりにも
phuclv

2
@LưuVĩnhPhúcうん-YMMV。core-utilsバージョン8.25にはこれらがありますが、8.4にはないことがわかりました。どのバージョンを使用していますか?
デジタル外傷

1
coreutils 8.13
phuclv

@StéphaneChazelas巧妙paste/ printfトリック-ありがとう。あなたの答えは今や明らかに速くなっています。
デジタル外傷

17

非常に高品質のランダム性を必要とせず、均一に近い分布で十分であれば、特にSSE2またはAVX2を備えたx86のような効率的なSIMD整数ベクトルを備えた最新のCPUで非常に高速に実行できます。

これは、@ NominalAnimalの答えに似ています。どちらも同じ考えを持っているが、x86用に手動でベクトル化されているためです。(そして、乱数の品質は悪いですが、多くのユースケースにはおそらく十分です。)これは、2.5GHz Intel 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比べてオーバーヘッドが大きくなることに注意してください。Skylakei7-6700k、パイピング、wc -cまたは入力を読み取り+破棄するだけの別のプログラムでは、書き込みに比べて約8倍遅く/dev/null、 CPU:ただし、3.9GHz CPUでは4.0GB / sです。

再生成は、高速のPCIe接続SSDからでも再読み込みよりも高速ですが、IDKの方が電力効率が高い場合(ベクトル整数乗算器はかなりビジーのままで、おそらく他のAVX2と一緒にかなり電力を消費します) 256bベクトルALU)。OTOH、この入力を処理するすべてのコアを最大限に使用していたものから、ディスクからのCPU時間の読み取りがどれだけ無駄にするかわかりません。128kのチャンクで再生成するコンテキストスイッチは、ファイルシステム/ページキャッシュコードの実行や、ディスクからデータを読み取るためのページの割り当てと競合する可能性があると思います。もちろん、ページキャッシュですでに暑い場合は、基本的にmemcpyです。OTOH、私たちはすでにmemcpyと同じくらい速く書いています!(メインメモリの帯域幅を読み取りと書き込みに分割する必要があります)。(また、メモリへの書き込みがrep movsb(マイクロコードでmemcpyとmemsetを最適化し、アンディグルーがP6(Pentium Pro)で実装したため RFOを回避します)。


これまでのところ、これは概念実証にすぎず、改行の処理はほぼ正しいだけです。2のべき乗のバッファの両端では間違っています。より多くの開発時間。正確に正しい改行を挿入するより効率的な方法を見つけることができると確信しています(少なくともスペースだけを出力する場合と比べて)。これは10〜20%程度だと思います。私は実際に洗練されたバージョンを持っているのではなく、この実行をどれくらい速くできるかを知ることにのみ興味があるので、いくつかのアイデアを説明するコメントとともに、その部分を読者の演習として残します。


DDR3-1600MHz RAMを搭載した2.5GHz maxターボのHaswell i5では、100GiBを生成するタイミングですが、縮小されました。(Win10のcygwin64でgcc5.4を使用して計時しましたが、この借用したラップトップで適切なタイミングを実行するのに十分な時間を費やしていたため-O3 -march=native、省略-funroll-loopsしました。

特に指定がない限り、/ dev / nullに書き込みます。

  • ジェームズ・ホリス:(未テスト)
  • 公称のfwriteバージョン:〜2.21s
  • this(SSE2):〜0.142s(スケールなしの時間= real = 14.232s、user = 13.999s、sys = 0.187s)。
  • これ(AVX-128):〜0.140s
  • これ(AVX2):〜0.073s(スケールなし:real = 0m7.291s、user = 0m7.125s、sys = 0m0.155s)。
  • この(AVX2)cygwinパイピング、へwc -c、128kiBバッファサイズ:CPUで2.38 GHz(最大デュアルコアターボ)で0.32秒。(スケールなしの時間:real = 32.466s user = 11.468s sys = 41.092s、これとの両方を含むwc)。しかし、私の愚かなプログラムは書き込みが完全なバッファーを行うことを想定しているため、実際にはデータの半分しかコピーされませんでしたが、cygwin write()はパイプへの呼び出しごとに64kしか行いません。

したがって、SSE2では、これは@Nominal Animalのスカラーコードよりも約15倍高速です。AVX2を使用すると、約30倍高速になります。私はのwrite()代わりに使用するNominalのコードのバージョンを試しませんでしたfwrite()が、おそらく大きなバッファーの場合、stdioはほとんど邪魔にならないでしょう。データをコピーしている場合、それは多くの速度低下の原因となります。


Core2Duo E6600(Merom 2.4GHz、32kiBプライベートL1、4MiB共有L2キャッシュ)、 64ビットLinux 4.2(Ubuntu 15.10)でDDR2-533MHzで1GBのデータを生成する時間。まだwrite()に128kiBのバッファサイズを使用していますが、その次元については検討していません。

特に指定がない限り、/ dev / nullに書き込みます。

  • (SSE2)これは、改行処理と、ランダムバイトの各ベクトルからの数字の4つのベクトル:0.183秒(18.3秒で100GiBを実行するタイミングですが、1GiB実行で同様の結果)。サイクルあたり1.85命令。
  • (SSE2)これ、パイピングwc -c:0.593s(スケールなし:実数= 59.266sユーザー= 20.148s sys = 1m6.548s、wcのCPU時間を含む)。cygwinと同じ数のwrite()システムコール。ただし、Linuxがすべての128kのwrite()をパイプに処理するため、実際にはすべてのデータがパイプされます。
  • NominalAnimalのfwrite()バージョン(gcc5.2 -O3 -march=native)、./decdig 100 $((1024*1024*1024/200)) > /dev/null3.19秒+/- 0.1%で実行、サイクルあたり1.40命令。-funroll-loopsはおそらく小さな違いをもたらしました。 clang-3.8 -O3 -march=native:3.42秒+/- 0.1%
  • 公称fwrite配管wc -c:real = 3.980s user = 3.176s sys = 2.080s
  • ジェームズ・ホリスのラインアットアタイムバージョン(clang++-3.8 -O3 -march=native):22.885秒+/- 0.07%、サイクルあたり0.84命令。(g ++ 5.2はわずかに遅くなりました:22.98s)。一度に1行だけを書くと、かなりひどく傷つきます。
  • StéphaneChazelas's tr < /dev/urandom | ...:real = 41.430s user = 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:real = 4m18.637s user = 3m50.324s sys = 0m29.356s。まだ非常に遅いです。

  • (SSE2)これは改行を処理せず、ランダムバイトの各ベクトルから3または4桁のベクトル(ほぼ同じ速度:dig3 = v%10このハードウェアではステップは損益分岐点です):0.166s(サイクルあたり1.82命令) 。これは基本的に、完全に効率的な改行処理で近づくことができるものの下限です。

  • (SSE2)改行を処理しないこの旧バージョンですがv%100.222秒 +/- 0.4%、サイクルあたり2.12命令を使用して、uint16_t要素ごとに1桁のみを取得します。(gcc5.2でコンパイルされてい-march=native -O3 -funroll-loopsます。アンロールループは、このハードウェアでこのコードを実行するのに役立ちます。特に大きなプログラムでは、盲目的に使用しないでください)。
  • (SSE2)これの古いバージョン、ファイルへの書き込み(3つの高速磁気ハードドライブのRAID10f2上、書き込み用にあまり最適化されていない):〜4秒。カーネルI / Oバッファ設定を微調整して、write()ブロックの前により多くのダーティデータを許可することにより、高速化できます。「システム」時間はまだ約1.0秒で、「ユーザー」時間よりはるかに長くなっています。低速のDDR2-533 RAMを搭載したこの古いシステムでは、カーネルがデータをページキャッシュにmemcpyし、XFS関数を実行するのに、ループがホットのままのバッファーに書き換えを続けるよりも、約4倍時間がかかりますキャッシュ。

方法

高速PRNGは明らかに不可欠です。 xorshift128 +はベクトル化できるため、SIMDベクトルの要素に2つまたは4つの64ビットジェネレーターを並列に配置できます。各ステップは、ランダムバイトの完全なベクトルを生成します。(ここではIntel組み込み関数を使用した256b AVX2実装)。64ビットのベクトル整数の乗算はSSE2 / AVX2で拡張精度の手法でのみ可能だからです。


ランダムなバイトのベクトルが与えられると、各16ビット要素を複数の10進数に切り分けることができます。それぞれが1つのASCII数字+ ASCIIスペースである16ビット要素の複数のベクトルを生成します。出力バッファに直接保存します。

私の元のバージョンx / 6554は、ベクトルのすべてのuint16_t要素から1つのランダムな数字を取得するために使用されていました。常に0から9の間です。それはから離れてバイアスされています9ので、(2^16 -1 ) / 6554唯一の9.99923です。(6554 = ceil((2 ^ 16-1)/ 10)。これにより、商は常に<10になります。)

x/6554「マジック」定数(固定小数点の逆数)と上位半分の結果の右シフトを1回乗算して計算できます。これは、定数による除算に最適なケースです。一部の除数ではより多くの操作が行われ、署名付き除算では余分な作業が必要になります。 x % 10同様のバイアスがあり、計算がそれほど安くありません。(gccのasm出力はに相当しますx - 10*(x/10)。つまり、モジュラー乗法逆数を使用した除算の最上部での乗算と減算です。)また、xorshift128 +の最下位ビットはそれほど高品質はないため、上位ビットからエントロピーを取得するための除算の方が優れています(低ビットからエントロピーを取得するためのモジュロよりも品質と速度の点で重要です。

ただし、@ Nominalのdigit()関数のように下位10進数を調べることにより、各uint16_tでより多くのエントロピーを使用できます。最大のパフォーマンスを得るために、下位3桁の10進数を取得し、x/65541つのPMULLWとPSUBW(およびおそらくMOVDQA)を保存することと、下位4桁の10進数を取得する高品質オプションを選択することにしました。x / 6554は下位3桁の10進数の数字の影響をわずかに受けるため、同じ要素の数字の間には相関関係があります(ベクトル幅に応じて、ASCII出力では8桁または16桁の間隔)。

gccは、10で連続的に分割する長いチェーンではなく、100と1000で分割していると思うので、各PRNG出力から4つの結果を生成するループを持たない依存チェーンの長さを大幅に短縮することはおそらくないでしょう。port0(ベクトルの乗算とシフト)は、モジュラー乗算逆数とxorshift +のシフトのためのボトルネックです。したがって、ベクトル乗算を保存することは間違いなく便利です。

xorshift +は非常に高速であるため、16個ごとに最大3.3ビットのランダム性(つまり20%の効率)を使用しても、複数の10進数に切り刻むよりもそれほど遅くありません。品質がそれほど悪くない限り、この答えは速度に焦点を合わせているため、均一な分布のみを近似します。

可変数の要素を保持するあらゆる種類の条件付き動作は、より多くの作業を必要とします。(ただし、SIMD左パッキングテクニックを使用すると多少効率的に実行できます。ただし、小さな要素サイズでは効率が低下します。巨大なシャッフルマスクルックアップテーブルは実行可能ではなく、32未満のAVX2レーンクロスシャッフルはありません。ビット要素:128b PSHUFBバージョンは、より大きな要素を持つAVX2のように、BMI2 PEXT / PDEPでオンザフライでマスク生成できる可能性がありますが、64ビット整数は8バイトしか保持できないため、注意が必要です。その答えには、より多くの要素数で機能する可能性のあるコードがあります。)


RNGのレイテンシがボトルネックである場合、ジェネレーターの2つのベクターを使用し、どちらかを交互に実行することにより、さらに高速化できます。コンパイラーは、すべてを展開されたループ内のレジスターに簡単に保持できます。これにより、2つの依存関係チェーンを並行して実行できます。

現在のバージョンでは、PRNGの出力を切り刻んでいますが、実際にはPRNGレイテンシではなくポート0スループットのボトルネックとなっているため、その必要はありません。


コード:AVX2バージョン

Godboltコンパイラエクスプローラーに関するコメントが追加されたフルバージョン。

あまりきちんとはしていません。申し訳ありませんが、眠りにつく必要があります。

SSE2バージョンを取得するには、s/_mm256/_mms/256/128/s/v16u/v8u/、および変更vector_size(32)16にはまた、4 * 16から4 * 8から改行増分を変更します。(私が言ったように、コードは乱雑であり、2つのバージョンをコンパイルするためにうまくセットアップされていません。もともと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カウンター/分岐がなくなるほど十分に展開すれば、1つの要素に改行がある何らかの種類の回転ウィンドウを使用すると、さらに高速になる可能性があります。


/ dev / nullへの書き込みは基本的にノーオペレーションなので、おそらくバッファはL2キャッシュ(Haswellのコアあたり256kiB)でホットのままです。128bベクトルから256bベクトルへの完全な高速化が期待されます。余分な命令はなく、すべて(ストアを含む)が2倍の幅で発生します。ただし、改行挿入ブランチは2倍の頻度で取得されます。残念ながら、Haswellのcygwinのセットアップには時間がかかりませんでし#ifdefた。

2.5GHz * 32B / 13.7GB / s = HaswellのAVX2ストアごとに5.84サイクル。 それはかなり良いですが、より速くなる可能性があります。おそらく、cygwinのシステムコールには、思っていたよりもいくらかオーバーヘッドがあります。コンパイラーのasm出力でそれらをコメントアウトしようとしませんでした(これにより、最適化されたものがなくなります)。

L1キャッシュは1クロックあたり1つの32Bストアを維持でき、L2は帯域幅がそれほど低くありません(ただし、待ち時間は長くなります)。

IACAを数バージョン前に見たとき(改行の分岐はありませんが、RNGベクトルごとに1つのASCIIベクトルしか取得できませんでした)、4または5クロックごとに1つの32Bベクトルストアのようなものを予測していました。

SO x86タグwikiにリンクを追加したAgner Fogのガイドやその他の最適化リソースを考慮して、私自身のasmに基づいて、各RNG結果からより多くのデータを抽出することで、より高速化を望んでいました。

ベクトル整数の乗算とシフトがHaswell(p0のみ)に比べて2倍のポート(p0 / p1)で実行できるSkylakeでは、おそらくより高速になります。xorshiftと桁抽出の両方で、多くのシフトと乗算が使用されます。(更新:Skylakeは3.02 IPCで実行し、32バイトAVX2ストアごとに3.77サイクル、1GB反復あたり0.030秒のタイミングで、3.9GHzのi7-6700k /dev/null上のLinux 4.15に書き込みます。


うまく動作するために64ビットモードは必要ありません。SSE2バージョンは-m32、非常に多くのベクトルレジスタを必要とせず、すべての64ビット演算が汎用レジスタではなくベクトルで実行されるため、とコンパイルした場合と同じくらい高速です。

比較/分岐マクロ融合は32ビットモードでのみ機能するため、実際にはCore2の32ビットモードでわずかに高速です。したがって、アウトオブオーダーコアのuopが少なくなります(18.3(1.85命令/クロック)対。16.9s(2.0 IPC))。REXプレフィックスを持たないことによるコードサイズの縮小は、Core2のデコーダーにも役立ちます。

また、すべての定数がベクトルregで修正されるわけではないため、一部のreg-regベクトル移動はロードに置き換えられます。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では消えます。Haswellでは、ループバックバッファーではない場合、デコードされたuopキャッシュからすべてを実行する必要があります。ALU + branchマクロ融合は、Nehalem以降の両方のモードで機能します。


6
私はあなたがどのように「獣モード」を主題にしたかが大好きです!:)さらに重要なことは、手元のハードウェアの非常に低レベルの知識を活用して、最大のパフォーマンスを本当に必要とするか、絞り出す場合に、どのようなゲインが利用できるかの良い例です。さらに、ここでは1つのスレッドのみを使用しています。最新のデスクトップおよびサーバーのIntel / AMDプロセッサ(および軽量タブレットとのSBCでさえARMのもの)はそうある、複数のコアを持っていることがさらに現実世界-時間取らスピードアップが利用可能。そして最後に、多大な労力が関係しているため、「最速の方法」の質問はどれほど非現実的か。
公称動物

1
@NominalAnimal:ええ、64ビット整数のSIMD加算およびシフトがある場合、遅いARMクワッドまたはオクトコアでさえ、NEONで同じことを行って(高速デュアルチャネルDDR3に接続されていても)メインメモリ帯域幅を簡単に飽和させることができます。NEONには、オーディオ処理用に16ビットの要素サイズの乗算があると思います。命令スケジューリングは、インオーダーARMのためのより多くの作業になりますので、ループ運搬依存チェーンの各反復(xorshift128 +)それまでチョッピングして...それはメモリに保存され得ることのいくつかの独立した依存関係のチェーンを供給
ピーターCordes

...アウトオブオーダー実行は、朝食のためにそれを食べます。なぜなら、いくつかの反復がROB(HSW IIRCで192 uops)に収まるほど全体が短いからです。(つまり、アウトオブオーダー実行が認識する命令の「ウィンドウ」には、複数の反復が含まれます)。そのため、CPUは、2〜3回の反復で最終ストアを終了し、現在の反復の開始時に開始することができます。これにより、独立チェーンのレイテンシが隠されるため、スループットのみが重要になります。インオーダーコアでは、これはソフトウェアのパイプライン化...必要になります
ピーター・コルド

...優れたARMコンパイラーは、組み込み関数(または、最初にやるべきだったように、GNU Cのネイティブなベクトル構文)を使用して作成する場合、その一部を実行する必要があります。私はそれを実際に行った経験がないので、ループをマッサージし、ソースを手動で展開/ソフトウェアパイプライン処理して、良いasmを取得する必要があるかもしれません。(いくつかの異常な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から16進数の均一なストリームを作成し/dev/randomます。
  • tr文字を取り除き、0-9数字のみを保持します
  • fold 1行に100桁あることを確認します
  • awk 行内にスペースを挿入します
  • head 入力を1ギガバイトに切り捨てます

2
これは、平均的な/ dev / urandomの256バイトごとに320桁を生成する(バイトを200モジュロ未満に変換する場合よりも少ない)均一な分布を持ちながら、/ dev / randomのバイトで1桁以上を生成するための素晴らしい代替方法です100から10進数で、256バイトごとに400桁が得られます)。
ステファンシャゼル

6

jotこれには次のコマンドを使用できます。

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

1
@DigitalTraumaの私のバージョンにfmtは目標幅オプションがありません。とにかく、すべての桁が正確に1列を占めるため、正確になります。
ガーデンヘッド

記録のために私のfmtバージョンはfmt (GNU coreutils) 8.25(Ubuntu 16.04)
デジタルトラウマ

2
半分のgbの正しい数値は、1024 * 1024 * 536870912
1024/2

1
@OlivierDulacあなたが話している「ギガバイト」に依存します。技術的には正しくない場合でも、1 Gbを使用して2 ^ 30ではなく10 ^ 9を意味する人もいます。プラス私は素敵なラウンド番号が好きです:)
gardenhead

6
@gardenheadでは、ますます多くの人々がGigabyte == 1e9およびGibibyte == 2 ^ 30に移行する傾向があります。これがIEC標準の定義です。ウィキペディアを参照してください。注ギガビット自体はかなりギガのだろうと少し
ステファンシャゼル

6

これはStéphaneChazelasの方法に似ていますが、パフォーマンスを改善するために一度に64ビットを読み取ります。分布は依然として均一ですが、今では、以前のような最良の場合では8バイトだけでなく、8バイトごとに19桁を取得します

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

32ビットプラットフォームでは、毎回19桁ではなく9桁が読み取られます。


システムが64ビット整数をサポートしていない場合、またはperlクワッドサポートでコンパイルされていない場合、例外が発生する可能性があります。
cuonglm

@cuonglmはい、私が言ったように、perlがそのシステムで64ビットでない場合、プログラムはnext if $n >= 1000000000; $s = sprintf("%09u", $n);9桁のみを取得するように変更する必要があります
-phuclv

$n = unpack("Q")quadがサポートされていない場合、プログラムはクラッシュします。
クオングルム

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

1
申し訳ありませんが、これは8バイトの入力から19桁、時間の約54.2%のみを取得し、残りは入力バイトあたり平均1.29桁を取得します。ステファンのようなものを使用<16e18して16で割ると、1.95 dpBで18桁の86.7%になります。32ビットで<4e9 /4は、2.10 dpBで9桁の93.1%を取得します。ただし、5バイト(hex(H10)として)<1e12は2.18 dpBの場合は12桁の90.9%を<1e6 提供します。これは、log_10(256)= 2.41の制限に近づきます。
-dave_thompson_085

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

上記のコードはかなり単純で、出力をファイルにパイプするのに約1分かかります。100桁に十分な大きさの文字列を作成し、その数字をその中にハックすることにより、はるかに高速に処理できます。これにより、すべての桁ではなく、すべての行でcoutを呼び出すことができます。

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

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

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

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

このコードでは、マシンに約6秒かかります。それが標準出力であることを忘れないで、ファイルにパイプしてください。

免責事項がいくつかあります。まず、私はこれをWindows PCで書いています。ライブラリはすべてLinux上に存在すると思いますが、間違っている場合は必ず指摘してください。

また、実際には正確に5億個のスペースで区切られた数字を出力します。これは技術的にはギガバイトですが、望みどおりではないかもしれません。500万行、1行あたり100桁を出力します。違いが重要な場合は、行数を増やすことができます。私のWindowsボックスでは、ファイルは10 ^ 9バイトよりわずかに大きいようです。これは余分な改行文字と関係があると思います。


2
ちょっと、批判は本当に公平ではありません!:)私のプログラムのほとんどは、コマンドラインパラメーターの解析です。また、コメント、エラーチェック、および列と行の出力数のハードコードを省略した場合、コードのサイズの2倍未満(ほとんど怪しげではない)にすることができます。:)冗談はさておき:はい、ライブラリはほとんどのLinuxディストリビューションで利用可能です。私のラップトップでは、回線ごとに約14秒かかりますが、回線ごとのバージョンには1.3秒しかかかりません。違いはPRNGによるものです。MersenneTwisterはXorshift64 *よりもはるかに遅いです。
公称動物

1
あなたが逃したことを指摘したい実用的なものが1つありますが、ネガティブとしては考えないでください。彼らが書くのにかかった時間。そのため、コマンドライン解析とヘルプの使用方法のテキストを追加するのは、ほとんど常に価値があります。このようなユーティリティプログラムは多数ありますが、それらのソースを探してそれぞれの動作を確認するのではなく、単に実行するだけで、彼らに教えてくれます。また、複数のニーズに合うように動作を変更できます。開発コストの償却。
公称動物

@NominalAnimalもう1つの重要なことは/dev/null、実際のファイルに書き込むよりもはるかに高速な出力をパイプしたことです
-phuclv

@LưuVĩnhPhúc:まあ、そうでもない。このラップトップには、Samsung 128GB SSDがあり、最大500 MB / sのシーケンシャル読み取りおよび書き込みが可能です。LinuxソフトウェアRAID0構成に4つ入れると、このような大規模なデータセットを生成するときに、1秒間に1ギガバイト以上の読み取りと書き込みが可能になります(推定1.75 TB / s)。Linux sw-RAID0を搭載した12個のSATAドライブ(SSDでさえなく、回転するプラッター)で数年前に1GB / sに達しました。(注:バイト/秒、いないビット/秒)確かに、それは「通常」のマシンのために愚かに聞こえるが、大規模なデータセットで遊ぶ人たちは、その価値がある見つける-あなたはオフの時間を剃るすべてあなた行う(大規模なデータセット付き)そのように。
公称動物

1
@NominalAnimalとLu'u:さらに重要なことは、十分なRAMがある場合、データがすべてディスクに格納される前にプログラムが終了する可能性があることです。大規模なwrite()システムコールでのほとんどの作業は、ページキャッシュへのmemcpyです。これは、カーネルがより多くのバッファスペースを割り当てる代わりにそれを行うことを決定した場合にのみブロックします。このプログラムは、メモリが不足している場合、またはO_DIRECTを使用してページキャッシュをバイパスした場合にのみ、ディスクI / Oのボトルネックになります。write()キャッシュサイズよりも小さいチャンクの場合、データがメインメモリに1回しか入らないようにし、所定の場所に書き換えられたバッファがL2またはL3キャッシュでホットのままであることを願っています。
ピーターコーデス

1

「ランダム」の定義に依存します。暗号的にランダムを意味する場合は、適切なライブラリを取得して弾丸を噛み、それが実行されるのを待つだけです。

かなりランダムに見えるものが必要な場合、簡単な方法を次に示します。

  1. 長さが数Gbのファイルを取得します。お気に入りの映画がいいでしょう。
  2. Gzip it、繰り返しパターンを絞り出す簡単な方法
  3. 一度にニブル(半バイト)ずつファイルを検索します。各値は0〜15の間です。1未満または10より大きい値を捨てます。最初の10億人の生存者からそれぞれ1を引き、数字として書き出します。

低速のマシンで実行するには1時間かかる場合があります。ほとんどの目的に十分な速さとランダム性。


9
/dev/urandomgzip、速度とランダム性の両方で、より優れている可能性があります。
スティグヘマー

Get a file that is several Gb longあなたは1GBのファイルを取得するために少なくとも8Gb`で**ファイルが必要になります
phuclv

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

1
サイトへようこそ!プロフィールページのリンクをご覧ください。ここには非常に多くの問題があり、それらはシェルスクリプトでほぼ普遍的に見られますが、それで正しくなりません。
ワイルドカード

2
@Wildcard:cat file | trいつでもできることはありませんtr <file。IIRC、できます<file tr。このシェルスクリプトについては、du | awk各行のサイズを確認した後、ループ外にリダイレクトするのではなく、すべての行を追加するためにファイルを再度開くなど、不格好で遅いように見えると考えていました。
ピーターコーデス

2
@PeterCordes、はい。 シェルループを使用してテキストを処理するのは悪い習慣と見なされるのはなぜですか?特に関連があります。このスクリプトは、BashはCなどのプログラミング言語ですが、そうではないという考えに基づいています。しかし、\ @ NamNT、非常に論理的な心を持っていることは明らかなので、このサイトに留まることを望みます。:)
ワイルドカード

4
@PeterCordes cat /dev/urandom | busy-cmdは、プロセッサ間でランダム生成とbusy cmdを分割できるため、理にかなっているまれなケースの1つです。trにはそれほどではありませんがod、たとえばSamには違いがあります。
ステファンシャゼラス

1
@StéphaneChazelas:そうそう!! ええ、read()システムコールはRNG CPU時間を費やしている場所です。
ピーターコーデス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.