Bash에서 계산을 수행하는 효율적인 방법

Bash에서 계산을 수행하는 효율적인 방법

숫자로 가득 찬 파일(1열)의 기하 평균을 계산하려고 합니다.

기하 평균의 기본 공식은 모든 값의 자연 로그(또는 로그)의 평균을 낸 다음 e(또는 밑수 10)를 해당 값으로 올리는 것입니다.

현재 bash 전용 스크립트는 다음과 같습니다.

# Geometric Mean
count=0;
total=0; 

for i in $( awk '{ print $1; }' input.txt )
  do
    if (( $(echo " "$i" > "0" " | bc -l) )); then
        total="$(echo " "$total" + l("$i") " | bc -l )"
        ((count++))
    else
      total="$total"
    fi
  done

Geometric_Mean="$( printf "%.2f" "$(echo "scale=3; e( "$total" / "$count" )" | bc -l )" )"
echo "$Geometric_Mean"

기본적으로:

  1. 입력 파일의 각 항목을 확인하여 모든 호출에서 bc가 0보다 큰지 확인하세요.
  2. 항목이 0보다 큰 경우 해당 값의 자연 로그(l)를 취하여 각 호출 bc의 누적 합계에 추가합니다.
  3. 항목 <= 0이면 아무것도 하지 않습니다.
  4. 기하 평균 계산

이는 작은 데이터 세트에 매우 효과적입니다. 안타깝게도 대규모 데이터 세트(input.txt의 값이 250,000개)에서 사용하려고 합니다. 이것이 결국 성공할 것이라고 확신하지만 매우 느립니다. 완료할 만큼 인내심이 부족했습니다(45분 이상).

이 파일을 보다 효율적으로 처리할 수 있는 방법이 필요합니다.

Python을 사용하는 것과 같은 다른 방법이 있습니다.

# Import the library you need for math
import numpy as np

# Open the file
# Load the lines into a list of float objects
# Close the file
infile = open('time_trial.txt', 'r')
x = [float(line) for line in infile.readlines()]
infile.close()

# Define a function called geo_mean
# Use numpy create a variable "a" with the ln of all the values
# Use numpy to EXP() the sum of all of a and divide it by the count of a
# Note ... this will break if you have values <=0
def geo_mean(x):
    a = np.log(x)
    return np.exp(a.sum()/len(a))

print("The Geometric Mean is: ", geo_mean(x))

Python, Ruby, Perl 등은 피하고 싶습니다.

Bash 스크립트를 보다 효율적으로 작성하는 방법에 대한 제안 사항이 있습니까?

답변1

쉘에서는 이 작업을 수행하지 마십시오. 아무리 조정해도 효율적으로 만들 수 없습니다. 쉘 루프는느린쉘을 사용하여 텍스트를 구문 분석하는 것은 나쁜 습관입니다. 전체 스크립트를 awk다음과 같은 간단한 한 줄로 대체할 수 있으며 이는 훨씬 더 빠릅니다.

awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++} END{m=tot/c; printf "%.2f\n", E^m}' file

예를 들어, 1에서 100 사이의 숫자가 포함된 파일에 대해 이 명령을 실행하면 다음과 같은 결과가 나타납니다.

$ seq 100 > file
$ awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++} END{m=tot/c; printf "%.2f\n", E^m}' file
37.99

속도 측면에서 저는 1부터 10000까지의 숫자가 포함된 파일에 대해 위에서 제공한 쉘 솔루션, Python 솔루션 및 awk를 테스트했습니다.

## Shell
$ time foo.sh
3677.54

real    1m0.720s
user    0m48.720s
sys     0m24.733s

### Python
$ time foo.py
The Geometric Mean is:  3680.827182220091

real    0m0.149s
user    0m0.121s
sys     0m0.027s


### Awk
$ time awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++} END{m=tot/c; printf "%.2f\n", E^m}' input.txt
3680.83

real    0m0.011s
user    0m0.010s
sys     0m0.001s

보시다시피 awkPython보다 빠르고 작성이 더 간단합니다. 원하는 경우 이를 "쉘" 스크립트로 만들 수도 있습니다. 다음과 같습니다.

#!/bin/awk -f

BEGIN{
    E = exp(1);
} 
$1>0{
    tot+=log($1);
    c++;
}
 
END{
    m=tot/c; printf "%.2f\n", E^m
}

또는 쉘 스크립트에 명령을 저장하십시오.

#!/bin/sh
awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++;} END{m=tot/c; printf "%.2f\n", E^m}' "$1"

답변2

몇 가지 제안이 있습니다. 귀하의 파일에 정확히 무엇이 있는지 알지 못하면 테스트할 수 없지만 이것이 도움이 되기를 바랍니다. 일을 수행하는 방법은 항상 다르며 더 나은 방법이 있으므로 이것이 전부는 아닙니다.


if 조건 변경

if (( $(echo " "$i" > "0" " | bc -l) )); then

다음으로 변경하세요.

if [[ "$i" -gt 0 ]]; then

첫 번째 줄은 단순한 계산을 수행하더라도 여러 프로세스를 생성합니다. 해결책은 [[shell 키워드를 사용하는 것입니다.


불필요한 코드 제거

else
  total="$total"

이것은 기본적으로 아무것도 하지 않고 시간을 낭비하는 명확한 방법입니다 :). 이 두 줄은 직접 삭제할 수 있습니다.

관련 정보