2016-09-30 12 views
1

50000個のファイルで構成されるかなり大きなベンチマークでコマンドラインツールを評価する必要がありました。
残念ながら、このツールは並列化されておらず、このサイズのベンチマークで順番に実行するには時間がかかりました。
gnu並列(またはgnuセマフォ)についての記事をいくつか読んでいますが、gnuセマフォによって生成された複数のバックグラウンドプロセスの結果をどのように組み合わせるかを示す良い例は見つかりませんでした。コマンドラインツールをgnuセマフォでbashスクリプトにラップして並列化する

アンラップされたツールは入力パラメータとして1つのファイルを必要とするため、ツールを複数回並列に実行することによって得られるすべての結果を収集する方法を見つけなければなりませんでした。
さらに、私はクラッシュの場合に結果を失いたくはありませんでした。
スクリプトがキャンセルされるたびに、以前に処理されたファイルは再処理しないでください。

バックグラウンドプロセスworkerに十分な作業が行われるように、以下のスクリプトは複数のファイルをworkerに一度に渡します。
bashスクリプトは、私のユースケースではうまく動作します。

誰かが同様の問題を抱えている場合は、このスクリプトをあなたと共有したいと思います。
worker関数を変更し、変数$JOBS$WPSIZEを変更して、スクリプトを別のユースケースに適合させることができます。

スクリプトをより効率的にする方法に関するフィードバックを私に提供できると大変うれしいです。

おかげでたくさん、 ジュリアン

並列にFIFOへの追加
#!/bin/bash 

# make variables available in function started by 
# gnu semaphore 
export FINALRES="result.log" 
export RESFIFO="/tmp/res.fifo" 
export FILFIFO="/tmp/fil.fifo" 
export FILELIST="/tmp/flist" 
export WPSIZE=5 
export JOBS=4 

PUTFPID="" 
WRITPID="" 

# find input files fo process 
find . -name "*.txt" > ${FILELIST} 

# setup fifos and files 
[ ! -e "${FINALRES}" ] && touch "${FINALRES}" 
[ ! -e "${RESFIFO}" ] && mkfifo "${RESFIFO}" 
[ ! -e "${FILFIFO}" ] && mkfifo "${FILFIFO}" 

FILES=$(diff ${FINALRES} ${FILELIST} | grep '>' | cut -d '>' -f2 | tr -d ' ') 
exec 4<> ${RESFIFO} 
exec 5<> ${FILFIFO} 

trap cleanup EXIT TERM 

function cleanup() { 
    # write results that have been obainted so far 
    echo "cleanup" 
    [ -n "${PUTFPID}" ] && (kill -9 ${PUTFPID} 2>&1) > /dev/null 
    [ -n "${WRITPID}" ] && (kill -9 ${WRITPID} 2>&1) > /dev/null 
    rm -f "${RESFIFO}" 
    rm -f "${FILFIFO}" 
    rm -f "${LOCKFILE}" 
} 

# this function takes always #WPSIZE (or less) files from the fifo 
function readf() { 
    local cnt=0 
    while read -r -t 2 line; do 
    echo "$line" 
    [ -z "${files}" ] && { files=${line}; let cnt=${cnt}+1; continue; } 
    let cnt=${cnt}+1 
    [ ${cnt} -eq ${WPSIZE} ] && break 
    done <& 5 
} 

# this function is called by gnu semaphore and executed in the background 
function worker() { 
    for fil in "${@}"; do 
    # do something ... 
    echo "result" > "${RESFIFO}" 
    done 
    exit 0 
} 

# this function is used (at the end) to write the comutation results to a file 
function writeresult() { 
    while read -r line; do 
    [ "${line}" = "quit" ] && break 
    echo "${line}" >> ${FINALRES} 
    done < ${RESFIFO} 
} 

# this simple helper puts all input files into a fifo 
function putf() { 
    for fil in $FILES; do 
    echo "${fil}" > "${FILFIFO}" 
    done 
} 

# make function worker known to gnu semaphore 
export -f worker 
# put file into fifo 
putf & 
PUTFPID=$! 
writeresult & 
WRITPID=$! 

while true; do 
    ARGS=$(readf) 
    [ -z "${ARGS}" ] && break 
    # used word spitting on purpose here (call worker with multiple params) 
    sem --bg --jobs "${JOBS}" worker ${ARGS} 
done 

sem --wait 

echo "quit" > ${RESFIFO} 
wait 

echo "all jobs are finished" 
exit 0 
+0

ご覧ください:http://www.shellcheck.net/ – Cyrus

+0

ありがとう、私は 'sem --bg --jobs '$という単語の分割を除いて、spellcheck.net健全性チェックに従ってスクリプトを変更しました{仕事}私が意図的にやった{求人} "労働者$ {ARGS}" – Julian

+0

'>" $ {FINALRES} "をループの外側に置くことで、それぞれの結果を個別に検索して追加する必要がなくなります。 –

答えて

2

は一般的に悪い考えです:あなたは本当にそのためにOSバッファFIFOのこのバージョンは安全である方法について多くのことを知っておく必要があります。この例では、なぜ示していますsize=10

#!/bin/bash 

size=3000 

myfifo=/tmp/myfifo$$ 
mkfifo $myfifo 

printone() { 
    a=$(perl -e 'print ((shift)x'$size')' $1) 
    # Print a single string 
    echo $a >> $myfifo 
} 
printone a & 
printone b & 
printone c & 
printone d & 

# Wait a little to get the printones started 
sleep .1 

cat $myfifo | perl -ne 'for(split//,$_){ 
    if($_ eq $l) { 
    $c++ 
    } else { 
    /\n/ and next; 
    print $l,1+$c," "; $l=$_; $c=0; 
    } 
}' 
echo 

をいつでも取得します:

1 a10 b10 c10 

FIFOから10のCさんに続いて10件のBさんに続いての10のを読まれたことを意味しています。私。ミキシングなし。

しかしsize=100000に変更して、あなたのような何かを得る:そして、その後、65Kのbの、100KのC、その後の、34K D'sの場合、32Kのbの、100Kの、そして最後に208 D'sの

1 d65536 b65536 c100000 d34256 b34256 a100000 d208 

65K D'sの読み取りを。私。 4つのアウトプットが混在していた。非常に悪い。

そのため、同じFIFOに並行して追加することについてアドバイスします。競合状態の危険性があり、しばしばこれを避けることができます。

あなたのケースでは、あなたが単純に50000個の各ファイルへ# do something ...にしたいようで、それが死んで簡単です:

do_something() { 
    # do something ... 
    echo do something to $1 
    echo result of $1 is foo 
} 
export -f do_something 
find . -name "*.txt" | parallel do_something > results 

ここでGNUパラレルは確かstdoutとstderrは、それぞれに混ざらないことによって、あなたを助けます仕事の

クラッシュ/キャンセルの際の再処理を避けるには、--joblog--resumeを使用してください。

+0

ありがとうございます - スレッドセーフにするために上記のスクリプトにロックを追加します。 – Julian

+0

これを行う前に、GNU Parallelのチュートリアルを試してみてください。 man parallel_tutorial –

+0

ありがとうございます。私はあなたの提案とgnuの並列ドキュメントに従ってスクリプトを変更しました。私はhttps://gist.github.com/julianthome/161e6734c36611fcf03c91c9f76ebd5aで利用可能にしました – Julian

関連する問題