![Perl 명령 성능 문제](https://linux55.com/image/14501/Perl%20%EB%AA%85%EB%A0%B9%20%EC%84%B1%EB%8A%A5%20%EB%AC%B8%EC%A0%9C.png)
다음 명령을 사용하여 숫자 열(.CSV 파일)의 특수 문자를 제거하고 있는데 제대로 작동하고 있지만 여기서 문제는 성능입니다. 내 CSV 파일 번호 열 데이터에는 다음이 포함됩니다. 데이터에서 1000개의 구분 기호 쉼표를 제거하기 위해 다음 Perl 명령을 사용했습니다.
payment
"4,326.34"
590.20
"12,499.40"
참고: 내 파일 구분 기호는 "," 쉼표입니다.
input file :
Organization,Amount,Revenue,Balance,Desc
Congos,"4,233.78","3,233.78","1,233.78",Payment
Toyoto,590.2,390.2,190.2,Payment
lenives,"5,234.89","2,234.89","1,234.89",Payment
Excepted OutPut:
Organization,Amount,Revenue,Balance,Desc
Congos,4233.78,3233.78,1233.78,Payment
Toyoto,590.2,390.2,190.2,Payment
lenives,5234.89,2234.89,1234.89,Payment
주문하다: cat | perl -p -e 's/,(?=[\d,.]\d")//g and s/"(\d[\d,.])"/\1/g' 'test.csv' >> newfile.csv
파일 데이터 수:1,100만 개의 데이터
질문:데이터에서 1,000개의 개별 "쉼표"를 제거하는 데 거의 10분이 걸렸습니다.
성능을 향상시킬 수 있는 더 나은 솔루션이 있습니까?
답변1
샘플 입력을 파일에 저장한 다음 데이터 행을 수백만 번 복사하여 약 1,100만 개의 레코드를 포함했습니다.
$ wc -l input2.csv
11100445 input2.csv
$ ls -l input2.csv
-rw-r--r-- 1 cas cas 505070243 Apr 10 22:32 input2.csv
빠르고 더러운 한 줄짜리:
$ time perl -pe 's/"([0-9-.]+),?([0-9-.]+)"/$1$2/g' input2.csv >output.csv
real 0m37.922s user 0m36.371s sys 0m1.347s
"
이렇게 하면 숫자 필드에서 쉼표와 따옴표( ) 문자가 제거됩니다. 안타깝게도 이 기능은 쉼표가 하나만 있는 숫자 필드(예: 아래 숫자)에서만 작동합니다 1,000,000
. 숫자 필드의 여러 쉼표와 일치하는 정규식을 찾는 것이 가치가 있다고 생각하지 않고 대신 csv 구문 분석 라이브러리를 사용합니다.
Text::CSV 모듈을 사용하는 것은 느리지만 더 "정확"합니다.
이 버전은 각 필드를 검사하여 숫자인지 확인하고 그렇다면 모든 쉼표를 제거하는 CSV 구문 분석 Perl 모듈을 사용합니다. 이는 모든 값의 숫자 필드에 적용됩니다.
숫자 필드 주위의 따옴표 문자를 제거할 필요가 없습니다. Text::CSV는 필드 따옴표를 자동으로 처리합니다.
#!/usr/bin/perl
use Text::CSV;
my $filename = shift;
my $csv = Text::CSV->new;
open my $fh, $filename or die "$filename: $!";
while (my $row = $csv->getline ($fh)) {
my @row = map { tr/,//d if ( m/^[0-9,.-]+$/); $_ } @{ $row };
$csv->print(*STDOUT, \@row);
print "\n";
}
close($fh);
예를 들어 다른 이름으로 저장 strip.pl
하고 실행 가능하게 만듭니다 chmod +x strip.pl
.
$ time ./strip.pl input2.csv > output2.csv
real 1m32.379s user 1m30.422s sys 0m1.609s
이것은 아마도 많이 최적화될 수 있을 것입니다. 더 빠르게 만들기 위해 어떤 노력도 하지 않았습니다. 1,100만 개의 레코드를 처리하고 각 레코드의 5개 필드를 확인/수정하는 데 1.5분도 나쁘지 않은 것 같습니다.
$ ls -l input2.csv output.csv output2.csv
-rw-r--r-- 1 cas cas 505070243 Apr 10 22:32 input2.csv
-rw-r--r-- 1 cas cas 430142246 Apr 10 22:40 output2.csv
-rw-r--r-- 1 cas cas 430142246 Apr 10 22:37 output.csv
$ cmp output.csv output2.csv
$ echo $?
0
두 출력 파일 모두 동일하므로 CSV 파일에 >= 100만 값의 숫자 필드가 없는 것 같습니다. 빠르고 더러운 버전이 작동합니다.
성능 문제
귀하의 Perl 코드 한 줄은 내 시스템(threadripper 1950x, 64GB RAM, zfs 미러 쌍의 2개의 NVME SSD*)에서 약 15초 동안 실행되지만 아무 작업도 수행하지 않습니다. 출력은 입력과 동일합니다.
귀하의 성능 문제는 다음 중 하나 또는 조합으로 인해 발생하는 것 같습니다.
- 느린 CPU
- 하드 드라이브가 느립니다.
- 가장 중요한 것은 메모리 부족입니다.
즉, 적어도 시스템을 크게 업그레이드하기 전까지는 알고리즘을 최적화하여 성능을 향상시킬 수 없을 것입니다. 작동하지 않는다는 사실을 무시한다면 코드 한 줄이 내 코드보다 거의 3배 빠르고 Text::CSV 버전보다 약 7배 빠릅니다.
한 가지는가능한성능을 크게 향상시키는 방법(느린 디스크 I/O가 문제의 주요 원인인 경우)은 CSV 파일을 압축한 다음 gzip
사용 zcat
가능한 단일 코드 줄로 변환하는 것입니다. 10MB 이하로 압축될 수 있습니다 -읽다10MB 미만의 디스크에 있는 데이터는 디스크보다 약 50배 빠릅니다.읽다500MB 이상. xz
그리고 xzcat
비슷한 압축 프로그램도 더 좋진 않더라도 마찬가지로 잘 작동할 것입니다.
이는 전체 프로세스가 50배 더 빠르게 실행된다는 의미가 아니라 파일을 훨씬 더 빠르게 읽을 수 있다는 의미입니다. Perl은 여전히 각 라인을 개별적으로 처리해야 합니다.
* 그런데 NVME는 빠르지만 zfs는 약간 느립니다(ext4 또는 xfs와 같은 단순한 파일 시스템에 비해). OTOH lz4 압축을 활성화하면 파일 읽기 속도가 다시 빨라집니다.
답변2
사용csvkit
:
$ in2csv file.csv
Organization,Amount,Revenue,Balance,Desc
Congos,4233.78,3233.78,1233.78,Payment
Toyoto,590.2,390.2,190.2,Payment
lenives,5234.89,2234.89,1234.89,Payment
이 in2csv
유틸리티는 다양한 표 형식의 데이터 형식을 CSV로 변환합니다. 완전히 유효한 CSV 파일을 변환하면 in2csv
해당 로캘의 숫자가 en_US
기본적으로 재해석되어 해당 숫자가 삭제됩니다 ,
.
이러한 csvkit
도구는 Python으로 구현된 CSV 파서를 기반으로 구축되었습니다.