나는 GNU 병렬성을 사용/테스트할 목적으로 지루함에서 이 스크립트를 만들었으므로 이것이 특별히 유용하거나 최적화되지는 않는다는 것을 알고 있지만 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 병렬 처리로 대체하면 작업 속도가 훨씬 빨라질 것이라고 기대했지만, 내 경험은 그렇지 않았습니다. 평균적으로 약 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
답변1
GNU Parallel에는 작업당 2~10밀리초의 오버헤드가 있습니다. 를 사용하면 약간 낮아질 수 있지만 -u
이는 다른 작업의 출력을 혼합할 수 있음을 의미합니다.
작업이 밀리초 범위에 있고 런타임이 중요하다면 GNU Parallel은 이상적이지 않습니다. 일반적으로 오버헤드가 너무 큽니다.
여러 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
답변2
is_prime
(n)에 대한 sqrare_root의 반복 최적화에 대해서는 언급하지 않겠습니다 .
병렬 버전에서는 프로세스를 시작하는 데 많은 시간이 걸리는 것 같습니다. 그러니 더 큰 덩어리로 나누어 보세요. 예를 들어, n/Number_of_cpus는 가장 빠릅니다(각 블록이 동일한 시간을 소요하는 경우). 몇 가지 블록 크기를 시도해보고 어떤 일이 일어나는지 확인하십시오.
감소 및 증가하려면 스크립트를 조정해야 합니다.
예를 들어 병렬 실행을 예약합니다(코어가 5개인 경우).
./script 0 1000 &
./script 1000 1000 &
./script 2000 1000 &
./script 3000 1000 &
./script 4000 1000 &
답변3
기본 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
& 기호가 두 번째 호출에만 나타나면 작업 속도가 크게 느려집니다(^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