대용량 파일을 추가/추가하는 빠른 방법 [닫기]

대용량 파일을 추가/추가하는 빠른 방법 [닫기]

상당히 큰 파일을 한 줄씩 읽고 각 줄에서 일부 처리를 수행하고 결과를 다른 파일에 쓰는 bash 스크립트가 있습니다. 현재 내가 사용하고 있는echo결과 파일의 끝에 추가되지만 파일 크기가 커질수록 속도가 점점 느려집니다. 그래서 내 질문은 대용량 파일에 줄을 추가하는 빠른 방법은 무엇입니까?

파일에 줄이 추가되는 순서는 나와 관련이 없으므로 다음에 추가하겠습니다.시작또는아니면 뭐든지무작위의파일의 위치. RAM이 많은 서버에서 스크립트를 실행하고 있기 때문에 결과를 변수에 저장하고 마지막에 전체 내용을 작성하면 더 빨라질 것입니다. 이 역시 저에게는 잘 작동합니다.

실제로 2개의 스크립트가 있으며 여기에 각각의 예를 넣었습니다. (실제 스크립트의 일부이지만 단순성을 위해 일부 부분을 제거했습니다.

while read line
do
    projectName=`echo $line | cut -d' ' -f1`
    filepath=`echo $line | cut -d' ' -f2`
    numbers=`echo $line | cut -d' ' -f3`
    linestart=`echo $numbers | cut -d: -f2`
    length=`echo $numbers | cut -d: -f3`
    lang=`echo $line | cut -d' ' -f9`
    cloneID=`echo $line | cut -d' ' -f10`
    cloneSubID=`echo $line | cut -d' ' -f11`
    minToken=`echo $line | cut -d' ' -f12`
    stride=`echo $line | cut -d' ' -f13`
    similarity=`echo $line | cut -d' ' -f14`
    currentLine=$linestart
    endLine=$((linestart + length))
    while [ $currentLine -lt $endLine ];
    do
        echo "$projectName, $filepath, $lang, $linestart, $currentLine, $cloneID, $cloneSubID, $minToken, $stride, $similarity"
        currentLine=$((currentLine + 1))
    done
done < $filename

위의 코드를 사용하는 방법은 다음과 같습니다../script filename > outputfile

두 번째 스크립트는 다음과 같습니다.

while read -r line;
do
    echo "$line" | grep -q FILE
    if [ $? = 0 ];
    then
        if [[ $line = *"$pattern"* ]];
        then
            line2=`echo "${line//$pattern1/$sub1}" | sed "s#^[^$sub1]*##"`
            newFilePath=`echo "${line2//$pattern2/$sub2}"`
            projectName=`echo $newFilePath | sed 's#/.*##'`
            localProjectPath=`echo $newFilePath | sed 's#^[^/]*##' | sed 's#/##'`
            cloneID=$cloneCounter
            revisedFile="revised-$postClusterFile-$projectName"
            overallRevisedFile="$cluster_dir/revised-overall-post-cluster"
            echo $projectName $localProjectPath $lang $cloneID $cloneSubID $minToken $stride $similarity >> $overallRevisedFile
            cloneSubID=$((cloneSubID + 1))
        fi
    fi
done < $cluster_dir/$postClusterFile

두 번째 코드의 사용법은 다음과 같습니다../script input output


고쳐 쓰다

글쎄, 분명히 범인은 백틱의 광범위한 사용입니다. 첫 번째 스크립트는 대폭 수정되어 이전 50분에 비해 이제 2분 만에 실행됩니다. 나는 그것에 매우 만족합니다. 다음 코드를 제공한 @BinaryZebra에게 감사드립니다.

while read -r projectName filepath numbers a a a a a lang cloneID cloneSubID minToken stride similarity;
do
    IFS=':' read -r a linestart length <<<"$numbers"
    currentLine=$linestart
    endLine=$((linestart + length))

    while [ $currentLine -lt $endLine ]; do
        echo "$projectName, $filepath, $lang, $linestart, $currentLine, $cloneID, $cloneSubID, $minToken, $stride, $similarity"
        currentLine=$((currentLine + 1))
    done
done < $filename >>$outputfile

그러나 두 번째 스크립트의 경우 다음과 같이 수정했습니다(여기에 실제 스크립트도 더 많이 추가했습니다).

while read -r line;
do
  echo "$line" | grep -q FILE
  if [ $? = 0 ];
  then
    if [[ $line = *"$pattern"* ]];
    then
      IFS=$'\t' read -r a a filetest  <<< "$line"
      filetest="${filetest#*$pattern1}"
      projectName="${filetest%%/*}"
      localProjectPath="${filetest#*/}"
      cloneID=$cloneCounter
      revisedFile="revised-$postClusterFile-$projectName"
      echo $projectName $localProjectPath $lang $cloneID $cloneSubID $minToken $stride $similarity
      cloneSubID=$((cloneSubID + 1))
    fi
  else
    echo "This is a line: $line" | grep -q \n
    if [ $? = 0 ];
    then
       cloneCounter=$((cloneCounter + 1))
       cloneSubID=0
    fi
  fi
done < $cluster_dir/$postClusterFile >> $overallRevisedFile

이전보다 훨씬 빨라졌습니다(7분 대 20분). 하지만 더 빨라야 하고 더 큰 테스트에서는 여전히 속도가 느려지는 것을 느낍니다. 약 24시간 동안 실행되었으며 현재 출력 크기는 200MB에 가깝습니다. 출력 파일이 3GB 정도 될 것으로 예상하므로 2주가 걸릴 수도 있지만 그럴 여유가 없습니다. 출력의 크기/증가도 비선형적이므로 시간이 지남에 따라 속도가 느려집니다.

내가 할 수 있는 다른 일이 있나요, 아니면 그것뿐인가요?

답변1

몇 가지 아이디어:
1.- 각 줄에서 반복적으로 cut을 호출하는 대신 읽기를 활용합니다.
잘라낼 변수 목록은 다음 ' '과 같습니다.

projectName 1
filepath 2
numbers 3
lang 9
cloneID 10
cloneSubID 11
minToken 12
stride 13
similarity 14

이 작업은 다음을 읽어서 직접 수행할 수 있습니다.

while read -r projectName filepath numbers a a a a a lang cloneID cloneSubID minToken stride similarity;

생산 라인은 길지만 처리 시간은 더 짧습니다. 변수 a는 사용되지 않은 값의 공간을 채우기 위해 존재합니다.

2.- ':'으로 나눌 변수 번호를 재처리하는 작업은 다음과 같이 수행할 수 있습니다(귀하의 질문에는 bash 태그가 지정됨).

IFS=':' read -r a linestart length <<<"$numbers"

이는 코드를 다음과 같이 단순화합니다.

while read -r projectName filepath numbers a a a a a lang cloneID cloneSubID minToken stride similarity;
do
    IFS=':' read -r a linestart length <<<"$numbers"

    currentLine=$linestart
    endLine=$((linestart + length))

    while [ $currentLine -lt $endLine ]; do
        echo "$projectName, $filepath, $lang, $linestart, $currentLine, $cloneID, $cloneSubID, $minToken, $stride, $similarity"
        currentLine=$((currentLine + 1))
    done
done < $filename >>$outputfile

3.- 두 번째 스크립트의 경우 sub1 및/또는 sub2 변수가 무엇인지에 대한 설명이 없습니다.

4.- 일반적으로 말해서, 스크립트를 일련의 작은 스크립트로 분할할 수 있다면 각 스크립트의 시간을 측정하여 시간이 소요되는 영역을 찾을 수 있습니다.

5.- 그리고 다른 답변에서 알 수 있듯이 파일(및 모든 중간 결과)을 메모리 파티션에 배치하면 첫 번째 파일을 더 빠르게 읽을 수 있습니다. 이후에 스크립트를 실행하면 메모리 내 캐시에서 읽어 개선 사항이 숨겨집니다.이 가이드도움이 될 것입니다.

답변2

메모리 상주 파일 시스템인 /dev/shm에 파일을 넣어 보셨나요? 파일 읽기 및 쓰기에 대한 액세스 속도가 향상됩니다. 마지막으로 shm에서 영구 디스크 파티션으로 파일을 복사할 수 있습니다.

답변3

하나여기서 문제는 다음과 같이 한다는 것입니다.

while : loop
do    : processing
      echo "$results" >>output
done  <input

이로 인해 각 반복의 실행 시간이 극적으로 증가합니다.outputopen()지난번보다 약간 더 큰 오프셋으로 **ed를 반복합니다. 나는 말했다꼼꼼하게있기 때문에거의이전 오프셋에서 파일을 여는 데 걸리는 시간과 이후 오프셋에서 파일을 여는 데 걸리는 시간에는 차이가 없습니다.일부. 그리고 매번 당신은open() O_APPEND지난번에 ypu가 있던 위치보다 약간 더 먼 위치에서 이 작업을 수행하고 있습니다. 소요되는 시간은 디스크 구성/기본 파일 시스템에 따라 다르지만일부발생당 비용은 파일 크기가 증가함에 따라 어느 정도 증가합니다.

아마도 다음 중 하나만 수행해야 할 것입니다.open()그리고 유지하다write()루프 수명 주기에 대한 설명입니다. 다음과 같이 할 수도 있습니다:

while : loop
do    : processing
      echo "$results"
done  <input >>output

이것이 주된 이유는 아닐 수도 있습니다. 나에게는 이것이 가장 분명한 이유이며 아마도 반복 증가와 직접적으로 관련되어 있을 것입니다. 그러나 루프에서는 발생해서는 안되는 많은 일이 일어나고 있습니다. 루프 반복당 10회 이상의 하위 쉘 데이터 평가를 수행해서는 안 됩니다. 모범 사례는 아무것도 수행하지 않는 것입니다. 일반적으로 분기 없이 처음부터 끝까지 완전히 실행될 수 있도록 독립형 쉘 루프를 효율적으로 구축할 수 없다면 전혀 수행해서는 안 됩니다.

대신 여기와 저기를 슬라이싱하여 평가를 관리할 수 있는 도구를 사용하여 평가에 집중해야 합니다.TV 시리즈- 이것이 잘 작성된 파이프라인이 작동하는 방식입니다. 모든 루프 반복에서 많은 무한 루프를 잡는 대신입니다. 다음과 같이 생각해 보십시오.

input |
(Single app single loop) |
(Single app single loop) |
(Single app single loop) |
output

이는 각 단일 루프가 이전 루프와 동시에 실행되는 파이프라인입니다.

그러나 당신은 오히려 다음을 원합니다:

input |
(Single app \
        (input slice|single app single loop);
        (input slice|single app single loop);
        (input slice|single app single loop);
 single loop) |
 output

이것이 서브쉘에 의존하는 쉘 루프가 작동하는 방식입니다. 어쨌든 이것은 효율적이지 않으며 입력과 출력이 버퍼링되지 않는 것도 도움이 되지 않습니다.

서브쉘은 악한 것이 아닙니다. 평가 컨텍스트를 포함하는 편리한 방법입니다. 그러나 더 나은 준비나 조건부 입력 또는 출력을 위해 필요하므로 모든 종류의 루프 전후에 적용하는 것이 거의 항상 가장 좋습니다.보다 효율적인 사이클에 적합. 이러한 작업을 반복적으로 수행하는 대신 시간을 들여 먼저 올바르게 설정한 다음 시작한 후에는 다른 작업을 수행하지 마십시오.

답변4

  • 큰 파일은 작은 파일보다 약간 느리게 처리될 수 있습니다. 이는 단지 데이터가 더 많기 때문만은 아닙니다. 만약 파일 두번째파일 크기의 1000배 , 전체 처리 시간이 1001배 또는 1002배 더 길어질 수 있습니다.
  • 반복할 때마다 출력 파일을 다시 열고 끝을 찾으면 약간의 성능 저하가 발생합니다. 두 번째 스크립트를 변경해 보세요.

    -r 줄을 읽을 때
    하다
                echo "$projectName $localProjectPath … $stride $similarity"
    완료<"$cluster_dir/$postClusterFile">>"$overallRevised파일"

    기존 파일에 콘텐츠를 추가하지 않는 경우 해당 줄에 (대신) $overallRevisedFile만 입력하면 됩니다 .> "$overallRevisedFile">>done

    하지만 큰 영향을 미칠 것 같지는 않습니다.

  • 전체 루프의 표준 출력을 리디렉션하지 않으려면 다음과 같이 할 수 있습니다.

    -r 줄을 읽을 때
    하다
                echo "$projectName $localProjectPath … $stride $similarity">&3
    완료<"$cluster_dir/$postClusterFile"  3>>"$overallRevisedFile"

    여러 루프에서 출력 파일에 액세스해야 하는 경우 다음을 수행하십시오.

    3>>"$overallRevisedFile" 실행
    -r 줄을 읽을 때
    하다
                echo "$projectName $localProjectPath … $stride $similarity">&3
    완료<"$cluster_dir/$postClusterFile"
    (다른 코드) >&3
    실행 3>&-
  • 스크립트를 더 좋게 만들 수 있지만 반드시 더 빠르지는 않은 몇 가지 사항이 있습니다.

    • 특별한 이유가 없는 한 항상 쉘 변수 참조(예: , 및 )를 인용 "$line"해야 합니다 "$cluster_dir"."$postClusterFile""$overallRevisedFile"틀림없이당신은 당신이 무엇을하고 있는지 알고 있습니다.
    • $(command)거의 동일하며 더 읽기 쉬운 것으로 널리 간주됩니다.`command`
    • (적어도) echo필요하지 않은 것이 하나 있습니다.

      newFilePath=`echo "${line2//$pattern2/$sub2}"`
      

      다음과 같이 단순화될 수 있다

      newFilePath="${line2//$pattern2/$sub2}"
      

관련 정보