sed 최적화(더 작은 데이터 세트를 기반으로 한 대용량 파일 수정)
-
21-08-2019 - |
문제
나는 매우 긴 줄이 포함된 매우 큰 일반 텍스트 파일(10GB 이상, 예, 크기가 무엇인지에 따라 다르다는 것을 알고 있습니다)을 처리해야 합니다.
가장 최근 작업에는 다른 파일의 데이터를 기반으로 한 줄 편집이 포함되었습니다.
(수정해야 하는) 데이터 파일에는 1500000줄이 포함되어 있으며 각 줄은 다음과 같습니다.길이는 800자입니다.각 줄은 고유하며 하나의 식별 번호만 포함하며 각 식별 번호는 고유합니다)
수정자 파일은 예를 들어 다음과 같습니다.1800줄 길이이며 데이터 파일에서 수정해야 하는 ID 번호와 금액 및 날짜가 포함되어 있습니다.
방금 수정자 파일을 (Vim 정규식을 사용하여) sed로 변환했지만 매우 비효율적입니다.
데이터 파일에 다음과 같은 줄이 있다고 가정해 보겠습니다.
(some 500 character)id_number(some 300 character)
그리고 300자 부분의 데이터를 수정해야 합니다.
수정자 파일을 기반으로 다음과 같은 sed 줄을 생각해 냈습니다.
/id_number/ s/^\(.\{650\}\).\{20\}/\1CHANGED_AMOUNT_AND_DATA/
그래서 이렇게 1800개의 줄이 생겼습니다.
하지만 매우 빠른 서버에서도 내가 다음 작업을 수행하면
sed -i.bak -f modifier.sed data.file
모든 패턴 x 모든 라인을 읽어야 하기 때문에 매우 느립니다.
더 좋은 방법은 없을까요?
메모: 저는 프로그래머가 아니며 학교에서 알고리즘에 대해 배운 적이 없습니다.서버에서는 오래된 Perl 버전인 awk, sed를 사용할 수 있습니다.
해결책
내가 제안하는 접근 방식(바람직한 순서)은 이 데이터를 다음과 같이 처리하는 것입니다.
- 데이터베이스(인덱스가 있는 간단한 SQLite 기반 DB라도 10GB 파일의 sed/awk보다 성능이 훨씬 뛰어남)
- 고정된 레코드 길이를 포함하는 플랫 파일
- 가변 레코드 길이를 포함하는 플랫 파일
데이터베이스를 사용하면 텍스트 파일 처리 속도를 늦추는 모든 작은 세부 사항(관심 있는 레코드 찾기, 데이터 수정, DB에 다시 저장)을 처리할 수 있습니다.Perl의 경우 DBD::SQLite를 살펴보세요.
플랫 파일을 계속 사용하려면 큰 파일과 함께 수동으로 색인을 유지 관리해야 조작해야 할 레코드 번호를 더 쉽게 찾을 수 있습니다.아니면 더 나은 방법은 아마도 귀하의 ID 번호일 것입니다. ~이다 당신의 기록적인 숫자는요?
가변 레코드 길이가 있는 경우 고정 레코드 길이로 변환하는 것이 좋습니다(귀하의 ID만 가변 길이로 표시되므로).그렇게 할 수 없다면 기존 데이터가 파일에서 이동하지 않을 수도 있습니다.그런 다음 이전에 언급한 색인을 유지하고 필요에 따라 새 항목을 추가할 수 있습니다. 차이점은 색인이 레코드 번호를 가리키는 대신 이제 파일의 절대 위치를 가리킨다는 것입니다.
다른 팁
나는 당신에게 perl로 작성된 프로그램을 제안합니다 (나는 sed/awk 전문가가 아니고 그들이 할 수있는 것이 무엇인지는 안 돼요).
"알고리즘"은 간단합니다. 우선 각 ID에 적용 할 새 데이터 문자열을 제공 할 수있는 해시 맵을 구성해야합니다. 이것은 물론 수정 자 파일을 읽는 것으로 달성됩니다.
이 hasmap에서 인구가 쌓이면 데이터 파일의 각 줄을 탐색하고 줄의 중간에 ID를 읽고 위에서 설명한대로 새 줄을 생성 할 수 있습니다.
나도 Perl Guru는 아니지만 프로그램이 매우 간단하다고 생각합니다. 글을 쓰는 데 도움이 필요하다면 :-)
Perl을 사용하면 특히 id_number의 너비가 일정한 경우 id_number를 얻으려면 substr을 사용해야합니다.
my $id_number=substr($str, 500, id_number_length);
그 후 $ id_number가 범위에 있으면 substr을 사용하여 나머지 텍스트를 대체해야합니다.
substr($str, -300,300, $new_text);
Perl의 정규 표현식은 매우 빠르지만이 경우에는 그렇지 않습니다.
내 제안은 데이터베이스를 사용하지 않는다는 것입니다. 잘 쓰여진 PERL 스크립트는 이러한 종류의 작업에서 크기로 데이터베이스를 능가합니다. 날 믿어, 나는 그것에 대한 많은 실용적인 경험이있다. Perl이 완료되면 데이터를 데이터베이스로 가져 오지 않았습니다.
800 숯으로 1500000 줄을 쓰면 1.2GB처럼 보입니다. 디스크 (30MB/s)가 매우 느리면 40 초 안에 읽습니다. 더 나은 50-> 24s, 100-> 12s 등. 그러나 2GHz CPU의 Perl Hash 조회 (DB 조인) 속도는 5Mlookups/s 이상입니다. 그것은 당신의 CPU 바운드 작업이 몇 초 안에 있고 당신은 IO 바운드 작업이 수십 초 안에 있음을 의미합니다. 실제로 10GB 숫자가 변경되지만 비율은 동일합니다.
데이터 수정 크기가 변경되는지 여부를 지정하지 않았습니다 (수정이 완료 될 수있는 경우) 따라서 우리는이를 가정하지 않고 필터로 작동합니다. "수정 자 파일"형식과 어떤 종류의 수정을 지정하지 않았습니다. 다음과 같은 것들에 의해 분리되었다고 가정합니다.
<id><tab><position_after_id><tab><amount><tab><data>
우리는 stdin의 데이터를 읽고 stdout에 쓸 것입니다. 스크립트는 다음과 같습니다.
my $modifier_filename = 'modifier_file.txt';
open my $mf, '<', $modifier_filename or die "Can't open '$modifier_filename': $!";
my %modifications;
while (<$mf>) {
chomp;
my ($id, $position, $amount, $data) = split /\t/;
$modifications{$id} = [$position, $amount, $data];
}
close $mf;
# make matching regexp (use quotemeta to prevent regexp meaningful characters)
my $id_regexp = join '|', map quotemeta, keys %modifications;
$id_regexp = qr/($id_regexp)/; # compile regexp
while (<>) {
next unless m/$id_regexp/;
next unless $modifications{$1};
my ($position, $amount, $data) = @{$modifications{$1}};
substr $_, $+[1] + $position, $amount, $data;
}
continue { print }
광산 노트북에서는 150 만 행, 1800 개의 조회 ID, 1.2GB 데이터에 대해 약 30 분이 걸립니다. 10GB의 경우 5 분이 넘지 않아야합니다. 당신에게 합리적입니까?
당신이 IO 바운드가 아니라고 생각하면 (예 : 일부 NAS를 사용하는 경우) CPU 묶음은 약간의 가독성과 변경을 희생 할 수 있습니다.
my $mod;
while (<>) {
next unless m/$id_regexp/;
$mod = $modifications{$1};
next unless $mod;
substr $_, $+[1] + $mod->[0], $mod->[1], $mod->[2];
}
continue { print }
거의 확실히 데이터베이스를 사용해야합니다 Mikeyb는 제안했다.
어떤 이유로 데이터베이스를 사용하지 않으려면, 수정 목록이 메모리에 맞는 경우 (현재 1800 줄에서) 가장 효율적인 방법은 제안한 수정으로 채워진 해시 가능입니다. Yves Baumes.
수정 목록조차도 큰 지점에 도달하면 ID로 두 파일을 모두 정렬 한 다음 수행해야합니다. 목록 병합 - 기본적으로 :
- 입력 파일의 "상단"에서 ID를 수정 파일의 "상단"에있는 ID와 비교합니다.
- 일치하는 경우 레코드를 그에 따라 조정하십시오
- 작성하십시오
- 파일에서 "상단"줄을 버리고 (알파벳순 또는 숫자) 가장 낮은 ID를 가진 사람이 해당 파일에서 다른 줄을 읽습니다.
- goto 1.
무대 뒤에서 데이터베이스는 단일 SQL을 사용 하여이 변경을 수행하는 경우 거의 확실히 목록 병합을 사용합니다. UPDATE
명령.
sqlloader 또는 datadump 결정에 대한 좋은 거래. 그게 갈 길입니다.