GNU를 사용한 병렬 라인 기반 출력, 임시 파일 필요 없음

GNU를 사용한 병렬 라인 기반 출력, 임시 파일 필요 없음

GNU 병렬 처리의 기본 출력 모드는 다음과 같습니다 --group.
각 작업의 출력은 임시 파일에 기록되고 parallel작업이 완료된 후에만 출력으로 전달됩니다.

/tmp이 기본 출력 모드는 공간 보다 큰 데이터와 함께 사용할 때
parallel lz4 -dc ::: /var/lib/apt/lists/*lz4 | wc 속도가 느리고 충돌이 발생합니다.
parallel: Error: Output is incomplete.
Cannot append to buffer file in /tmp.

이 모드를 사용하면 --ungroup라인이 중간에서 분할되어
parallel --ungroup lz4 -dc ::: /var/lib/apt/lists/*lz4 | wc
unparallelized와 다른 출력이 생성됩니다 lz4 -dmc /var/lib/apt/lists/*lz4 | wc.

parallel매뉴얼 페이지 에 따르면 이 문제는 내가 이해하는 옵션으로 해결되어야 합니다 --line-buffer. 모든 작업에는 병렬로 읽혀지는 출력 파이프가 있으며, 어떤 작업의 출력도 사용 가능하면 병렬 프로세스의 출력 파이프에 한 줄씩 전달됩니다. 그 자체. (편집: 라인당 하나의 시스템 호출이 아닌 병렬 프로세스에 걸쳐 많은 입력을 분산시키는 것과 같이 블록 단위의 라인을 의미합니다. 이는 너무 느립니다.)

그러나 이것은 작동하지 않습니다. 위에서 암시한
parallel --line-buffer lz4 -dc ::: /var/lib/apt/lists/*lz4 | wc -c
것과 --group동일한 디스크 가득 참 오류가 발생합니다.

parallel --line-buffer임시 파일 없이 어떻게 사용하나요?

시스템은 LUbuntu 20 LTS입니다. parallel -V반품 20161222. 하이퍼스레딩(4개 스레드)을 사용하는 듀얼 코어 i3-4130의 원시 직렬 및 병렬 압축 해제 성능 비교:

time ls -S /var/lib/apt/lists/*lz4 | parallel --ungroup lz4 -dc > /dev/null
1.461s
time lz4 -dmc /var/lib/apt/lists/*lz4 > /dev/null
3.069s

실제 사용 사례는 다음과 같습니다(솔루션 없음 --line-buffer).

time lz4 -dmc /var/lib/apt/lists/*Contents* | grep -F $'/parallel\t' | sort -u
usr/bin/parallel                                            universe/utils/moreutils,universe/utils/parallel
usr/bin/parallel                                            universe/utils/parallel
usr/lib/R/library/parallel/R/parallel                       universe/math/r-base-core
usr/lib/cups/backend/parallel                               net/cups-filters
usr/share/doc-base/parallel                                 universe/utils/parallel
real    0m5.349s
user    0m3.970s
sys     0m5.839s

time ls -S /var/lib/apt/lists/*Contents* | parallel lz4 -dc '{}' \| grep -F "\$'/parallel\t'" | sort -u
(same output as above)
real    0m3.669s
user    0m5.888s
sys     0m7.676s

이는 압축 해제뿐만 아니라 사후 처리도 병렬화하며 파이프라인의 첫 번째 부분이 99% 작동하지 않기 때문에 더 나은 솔루션입니다.
그러나 전체 파이프라인을 병렬화하는 이 방법은 항상 실현 가능한 것은 아니므로 첫 번째 단계의 출력이 그다지 작지 않고 스트리밍되어야 하는 경우에는 일반적인 문제가 남아 있습니다.

답변1

제안한 대로 수행하려면 lz4각 파이프의 출력을 별도의 파이프로 보내야 하며 모든 파이프에서 읽고 출력을 여러 줄로 분할하는 선택/폴링 루프가 필요하거나 프로세스/스레드가 매 파이프를 처리하도록 해야 합니다. 파이프.

이것은 엄청난 비용처럼 들립니다. 이러한 오버헤드가 없더라도 빠른 SSD가 장착된 12년 된 4코어 8스레드 노트북 printf '%s\0' /var/lib/apt/lists/*lz4 | xargs -r0 -n 1 -P8 lz4 -dc(GNU 병렬 오버헤드가 없더라도)에서는 lz4 -dmc /var/lib/apt/lists/*lz4.

이상적으로는 병렬 명령 출력 라인이 먼저 버퍼링되기를 원합니다. 이 작업을 수행하는 데 사용할 수 있는 방법이 많이 있습니다 stdbuf -oL.

그렇지 않은 것 같지만 다음과 같이 두 번째 접근 방식(하나의 출력에 대해 하나의 프로세스) 을 lz4수동으로 구현할 수 있습니다.lz4

printf '%s\0' /var/lib/apt/lists/*lz4 |
  stdbuf -oL xargs -r0 -n 1 -P4 sh -c 'lz4 -dc "$1" | paste' sh | 
  wc -c

( paste이것은 한 번에 한 줄씩 입력을 처리하는 명령이며 출력을 확실히 라인 버퍼링합니다 . GNU stdbuf도 참조하세요 . 한 번에 한 줄씩 출력하는 것을 피하고 한 번에 한 바이트씩 입력을 읽습니다.)grepgrep --line-buffered '^'sed -u

출력이 폐기되더라도 /dev/null내 시스템에서는 비병렬 시스템보다 13배 느립니다 lz4 -dmc /var/lib/apt/lists/*lz4(6.5초 대 0.5초).

이는 pasteC로 작성 되었습니다. GNU 병렬 처리는 에서 작성되었으며 perl내부적으로 이와 같은 기능을 지원한다면 훨씬 덜 효율적일 가능성이 높습니다.

병렬화(적어도 이런 방식)는 lz4쉬운 압축 해제와 달리 상대적으로 적은 텍스트 출력을 생성하는 CPU 집약적 작업에만 적합합니다.

답변2

버전 20170822의 릴리스 노트는 다음과 같습니다.

  • --line-buffer는 더 이상 임시 파일을 사용하지 않습니다. 이는 속도가 더 빠르며 단일 프로세스가 사용 가능한 디스크 공간보다 더 많은 데이터를 출력할 수 있게 해줍니다.

따라서 해결책은 20170822로 업그레이드하는 것입니다.

GNU Parallel은 여전히 ​​다른 목적으로 임시 파일을 사용하지만 라인 버퍼링에는 사용하지 않습니다.

대기열이 긴 경우에도 올바른 작업을 수행합니다.

#!/bin/bash

5gfile() {
    # Create file with 5GB long line
    perl -e '$a=(shift)x1000000;for(1..5000){print $a};print "\n"' $1 | lz4 > $1.lz4;
}
export -f 5gfile
parallel 5gfile ::: a b c d

echo The correct output: One line with a b c d
lz4 -dc {a..d}.lz4 | tr -s abcd

echo Output from parallel: One line with a b c d might be reordered
parallel --line-buffer lz4 -dc ::: {a..d}.lz4 |
    tr -s abcd

echo Output from xargs with stdbuf -oL
echo This does not handle long lines because stdbuf -oL does not guarantee only full lines will be written
printf '%s\0' /tmp/*lz4 |
    stdbuf -oL xargs -r0 -n 1 -P4 sh -c 'lz4 -dc "$1" | paste' sh |
    tr -s abcd 

관련 정보