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
_
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
最適化については触れません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 &
メインの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
_