シミュレーションデータ生成の改善

シミュレーションデータ生成の改善

シミュレートされたデータを使用してCSVを生成しようとしています。

for i in {1..1000000..1}
do 
  echo "$i,$(date -d "2017-08-01 + $(shuf -i 1-31 -n 1) days" +'%Y-%m-%d')" >> $F
done;

1から100万まで繰り返し、一意のIDと生成ランダム日付

しかし、非常に遅い実行されます。平行にできる単一の線はありますか?

答え1

最後に最終結果を確認してください。

for i in {1..1000000..1}
do 
  echo "$i,$(date -d "2017-08-01 + $(shuf -i 1-31 -n 1) days" +'%Y-%m-%d')" >> $F
done;

シェルループは遅く、この特定のループを特に遅くする2つの主な要因があります。

  1. 繰り返すたびにファイルを開いて追加します。
  2. 外部ユーティリティ(shufand)は各反復で2回実行されます。dateこれはechoおそらくシェルに組み込まれているため、オーバーヘッドが少なくなります。

出力リダイレクトは解決するのが最も簡単です。

for i in {1..1000000..1}
do 
  echo "$i,$(date -d "2017-08-01 + $(shuf -i 1-31 -n 1) days" +'%Y-%m-%d')" 
done >"$F"

これにより、出力ファイルが一度だけ開かれ、ループ中に開いたままになります。


残りのコードはawkGNUを使用してより効率的に実行できますdate(使用しているので、shufLinuxシステムを使用していると仮定するので、date実際にはGNUである可能性が高いですdate)。

awk 'END { for (i=0;i<100;++i) { printf("2017-08-01 + %d days\n", 1+int(31*rand())) }}' /dev/null

これにより、次の100行が生成されます。

2017-08-01 + 22 days
2017-08-01 + 31 days
2017-08-01 + 11 days
2017-08-01 + 27 days
2017-08-01 + 27 days
2017-08-01 + 20 days
(etc.)

これをGNUにインポートしましょうdate。 GNUには、プログラムが出力する日付仕様など、複数の日付仕様をまとめて入力できるdateフラグがあります。-fawk

awk 'END { for (i=0;i<100;++i) { printf("2017-08-01 + %d days\n", 1+int(31*rand())) }}' /dev/null |
date -f - +'%Y-%m-%d'

今私達は得ます

2017-08-23
2017-08-27
2017-08-21
2017-08-29
2017-08-25
2017-08-17
2017-08-07
(etc.)

次に、各行に一意のID(連続した整数)を追加します。

awk 'END { for (i=0;i<100;++i) { printf("2017-08-01 + %d days\n", 1+int(31*rand())) }}' /dev/null |
date -f - +'%Y-%m-%d' |
awk -vOFS=',' '{ print NR, $0 }'

これは君のためだ

1,2017-08-06
2,2017-08-17
3,2017-08-25
4,2017-08-28
5,2017-08-14
6,2017-08-15
7,2017-08-17
8,2017-08-10
9,2017-08-16
10,2017-08-08
(etc.)

これで終わりました。その過程で、私はシェルループがあるという事実を完全に忘れていました。不要であることがわかりました。

100必要な値に設定し、必要に応じて乱数ジェネレータを調整します。rand()0 <= 数値 < 1 になる浮動小数点値を返します。


明らかに、8月(31日の月)にランダムな日付が必要な場合は、date完全にバイパスできます。

awk 'END { for (i=1;i<=100;++i) { printf("%d,2017-08-%02d\n", i, 1+int(31*rand())) }}' /dev/null

BSDではなくGNUawkとMikeのawk()を使用すると、次のように直接正しい日付処理を実行することもできます。mawkawkawk

awk 'END { for (i=1;i<=100;++i) { printf("%d,%s\n", i, strftime("%Y-%m-%d", 1501545600 + int(2678400*rand()),1 )) }}' /dev/null

今、私たちは日付の代わりにUnixタイムスタンプを扱っています。 1501545600は、「2017年8月1日火曜日00:00:00 UTC」に対応し、31日間は2678400秒です。

答え2

# A "random" date between 2000-01-01 and 2025-12-28
# Only uses day 01 to 28 
rand_date() {
    printf "%4d-%02d-%02d" $((RANDOM%25+2000)) $((RANDOM%12+1)) $((RANDOM%28+1))
}

csv_data() {
    for ((i=1; i<="$1"; i++)); do printf "%d,%s\n" $i $(rand_date); done
}
$ time (csv_data 1000000 > data.csv)
real    7m26.683s
user    0m36.376s
sys 1m57.768s

Perlはより速いかもしれません。試してみます。

$ cat data.pl
#!/usr/bin/perl
$, = ",";
$\ = "\n";

sub rand_date {
    sprintf "%4d-%02d-%02d", int(rand(25))+2000, int(rand(12))+1, int(rand(28))+1;
}

sub csv_data {
    my $n = shift;
    for ($i = 1; $i <= $n; $i++) {
        print $i, rand_date();
    }
}

csv_data(1_000_000);
$ time (perl data.pl > data.csv)

real    0m0.881s
user    0m0.876s
sys 0m0.004s

うん、もっと早く...

関連情報