변수의 내용을 읽는 것보다 파일을 여는 것이 더 빠른 이유는 무엇입니까?

변수의 내용을 읽는 것보다 파일을 여는 것이 더 빠른 이유는 무엇입니까?

bash스크립트 에는 /proc/파일의 다양한 값이 필요합니다. 지금까지 다음과 같이 직접 grep 파일이 수십 줄 있습니다.

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo

효율성을 위해 파일 내용을 변수와 grep에 저장합니다.

a=$(</proc/meminfo)
echo "$a" | grep -oP '^MemFree: *\K[0-9]+'

파일을 여러 번 여는 대신 한 번만 열고 변수 내용을 파악해야 합니다. 이것이 더 빠를 것이라고 생각했지만 실제로는 더 느립니다.

bash 4.4.19 $ time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null
real    0m0.803s
user    0m0.619s
sys     0m0.232s
bash 4.4.19 $ a=$(</proc/meminfo)
bash 4.4.19 $ time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null
real    0m1.182s
user    0m1.425s
sys     0m0.506s

dash및 에도 마찬가지입니다 zsh. 파일의 특수한 상태가 /proc/원인이 아닐까 의심했지만 내용을 /proc/meminfo일반 파일에 복사하여 사용해보니 다음과 같습니다.

bash 4.4.19 $ cat </proc/meminfo >meminfo
bash 4.4.19 $ time for i in $(seq 1 1000);do grep ^MemFree meminfo; done >/dev/null
real    0m0.790s
user    0m0.608s
sys     0m0.227s

여기에서 문자열을 사용하여 파이프를 저장하면 약간 더 빨라지지만 여전히 파일을 사용하는 것만큼 빠르지는 않습니다.

bash 4.4.19 $ time for i in $(seq 1 1000);do <<<"$a" grep ^MemFree; done >/dev/null
real    0m0.977s
user    0m0.758s
sys     0m0.268s

변수에서 동일한 내용을 읽는 것보다 파일을 여는 것이 더 빠른 이유는 무엇입니까?

답변1

여기 얘기가 아니야파일을 열다비교적변수의 내용 읽기그러나 추가 프로세스를 포크할지 여부가 더 중요합니다.

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfogrep열기(디스크 I/O가 포함되지 않은 메모리의 가상 파일)를 실행하는 프로세스를 포크하여 /proc/meminfo이를 읽고 정규식과 일치시킵니다.

이 중 가장 비용이 많이 드는 부분은 프로세스를 포크하고 grep 유틸리티와 해당 라이브러리 종속성을 로드하고, 동적 연결을 수행하고, 로케일 데이터베이스를 열고, 디스크에 있는 수십 개의 파일(그러나 아마도 메모리에 캐시되어 있음)입니다.

이에 비해 읽기에 관한 부분은 /proc/meminfo사소합니다. 커널은 정보를 생성하는 데 시간이 거의 걸리지 않으며 grep정보를 읽는 데도 시간이 거의 걸리지 않습니다.

strace -c이를 실행하면 읽기에 사용되는 하나 open()및 하나의 시스템 호출이 시작 시 수행되는 다른 작업(포크 계산 아님)과 비교할 때 아무것도 아니라는 것을 알 수 있습니다 .read()/proc/meminfogrepstrace -c

존재하다:

a=$(</proc/meminfo)

ksh 연산자를 지원하는 대부분의 쉘에서 $(<...)쉘은 단순히 파일을 열고 그 내용을 읽습니다(그리고 후행 줄 바꿈을 제거합니다). bash차이점은 읽기 작업을 수행하기 위해 프로세스를 분기하고 데이터를 상위 프로세스로 파이프하기 때문에 훨씬 덜 효율적이라는 것입니다. 하지만 여기서는 한 번 해봤으니 상관없습니다.

존재하다:

printf '%s\n' "$a" | grep '^MemFree'

쉘을 생성해야 합니다동시에 실행되지만 파이프를 통해 서로 상호 작용하는 프로세스입니다. 파이프를 만들고, 해체하고, 쓰고 읽는 데 드는 비용은 적습니다. 더 큰 비용은 추가 프로세스를 생성하는 것입니다. 프로세스 일정도 어느 정도 영향을 미칩니다.

zsh <<<연산자를 사용하면 약간 더 빨라질 수 있습니다.

grep '^MemFree' <<< "$a"

zsh 및 bash에서는 $a임시 파일에 내용을 기록하여 이를 수행합니다. 이는 추가 프로세스를 생성하는 것보다 저렴하지만 데이터를 직접 가져오는 것에 비해 아무런 이점도 제공하지 않을 수 있습니다 /proc/meminfo. 이는 /proc/meminfo반복할 때마다 임시 파일 쓰기가 수행되므로 디스크에 복사 방식보다 여전히 효율성이 낮습니다 .

dashHere-string은 지원되지 않지만 해당 heredoc은 추가 프로세스 생성을 포함하지 않는 파이프를 사용하여 구현됩니다. 존재하다:

 grep '^MemFree' << EOF
 $a
 EOF

쉘은 파이프를 생성하고 프로세스를 분기합니다. 하위 프로세스는 grep파이프의 읽기 끝으로 stdin을 실행하고, 상위 프로세스는 파이프의 다른 쪽 끝에 씁니다.

그러나 배관 및 프로세스 동기화는 데이터를 직접 가져오는 것보다 여전히 비용이 더 많이 들 수 있습니다 /proc/meminfo.

내용 /proc/meminfo도 짧고 제작기간도 길지 않습니다. 일부 CPU 주기를 절약하려면 프로세스를 분기하고 외부 명령을 실행하는 등 비용이 많이 드는 부분을 제거해야 합니다.

좋다:

IFS= read -rd '' meminfo < /proc/meminfo
memfree=${meminfo#*MemFree:}
memfree=${memfree%%$'\n'*}
memfree=${memfree#"${memfree%%[! ]*}"}

bash패턴 일치 효율성이 매우 낮은 상황을 피하세요 . 를 사용하면 zsh -o extendedglob다음과 같이 단축할 수 있습니다.

memfree=${${"$(</proc/meminfo)"##*MemFree: #}%%$'\n'*}

이것은 ^많은 쉘에서 특별하다는 점에 유의하십시오(Bourne, fish, rc, es 및 zsh에는 최소한 Extendedglob 옵션이 있음). 인용하는 것이 좋습니다. 또한 echo임의의 데이터를 출력하는 데 사용할 수 없으므로 printf위의 방법을 사용했습니다.

답변2

첫 번째 경우에는 grep 유틸리티를 사용하고 file 에서 내용을 찾습니다 /proc/meminfo. file 은 /proc가상 파일 시스템이므로 /proc/meminfo파일은 메모리에 있고 내용을 가져오는 데 시간이 거의 걸리지 않습니다.

그러나 두 번째 경우에는 파이프를 생성한 다음 해당 파이프를 사용하여 첫 번째 명령의 출력을 두 번째 명령에 전달하므로 비용이 많이 듭니다.

차이점은 /proc(메모리에 있기 때문에) 파이프입니다. 아래 예를 참조하세요.

time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null

real    0m0.914s
user    0m0.032s
sys     0m0.148s


cat /proc/meminfo > file
time for i in {1..1000};do grep ^MemFree file;done >/dev/null

real    0m0.938s
user    0m0.032s
sys     0m0.152s


time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null

real    0m1.016s
user    0m0.040s
sys     0m0.232s

답변3

당신은 전화 중입니다외부두 경우 모두 명령(grep)입니다. 외부 호출에는 하위 쉘이 필요합니다. 셸을 포크하는 것이 지연의 근본 원인입니다. 두 경우 모두 유사하므로 대기 시간도 비슷합니다.

외부 파일을 한 번만 읽고 변수에서 여러 번 사용하려면 셸을 종료하지 마세요.

meminfo=$(< /dev/meminfo)    
time for i in {1..1000};do 
    [[ $meminfo =~ MemFree:\ *([0-9]*)\ *.B ]] 
    printf '%s\n' "${BASH_REMATCH[1]}"
done

grep 호출에는 1초가 아닌 약 0.1초만 소요됩니다.

관련 정보