web-dev-qa-db-ja.com

この用途のためにGNU並列を最適化する方法は?

GNU parallelを使用/テストすることを唯一の目的として、退屈からこのスクリプトを作成したので、特に有用または最適化されていないことはわかっていますが、nまでのすべての素数を計算するスクリプトがあります:

_#!/usr/bin/env bash

isprime () {
    local n=$1
    ((n==1)) && return 1
    for ((i=2;i<n;i++)); do
        if ((n%i==0)); then
            return 1
        fi
    done
    printf '%d\n' "$n"
}

for ((f=1;f<=$1;f++)); do
    isprime "$f"
done
_

ループで実行する場合:

_$ time ./script.sh 5000 >/dev/null

real    0m28.875s
user    0m38.818s
sys     0m29.628s
_

ForループをGNU parallelに置き換えると、これが大幅に高速化されると思いますが、それは私の経験ではありません。平均して、約1秒速くなります。

_#!/usr/bin/env bash

isprime () {
    local n=$1
    ((n==1)) && return 1
    for ((i=2;i<n;i++)); do
        if ((n%i==0)); then
            return 1
        fi
    done
    printf '%d\n' "$n"
}

export -f isprime

seq 1 $1 | parallel -j 20 -N 1 isprime {}
_

並列で実行:

_$ time ./script.sh 5000 >/dev/null

real    0m27.655s
user    0m38.145s
sys     0m28.774s
_

isprime()関数の最適化にはあまり興味がありません。GNU並列を最適化するために何かできることがあるかどうか疑問に思っていますか?

私のテストでは、seqは実際にはfor ((i=1...))よりも高速に実行されるため、ランタイムとはあまり関係がないと思います。


興味深いことに、forループを次のように変更すると次のようになります。

_for ((f=1;f<=$1;f++)); do
    isprime "$f" &
done | sort -n
_

それはさらに速く実行されます:

_$ time ./script.sh 5000 >/dev/null

real    0m5.995s
user    0m33.229s
sys     0m6.382s
_
2
jesse_b

GNU Parallelは、ジョブごとに2〜10ミリ秒のオーバーヘッドを費やします。 -uを使用することで少し下げることができますが、それは異なるジョブからの出力が混在する可能性があることを意味します。

ジョブがmsの範囲にあり、実行時間が重要な場合、GNUParallelは理想的ではありません。オーバーヘッドが大きすぎることがよくあります。

複数のGNU Parallels:を実行することで、オーバーヘッドを複数のコアに分散できます。

seq 5000 | parallel --pipe --round-robin -N100 parallel isprime

あなたはまだオーバーヘッドを支払います、しかし今あなたは少なくとも支払うべきより多くのコアを持っています。

より良い方法は、isprimeを変更して、複数の入力を必要とし、実行に時間がかかるようにすることです。

isprime() {
  _isprime () {
      local n=$1
      ((n==1)) && return 1
      for ((i=2;i<n;i++)); do
          if ((n%i==0)); then
              return 1
          fi
      done
      printf '%d\n' "$n"
  }
  for t in "$@"; do
    _isprime $t
  done
}
export -f isprime

seq 5000 | parallel -X isprime
# If you do not care about order, this is faster because higher numbers always take more time
seq 5000 | parallel --shuf -X isprime

1
Ole Tange

最適化については触れませんis_prime(n)のsqurare_rootを繰り返します。

並列バージョンでは、プロセスの開始にかなりの時間を費やしていると思います。したがって、それをより大きなチャンクに分割します。例えばn/Number_of_cpusが最速である必要があります(各チャンクに同じ時間がかかる場合)。いくつかのチャンクサイズを試して、何が起こるかを確認してください。

スクリプトを調整して、値を下げてインクリメントする必要があります。

例えば並列実行を手配します(5コアの場合)。

./script    0 1000 &
./script 1000 1000 &
./script 2000 1000 &
./script 3000 1000 &
./script 4000 1000 &
1
ctrl-alt-delor

メインのforループを変更するには:

_for ((f=1;f<=$1;f+=2)); do
    isprime $f &
    isprime $((f+1))
done
_

少し速く実行されます

_]# time ./jj.sh 5000 |wc
    669     669    3148

real    0m2.537s
user    0m8.109s
sys     0m1.374s
_

_&_がない場合より:

_real    0m5.758s
user    0m5.761s
sys     0m0.007s
_

またはバックグラウンド呼び出しのみで_&_:

_real    0m3.298s
user    0m10.743s
sys     0m1.869s
_

アンパサンドで28秒から5秒になりましたが、私は5秒から3秒になりました。

アンパサンドありで2つ、アンパサンドなしで1つも試しましたが、これはすでに遅くなっています。


_]# time ./jj.sh 5000 |wc
^C

real    0m17.668s
user    0m17.576s
sys     0m1.344s
_

アンパサンドが2回目の呼び出しでのみ発生する場合は、劇的に遅くなります(^ Cを参照)。

_for ((f=1;f<=$1;f+=2)); do
    isprime $f
    isprime $((f+1)) &
done
_

これは少し紛らわしいようです。


見つかった素数を除数としてのみ使用することで、係数20でスピードアップできます。

_max=5000
max2=75
primes=('3')
echo '2'; echo '3'

for ((n=5; n<max; n+=2))
do  size=${#primes[@]}
    for ((pi=0; pi<=$size; pi++))
    do  p=${primes[$pi]}
        if (( $n % $p == 0 ))
        then break
        fi
        if (( $p * $p > $n ))
        then echo $n
             (( $n < $max2 )) && primes+=("$n")
             break
        fi
    done
done
_

これは与える:

_]# time . prim.sh |wc
    669     669    3148

real    0m0.126s
user    0m0.142s
sys     0m0.001s
_

そして、Perlでも同じことが言えます。

_]# time Perl prim.pl | wc
    668    1336    6486

real    0m0.008s
user    0m0.009s
sys     0m0.001s
_

(最初の行は_*** 3_のように見えるので、wcの出力は正常です)

ただし、このアルゴリズムを並列化するのはより困難です。isprime()は、素数の(増大する)リスト(最大sqrt)にアクセスできる必要があります。

たぶんfactor(セクション6と戦うコマンド:)は標準の機能ユニットとして役立つでしょう。次に、さまざまな「チャンク」でフィードできます。

_]# time seq 2 5000 |factor |sed '/ .* /d' |cut -f1 -d':' |wc 
    669     669    3148

real    0m0.008s
user    0m0.014s
sys     0m0.005s
_

sedは、複数のスペース(つまり、複数の要素)を持つ行を削除します。

しかし、繰り返しになりますが、速すぎて助けられません。

_]# time seq 900000000002 900000005000 | factor  |wc
   4999   26848  163457

real    0m0.031s
user    0m0.035s
sys     0m0.003s
_
0
rastafile