Bash hat içeriğine göre büyük metin dosyasının ayrılmasını hızlandırılması

oy
7

Sekmelerle ayrılmış üç sütun içeren, (20 GB ve 300 milyon hatları etrafında) çok büyük bir metin dosyası vardır:

word1 word2 word3
word1 word2 word3
word1 word2 word3
word1 word2 word3

kelime-1, kelime2 ve word3 her hat farklıdır. word3 hattının sınıfını belirler ve farklı hat (farklı değerler binlerce olan) için, genellikle tekrar eder. Hedef, satır sınıfı (word3) dosyayı ayırmaktır. Yani kelime1 ve kelime2 tüm hatları için, bir dosya adı verilen word3 saklanmalıdır. Örneğin, hat için:

a b c

dize a b c denilen dosyaya eklenmelidir.

Şimdi bu bir dosyanın satır satır okuyarak ve her hat için uygun dosyayı ekleyerek, while döngüsüne ile yapılabilir biliyorum:

while IFS='' read -r line || [[ -n $line ]]; do
    # Variables
    read -a line_array <<< ${line}
    word1=${line_array[0]}
    word2=${line_array[1]}
    word3=${line_array[2]}

    # Adding word1 and word2 to file word3
    echo ${word1} ${word2} >> ${word3}  
done < inputfile

O inşaat, ama (ben SSD ile hızlı bir iş istasyonu var olsa bile) çok yavaş. Bu nasıl hızlandırmak edilebilir? Zaten / dev / SHM bu prosedürü yürütmek için çalıştı ve 10 parçaya dosyayı parçaya ayrıldı ve her bir dosya için paralel yukarıdaki komut dosyasını çalıştırdığınız. Ama yine de oldukça yavaştır. ayrıca bu hızlandırmak için bir yolu var mı?

Oluştur 20/10/2018 saat 14:07
kaynak kullanıcı
Diğer dillerde...                            


5 cevaplar

oy
4

Bunu bir örnek dosyası oluşturalım:

$ seq -f "%.0f" 3000000 | awk -F $'\t' '{print $1 FS "Col_B" FS int(2000*rand())}' >file

Yani, bu sütun içindeki 3 2,000 farklı değerlere sahip bir 3 milyon satır dosya oluşturur:

$ head -n 3 file; echo "..."; tail -n 3 file
1   Col_B   1680
2   Col_B   788
3   Col_B   1566
...
2999998 Col_B   1562
2999999 Col_B   1803
3000000 Col_B   1252

Basit bir ile awksize bu şekilde tarif dosyaları oluşturabilir:

$ time awk -F $'\t' '{ print $1 " " $2 >> $3; close($3) }' file
real    3m31.011s
user    0m25.260s
sys     3m0.994s

Yani bu awk yaklaşık 3 dakika 31 saniye içinde 2000 grup dosyaları oluşturur. Kesinlikle Bash daha hızlı, ancak bu üçüncü sütuna göre dosya ön düzenlemesine ve tek seferde her grup dosyasını yazarak hızlı olabilir.

Sen Unix kullanabilirsiniz sortbir borudaki yarar ve farklı dosyalara sıralı gruplar ayırabilirsiniz bir senaryoya çıktı verir. Kullanım -sile seçeneği sorthatları sırasını değişecek tek alanları olacak ve üçüncü alanın değerini.

Varsayabiliriz yana sortdosyanın sütunda 3 dayanan gruba dosyayı parçalara ayırdığında, script sadece bu değer değişiklikleri saptamak gerekir:

$ time sort -s -k3 file | awk -F $'\t' 'fn != ($3 "") { close(fn); fn = $3 } { print $1 " " $2 > fn }'
real    0m4.727s
user    0m5.495s
sys     0m0.541s

Çünkü ön ayrım kazanılan verimlilik, aynı net işlem 5 saniye içinde tamamlanır.

Eğer 3. sütunda 'kelimeleri' sadece ASCII emin iseniz, ayarlayabilirsiniz (yani UTF-8 ile uğraşmak gerekmez) LC_ALL=Ciçin ek hız :

$ time LC_ALL=C sort -s -k3 file | awk -F $'\t' 'fn != ($3 "") { close(fn); fn = $3 } { print $1 " " $2 > fn }'
real    0m3.801s
user    0m3.796s
sys     0m0.479s

yorumlarla Gönderen:

1) biz parantez ifadesini de neden ihtiyaç açıklamak için bir satır ekleyinfn != ($3 "") :

awkYapı içinde fn != ($3 "") {action}etkin bir kısa yoldur fn != $3 || fn=="" {action}kullanım en okunabilir düşünün bir.

2) dosya kullanılabilir bellek büyükse bu da çalışır, bu nedenle bu sınırlayıcı bir faktör olabilir emin değilim. :

İlk ve 300 milyon kayıtları ve 20.000 çıktı dosyaları ile son awk koştu. tür sonuncusu 12 dakikada görev yaptı. İlk 10 saat sürdü ...

Bir çeşit versiyonu aslında dosya eklemeyi açarak ve 300 milyon kez bir süre alır 20.000 dosya kapanış beri iyi scale olabilir. Bu birlik olmak için daha etkilidir ve benzeri veri akışı.

3) Yaklaşık sıralama erken düşünme ama sonra bu yaklaşımla iki kez tüm dosyayı okumak zorunda çünkü en hızlı olmayabilir hissedildi. :

Bu tamamen rastgele veriler için geçerlidir; gerçek veri ise biraz sipariş iki kez dosya okunurken bir tercih söz konusudur. İlk awk önemli ölçüde daha hızlı daha az rastgele verilerle olurdu. Ama sonra o da dosya sıralanır olmadığını belirlemek için zaman alacaktır. Eğer varsa bilmek dosya çoğunlukla sıralanır, ilk kullanın; muhtemelen biraz düzensiz ise, son kullanın.

Cevap 20/10/2018 saat 19:12
kaynak kullanıcı

oy
3

Sen awk kullanabilirsiniz:

awk -F $'\t' '{ print $1 " " $2 >> $3; close($3) }' file
Cevap 20/10/2018 saat 14:17
kaynak kullanıcı

oy
2

Bu çözelti, GNU paralel kullanır, ancak diğer ayarlanmış olabilir awkçözümler. Ayrıca güzel bir ilerleme çubuğu vardır:

parallel -a data_file --bar 'read -a arr <<< {}; echo "${arr[0]} ${arr[1]}" >> ${arr[2]}'
Cevap 20/10/2018 saat 14:34
kaynak kullanıcı

oy
2

Kullanım awkörneğin:

awk -F '{ print $1 FS $2 > $3 }' FILES

Veya bu Perl komut (bana göre yazılmış) - bu biraz daha uzun olduğu gibi, burada Repost olmaz. awko (yeniden) her satır için dosyaları açar gibi biraz daha yavaş olmalıdır. 250'den fazla farklı değerlere sahip olduğunda bu Perl komut daha iyidir / çıkış dosyaları (veya OS eşzamanlı açık filehandles sayısı için sınır olarak sahip olursa olsun). Perl komut çok daha hızlı bellekte tüm giriş verilerini muhafazaya çalışır ancak büyük girişler için sorunlu olabilir.

Çıktı dosyaların büyük sayım için çözüm kullanıcı oguzismail tarafından gönderilmiştir:

awk '{ print $1 FS $2 >> $3; close($3) }' file

Bu (yeniden) her hat için çıkış dosyası açılır ve aynı anda açık çok fazla açık çıktı filehandles sahip sorunla yayınlanmaz. (Re) dosyasını açarak yavaş olabilir, ama almadığı bildirildi olabilir.

Düzenleme: Sabit awkçağırma - bunun yerine, ilk iki sütun, çıkış bütün çizgi basılmış.
Cevap 20/10/2018 saat 14:18
kaynak kullanıcı

oy
1

Sen doğada çok benzer sorunun inceler mümkün GNU paralel yoluyla birden fazla dosya için awk yazma parallelize mı?

Disk Eğer bunu kullanabilir:

splitter() {
  mkdir -p $1
  cd $1
  awk -F $'\t' '{ print $1 " " $2 >> $3; close($3) }'
}
export -f splitter
# Do the splitting in each dir 
parallel --pipepart -a myfile --block -1 splitter {%}
# Merge the results
parallel 'cd {}; ls' ::: dir-* | sort -u | parallel 'cat */{} > {}'
# Cleanup dirs
rm -r */
Cevap 20/10/2018 saat 16:53
kaynak kullanıcı

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more