Particularizarea fișierului text multicore

Am o mașină cu patru nuclee și aș vrea să scriu un cod pentru a analiza un fișier text care să profite de toate cele patru nuclee. Fișierul text conține, în principiu, o înregistrare pe linie.

Multithreading nu este forta mea, asa ca ma intreb daca cineva ar putea sa-mi dea cateva modele pe care eu le-as putea folosi pentru a analiza fisierul intr-o maniera optima.

Primele mele gânduri sunt de a citi toate liniile într-un fel de coadă și apoi de a transforma fire pentru a trage linia de pe coadă și a le procesa, dar asta înseamnă că coada ar trebui să existe în memorie și acestea sunt fișiere mari, nu mă simt atât de dornic de ideea asta.

Următoarele gânduri sunt de a avea un fel de controler care să citească într-o linie și să-i atribuie un fir de analizat, dar nu sunt sigur dacă controlerul va deveni un obstacol în cazul în care firele procesează liniile mai repede decât poate citiți și le atribuiți.

Știu că este probabil o soluție mai simplă decât amândouă, dar în acest moment nu văd nimic.

0
fr hi bn

7 răspunsuri

Experienta mea este cu Java, nu C#, asa ca apologia daca aceste solutii nu se aplica.

Soluția imediată pe care o pot gândi în capul meu ar fi să am un executor care execută 3 fire (folosind Executorii .newFixedThreadPool , să zicem). Pentru fiecare linie / înregistrare citit din fișierul de intrare, declanșați o lucrare la executor (folosind ExecutorService .submit ). Executorul va coada cererile pentru tine, și va aloca între cele 3 fire.

Probabil există soluții mai bune, dar, sperăm, aceasta va face treaba. :-)

ETA: Sună foarte mult ca a doua soluție a lui Wolfbyte. :-)

ETA2: System.Threading.ThreadPool sună ca o idee foarte asemănătoare în .NET. Nu am folosit-o niciodată, dar ar putea fi în valoare de dvs.!

0
adăugat

Acest lucru va elimina blocajele de a avea un singur fir face citirea:

open file
for each thread n=0,1,2,3:
    seek to file offset 1/n*filesize
    scan to next complete line
    process all lines in your part of the file
0
adăugat

Întrucât, în general, procesul de blocare va fi în procesul de procesare și nu în lectură atunci când se ocupă de fișiere, aș merge cu producător -consumator model. Pentru a evita blocarea, m-aș uita la listele libere. Din moment ce utilizați C# puteți să aruncați o privire la codul Lista fără restricții a lui Julian Bucknall .

0
adăugat

M-aș duce cu ideea originală. Dacă sunteți îngrijorat de faptul că coada s-ar putea obține prea mare să pună în aplicare o zonă-tampon pentru aceasta (de exemplu, dacă se ajunge peste 100 de linii opritorul citirea fișierului și dacă acesta devine sub 20, apoi începe să citiți din nou. Ai nevoie de a face unele teste pentru a găsi barierele optime). Fă-l astfel încât oricare dintre firele pot fi potențial „firul cititorului“, deoarece trebuie să blocheze coada pentru a trage un element oricum poate, de asemenea, verifica pentru a vedea dacă „regiune tampon scăzută“, a fost lovit și începe citirea din nou. In timp ce face acest lucru celelalte fire se pot citi restul cozii.

Sau, dacă preferați, aveți un fir de cititor să alocați liniile altor trei fire procesor (prin propriile lor cozi) și să implementați o strategie de furt de muncă . N-am făcut niciodată așa, așa că nu știu cât de greu este.

0
adăugat

Răspunsul lui Mark este soluția mai simplă și mai elegantă. De ce să construiți un program complex cu comunicare între fire dacă nu este necesar? Creste 4 fire. Fiecare fir calculează dimensiunea fișierului / 4 pentru a determina punctul de pornire (și punctul de oprire). Fiecare fir poate funcționa complet independent.

Doar motivul pentru a adăuga un fir special care să se ocupe de citire este dacă vă așteptați ca unele linii să dureze foarte mult timp pentru a procesa și că vă așteptați ca aceste linii să fie grupate într- parte a dosarului. Adăugarea comunicării între fire atunci când nu aveți nevoie de ea este o idee foarte rea . Creșteți foarte mult șansa de a introduce o eroare neașteptată și / sau de sincronizare.

0
adăugat

@lomaxx

@Derek & Mark: I wish there was a way to accept 2 answers. I'm going to have to end up going with Wolfbyte's solution because if I split the file into n sections there is the potential for a thread to come across a batch of "slow" transactions, however if I was processing a file where each process was guaranteed to require an equal amount of processing then I really like your solution of just splitting the file into chunks and assigning each chunk to a thread and being done with it.

Fără griji. Dacă tranzacțiile "lente" grupate reprezintă o problemă, atunci soluția de așteptare este calea de urmat. În funcție de cât de repede sau de încet este tranzacția medie, s-ar putea să doriți, de asemenea, să vă uitați la atribuirea mai multor rânduri la un moment dat fiecărui lucrător. Acest lucru se va reduce la nivelul sincronizării. De asemenea, ar putea fi necesar să optimizați mărimea tamponului. Desigur, ambele sunt optimizări pe care probabil ar trebui să le faceți numai după profilare. (Nu are rost să vă faceți griji cu privire la sincronizare dacă nu este o strangulare.)

0
adăugat

Dacă textul pe care îl parcurgeți este alcătuit din șiruri repetate și jetoane, rupeți fișierul în bucăți și pentru fiecare bucată ați putea avea un fir de pre-parsare în token-uri constând din cuvinte cheie, "punctuație", șiruri de caractere și valori. String-ul compară și căutările pot fi destul de scumpe și trecerea acestui lucru la mai multe fire de lucru poate accelera partea pur logică / semantică a codului dacă nu are nevoie să facă căutări și comparații.

Fragmentele de date pre-analizate (unde ați făcut deja toate comparațiile de șir și le-ați "tokenizat") pot fi apoi trimise părții din cod care ar privi efectiv la semantică și ordonarea datelor tokenizate.

De asemenea, menționați că sunteți preocupat de dimensiunea fișierului dvs. ocupând o cantitate mare de memorie. Există câteva lucruri pe care le puteți face pentru a reduce bugetul de memorie.

Împărțiți fișierul în bucăți și analizați-l. Citiți doar cât mai multe bucăți pe măsură ce lucrați la un moment dat plus câteva pentru "citiți înainte", astfel încât să nu staționați pe disc atunci când terminați procesarea unei bucăți înainte de a merge la următoarea bucată.

Alternativ, fișierele mari pot fi mapate în memorie și încărcate "cererea". Dacă aveți mai multe fire care lucrează la procesarea fișierului decât procesoarele (de obicei firele = procesoarele 1.5-2X sunt un număr bun pentru aplicațiile de paginare a cererii), firele care se blochează pe IO pentru fișierul mapat cu memorie se vor opri automat din sistemul de operare până când memoria este gata și celelalte fire vor continua să fie procesate.

0
adăugat