Saya memiliki file teks (70GB), satu baris , dan saya ingin mengganti string (token) di dalamnya. Saya ingin mengganti token <unk>
, dengan token dummy lainnya ( masalah sarung tangan ).
Saya mencoba sed
:
sed 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
tetapi file output corpus.txt.new
memiliki nol-byte!
Saya juga mencoba menggunakan perl:
perl -pe 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
tapi saya mendapat kesalahan memori.
Untuk file yang lebih kecil, kedua perintah di atas berfungsi.
Bagaimana saya bisa mengganti string adalah file seperti itu? Ini adalah pertanyaan terkait, tetapi tidak ada jawaban yang cocok untuk saya.
Sunting : Bagaimana dengan memisahkan file dalam potongan 10GB (atau apa pun) masing-masing dan menerapkannya sed
masing-masing dan kemudian menggabungkannya cat
? Apakah itu masuk akal? Apakah ada solusi yang lebih elegan?
text-processing
sed
large-files
Christos Baziotis
sumber
sumber
split
dengan-b
opsi mendefinisikan ukuran file potongan dalam byte. Proses masing-masing pada gilirannya menggunakansed
dan merakit kembali. Ada risiko adalah bahwa<unk>
dapat dipecah menjadi dua file dan tidak akan ditemukan ...Jawaban:
Alat pemrosesan teks yang biasa tidak dirancang untuk menangani garis yang tidak sesuai dengan RAM. Mereka cenderung bekerja dengan membaca satu rekaman (satu baris), memanipulasinya, dan mengeluarkan hasilnya, kemudian melanjutkan ke rekaman berikutnya (baris).
Jika ada karakter ASCII yang sering muncul dalam file dan tidak muncul di
<unk>
atau<raw_unk>
, maka Anda dapat menggunakannya sebagai pemisah rekaman. Karena sebagian besar alat tidak memungkinkan pemisah rekaman khusus, tukar antara karakter itu dan baris baru.tr
memproses byte, bukan garis, sehingga tidak peduli tentang ukuran rekaman apa pun. Andaikan itu;
bekerja:Anda juga bisa berlabuh pada karakter pertama dari teks yang Anda cari, dengan asumsi itu tidak diulang dalam teks pencarian dan itu muncul cukup sering. Jika file dapat dimulai dengan
unk>
, ubah perintah sedsed '2,$ s/…
untuk menghindari kecocokan palsu.Atau, gunakan karakter terakhir.
Perhatikan bahwa teknik ini mengasumsikan bahwa sed beroperasi dengan mulus pada file yang tidak berakhir dengan baris baru, yaitu bahwa ia memproses baris parsial terakhir tanpa memotongnya dan tanpa menambahkan baris baru akhir. Ini bekerja dengan sed GNU. Jika Anda dapat memilih karakter terakhir dari file sebagai pemisah rekaman, Anda akan menghindari masalah portabilitas.
sumber
awk -v RS=, -v ORS=, '{gsub(/<unk>/, "<raw_unk>"); print}'
Tidak?-0
dan nilai oktal dari char, atau di dalam skrip dapat diatur dengan variabel khusus$/
awk
menghindari melewati aliran dua kali untuktr
. Jadi apakah masih lebih lambat?tr
sangat cepat dan pipa bahkan dapat diparalelkan.Untuk file sebesar itu, satu kemungkinan adalah Flex. Biarkan
unk.l
:Kemudian kompilasi dan jalankan:
sumber
make
memiliki aturan default untuk ini, alih-alih flex / cc Anda dapat menambahkan%option main
sebagai baris pertama unk.l dan kemudian hanyamake unk
. Saya lebih atau kurang secara refleks menggunakan%option main 8bit fast
, dan memilikiexport CFLAGS='-march=native -pipe -Os'
di saya.bashrc
.%option main
+make
+ opsionalCFLAGS
adalah trik yang sangat bagus !! Apakah-march=native
perilaku default?Jadi Anda tidak memiliki cukup memori fisik (RAM) untuk menampung seluruh file sekaligus, tetapi pada sistem 64-bit Anda memiliki ruang alamat virtual yang cukup untuk memetakan seluruh file. Pemetaan virtual dapat berguna sebagai peretasan sederhana dalam kasus seperti ini.
Semua operasi yang diperlukan termasuk dalam Python. Ada beberapa seluk yang menjengkelkan, tetapi tidak menghindari menulis kode C. Secara khusus, perawatan diperlukan untuk menghindari menyalin file dalam memori, yang akan mengalahkan sepenuhnya. Di sisi positifnya, Anda mendapatkan pelaporan kesalahan secara gratis (python "exception") :).
sumber
search
dapat berisi karakter NUL. Dan saya perhatikan versi C lainnya di sini tidak mendukung karakter NULreplace
.). Anda dipersilakan untuk menurunkan versi C untuk tujuan perbandingan. Namun ingat bahwa versi saya mencakup pelaporan kesalahan dasar untuk operasi yang dilakukan. Versi C setidaknya akan lebih mengganggu untuk membaca IMO, ketika pelaporan kesalahan disertakan.Ada
replace
utilitas dalam paket mariadb-server / mysql-server. Ini menggantikan string sederhana (bukan ekspresi reguler) dan tidak seperti grep / sed / awkreplace
tidak peduli\n
dan\0
. Konsumsi memori konstan dengan file input apa pun (sekitar 400kb pada komputer saya).Tentu saja Anda tidak perlu menjalankan server mysql untuk menggunakannya
replace
, hanya dikemas seperti itu di Fedora. Distro / sistem operasi lain mungkin mengemasnya secara terpisah.sumber
Saya pikir versi C mungkin berkinerja lebih baik:
EDIT: Dimodifikasi sesuai dengan saran dari komentar. Juga memperbaiki bug dengan polanya
<<unk>
.sumber
memcpy
kecepatan (yaitu kemacetan memori) adalah sesuatu seperti 12GB / detik pada CPU x86 baru-baru ini (misalnya Skylake). Bahkan dengan stdio + system call overhead, untuk file 30MB panas di cache disk, saya berharap mungkin 1GB / detik untuk implementasi yang efisien. Apakah Anda mengompilasi dengan optimasi yang dinonaktifkan, atau apakah I / O pada satu waktu sangat lambat?getchar_unlocked
Sayaputchar_unlocked
mungkin bisa membantu, tapi jelas lebih baik membaca / menulis dalam ukuran 128kiB (setengah dari ukuran cache L2 pada sebagian besar CPU x86, jadi Anda kebanyakan menekan L2 sambil mengulang setelah membaca)fix
ke program untuk"<<unk>"
masih tidak bekerja jikapattern
dimulai dengan urutan berulang karakter (yakni tidak akan bekerja jika Anda mencoba untuk mengganti aardvark dengan zebra dan Anda memiliki masukan dari aaardvak, atau Anda mencoba untuk mengganti ababc dan punya masukan abababc). Secara umum Anda tidak dapat bergerak maju dengan jumlah karakter yang telah Anda baca kecuali Anda tahu bahwa tidak ada kemungkinan kecocokan dimulai pada karakter yang telah Anda baca.GNU
grep
dapat menunjukkan offset pada file "biner", tanpa harus membaca seluruh baris ke dalam memori. Anda kemudian dapat menggunakandd
untuk membaca hingga offset ini, melewati pertandingan, lalu melanjutkan menyalin dari file.Untuk kecepatan, saya membaginya
dd
menjadi pembacaan besar dari blocksize 1048576 dan pembacaan yang lebih kecil dari 1 byte pada suatu waktu, tetapi operasi ini masih akan sedikit lambat pada file sebesar itu. Thegrep
output, misalnya,13977:<unk>
dan ini dibagi pada usus besar dengan membaca ke dalam variabeloffset
danpattern
. Kita harus melacakpos
berapa banyak byte yang telah disalin dari file.sumber
Berikut ini adalah baris perintah UNIX lain yang mungkin berkinerja lebih baik daripada opsi lain, karena Anda dapat "berburu" untuk "ukuran blok" yang berkinerja baik. Agar ini kuat, Anda perlu tahu bahwa Anda memiliki setidaknya satu ruang di setiap karakter X, di mana X adalah "ukuran blok" sewenang-wenang Anda. Dalam contoh di bawah ini saya telah memilih "ukuran blok" 1024 karakter.
Di sini, lipat akan ambil sampai dengan 1024 byte, tetapi -s akan memastikan istirahat pada ruang jika ada setidaknya satu sejak terakhir istirahat.
Perintah sed adalah milik Anda dan melakukan apa yang Anda harapkan.
Kemudian perintah tr akan "membuka" file yang mengonversi baris baru yang dimasukkan kembali menjadi kosong.
Anda harus mempertimbangkan mencoba ukuran blok yang lebih besar untuk melihat apakah kinerjanya lebih cepat. Alih-alih 1024, Anda dapat mencoba 10240 dan 102400 dan 1048576 untuk opsi -w lipat.
Berikut adalah contoh yang diuraikan oleh setiap langkah yang mengubah semua N menjadi huruf kecil:
Anda perlu menambahkan baris baru ke bagian paling akhir file jika ada, karena perintah tr akan menghapusnya.
sumber
Menggunakan
perl
Mengelola buffer Anda sendiri
Anda dapat menggunakan
IO::Handle
'ssetvbuf
untuk mengelola buffer default, atau Anda dapat mengelola buffer Anda sendiri dengansysread
dansyswrite
. Periksaperldoc -f sysread
danperldoc -f syswrite
untuk informasi lebih lanjut, pada dasarnya mereka melewatkan buffer io.Di sini kita menggulung buffer IO kita sendiri, tetapi kita melakukannya secara manual dan sewenang-wenang pada 1024 byte. Kami juga membuka file untuk RW sehingga kami melakukan semuanya pada FH yang sama sekaligus.
Jika Anda akan pergi dengan rute ini
<unk>
dan<raw_unk>
ukuran byte yang sama.CHUNKSIZE
batas, jika Anda mengganti lebih dari 1 byte.sumber
<unk>
jatuh pada batas antara potongan?Anda dapat mencoba bbe ( editor blok biner ), sebuah "
sed
untuk file biner".Saya sukses menggunakannya pada file teks 7GB tanpa
EOL
karakter, menggantikan beberapa kemunculan string dengan panjang yang berbeda. Tanpa mencoba optimasi apa pun, ia memberikan throughput pemrosesan rata-rata> 50MB / s.sumber
Dengan
perl
, Anda dapat bekerja dengan catatan panjang tetap seperti:Dan berharap tidak akan ada
<unk>
yang menjangkau dua dari 100MB rekaman itu.sumber
while read -N 1000 chunk;
(1000
memilih sebagai contoh). Solusi untuk<unk>
, dipecah antara potongan-potongan, adalah dua melewati file: yang pertama dengan potongan 100MB dan yang kedua dengan potongan '100MB + 5 byte'. Tapi itu bukan solusi optimal dalam hal file 70GB.<unk>
.<unk>
kejadiannya sangat jauh, jika tidak, gunakan$/ = ">"
dans/<unk>\z/<raw_unk>/g
) benar.Berikut adalah program Go kecil yang melakukan tugas (
unk.go
):Cukup buat
go build unk.go
dan jalankan sebagai./unk <input >output
.SUNTING:
Maaf, saya tidak membaca bahwa semuanya dalam satu baris, jadi saya mencoba membaca file karakter demi karakter sekarang.
EDIT II:
Perbaikan yang sama diterapkan untuk program C.
sumber
scanner.Split(bufio.ScanRunes)
melakukan keajaiban.go doc bufio.MaxScanTokenSize
ukuran buffer default.C
program Anda , ini tidak berfungsi untuk mengganti aardvark dengan zebra dengan input aaardvark.Ini mungkin berlebihan untuk file 70GB dan pencarian & penggantian sederhana, tetapi kerangka kerja Hadoop MapReduce akan menyelesaikan masalah Anda sekarang tanpa biaya (pilih opsi 'Node Tunggal' saat mengaturnya untuk menjalankannya secara lokal) - dan akan dapat diskalakan hingga kapasitas tak terbatas di masa depan tanpa perlu mengubah kode Anda.
Tutorial resmi di https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html menggunakan (sangat sederhana) Java tetapi Anda dapat menemukan perpustakaan klien untuk Perl atau bahasa apa pun yang Anda suka gunakan.
Jadi jika nanti Anda menemukan bahwa Anda melakukan operasi yang lebih kompleks pada file teks 7000GB - dan harus melakukan ini 100 kali per hari - Anda dapat mendistribusikan beban kerja di beberapa node yang Anda berikan atau yang secara otomatis disediakan untuk Anda oleh cloud- berdasarkan cluster Hadoop.
sumber
Semua saran sebelumnya mengharuskan membaca seluruh file dan menulis seluruh file. Ini tidak hanya membutuhkan waktu lama tetapi juga membutuhkan ruang kosong 70GB.
1) Jika saya memahami Anda dengan benar, apakah dapat mengganti dengan string lain dengan panjang SAMA?
2a) Apakah ada beberapa kejadian? 2b) Jika ya, Anda tahu berapa banyak?
Saya yakin Anda telah menyelesaikan masalah tahun-plus ini dan saya ingin tahu solusi apa yang Anda gunakan.
Saya akan mengusulkan solusi (kemungkinan besar dalam C) yang akan membaca BLOCKS dari file mencari masing-masing untuk string dengan mempertimbangkan kemungkinan blok silang. Setelah ditemukan ganti string dengan panjang SAMA alternatif dan tulis hanya BLOCK itu. Melanjutkan untuk jumlah kejadian yang diketahui atau sampai akhir file. Ini akan membutuhkan sesedikit jumlah kejadian menulis dan paling banyak dua kali lipat (jika setiap kejadian dibagi antara 2 blok). Ini TIDAK memerlukan ruang tambahan!
sumber
Jika kami memiliki jumlah minimum
<unk>
(seperti yang diharapkan oleh hukum Zipf),sumber
sed
Membaca baris sekaligus ke memori terlepas. Itu tidak akan dapat sesuai dengan garis ini.sed
tidak akan melakukan buffer input / output saat menggunakan flag ini. Saya tidak dapat melihat bahwa ia akan membaca sebagian baris.