2009-03-03 2 views
12

La scorsa settimana ho scritto alcune righe di codice in C# per attivare un file di testo di grandi dimensioni (300.000 righe) in un dizionario. Ci sono voluti dieci minuti per scrivere ed è stato eseguito in meno di un secondo.Gestione memoria stringa C++

Ora sto convertendo quel pezzo di codice in C++ (perché ne ho bisogno in un vecchio oggetto COM C++). Ho passato due giorni su questo fin qui. :-(Anche se la differenza di produttività è scioccante da sola, sono le prestazioni che avrei bisogno di un consiglio.

Ci vogliono sette secondi per caricare, e ancora peggio: ci vuole solo esattamente quel tempo per liberare tutto CStringWs dopo. questo non è accettabile, e devo trovare un modo per aumentare le prestazioni.

ci sono delle possibilità che io possa allocare questo molte stringhe senza vedere questo degrado performace orribile?

la mia ipotesi in questo momento è che dovrò riempire tutto il testo in un grande array e poi lasciare che la mia tabella hash punti all'inizio di ogni stringa all'interno di questo array e rilasciare le cose di CStringW

Ma prima, qualche consiglio da parte degli esperti di C++?

MODIFICA: La mia risposta a me stesso è riportata di seguito. Mi sono reso conto che quella è la via più veloce per me e che passo anche a ciò che considero nella direzione giusta - verso un codice più gestito.

+0

Hai davvero bisogno di mostrarci il codice che stai usando ora, la tua descrizione è troppo vaga per dare input. –

+0

Devi assolutamente fornire maggiori informazioni sulla struttura dati ... –

+1

Prima di conoscere .Net, passare da C++ a C# - senza alcuna idea di come i framework (nel mio caso VCL) fossero simili o diversi - avrebbe è stato altrettanto impegnativo come il contrario. Non c'è bisogno di incolpare C++ per fare la tua domanda. – overslacked

risposta

11

State entrando nei panni di Raymond Chen. Ha fatto esattamente la stessa cosa, scrivendo un dizionario cinese in C++ non gestito. Lo ha fatto anche Rico Mariani, scrivendolo in C#. Il signor Mariani ha realizzato una versione. Il signor Chen ha scritto 6 versioni, cercando di eguagliare il perfetto della versione di Mariani. Ha praticamente riscritto parti significative della libreria di runtime C/C++ per arrivarci.

Il codice gestito ha ottenuto molto più rispetto dopo. L'allocatore di GC è impossibile da battere. Controllare il post this blog per i collegamenti. Questo blog post potrebbe interessarti anche tu, istruttivo per vedere come la semantica del valore STL faccia parte del problema.

+0

Bene, per citare Mariani "Quindi, sì, puoi sicuramente battere il CLR." ma sono d'accordo sul fatto che la performance CLR sia davvero impressionante. –

3

In che tipo di contenitore stai memorizzando le tue stringhe? Se si tratta di un std::vector di CStringW e se non si dispone già della memoria sufficiente per lo reserve, si è costretti a subire un colpo. Un valore vector viene ridimensionato in genere una volta raggiunto il limite (che non è molto alto) e quindi copia completamente la nuova posizione di memoria che può darti un grande successo. Come il tuo vector cresce in modo esponenziale (ad esempio se la dimensione iniziale è 1, la prossima volta che assegna 2, 4 la prossima volta in poi, il colpo diventa sempre meno frequente).

Aiuta anche a sapere quanto sono lunghe le singole stringhe. (A volte :)

+0

Array fisso ...stai allocando tutta la memoria in primo piano o ridimensionando l'array con ogni nuova stringa? –

+0

No no no. Non lo realizzo mai. È un hash table e nelle * rare * occations in cui ottengo una chiave hash duplicata sono state fatte alcune cose extra, ma durante il benchmarking ho buttato via i duplicati solo per assicurarmi che non ci fossero problemi di prestazioni. –

+0

Sembra che tu abbia sistemato tutto perfettamente. Hai provato con std :: wstring? Inoltre, è ora di prendere l'aiuto di un profiler. – dirkgently

10

Yikes. sbarazzarsi dei CStrings ...

provare un profiler pure. sei sicuro di non aver appena eseguito il codice di debug?

utilizzare invece std :: string.

EDIT:

Ho appena fatto un semplice test di ctor e dtor confronti.

CStringW sembra richiedere da 2 a 3 volte il tempo per eseguire una nuova/eliminazione.

iterato 1000000 volte facendo nuovo/cancella per ciascun tipo. Nient'altro - e una chiamata a GetTickCount() prima e dopo ogni ciclo. Costantemente ottenere il doppio del tempo per CStringW.

Questo non riguarda l'intero problema, anche se ho il sospetto.

MODIFICA: Inoltre, non penso che l'uso di stringhe o CStringW sia il vero problema - c'è qualcos'altro che sta causando il problema.

(ma per l'amor di Dio, utilizzare stl comunque!)

È necessario il profilo it. Questo è un disastro.

+0

Proverò std :: string, restate sintonizzati! :-) –

+0

CString non è così male ... – peterchen

+1

CString è male. Innanzitutto, come ho appena confermato, è più lento di std :: string. Non è portatile È un hack MFC - insieme a tutti gli altri junk MFC. Non fraintendetemi, li ho usati per anni, ma meno MFC usiamo, meglio è ... – Tim

1

Quando si lavora con classi di stringhe, si dovrebbe sempre dare un'occhiata alle operazioni non necessarie, ad esempio, non utilizzare costruttori, concatenazioni e operazioni simili troppo spesso, specialmente evitarli nei loop. Suppongo che ci sia un motivo di codifica dei caratteri che usi CStringW, quindi probabilmente non puoi usare qualcosa di diverso, questo sarebbe un altro modo per ottimizzare il tuo codice.

4

Se si tratta di un dizionario di sola lettura, il seguente dovrebbe funzionare per voi.

Use fseek/ftell functionality, to find the size of the text file. 

Allocate a chunk of memory of that size + 1 to hold it. 

fread the entire text file, into your memory chunk. 

Iterate though the chunk. 

    push_back into a vector<const char *> the starting address of each line. 

    search for the line terminator using strchr. 

    when you find it, deposit a NUL, which turns it into a string. 
    the next character is the start of the next line 

until you do not find a line terminator. 

Insert a final NUL character. 

È ora possibile utilizzare il vettore, per ottenere il puntatore, che vi permetterà di accedere il valore corrispondente.

Al termine del dizionario, rilasciare la memoria, lasciare che il vettore muoia quando si esce dal campo di applicazione.

[EDIT] Questo può essere un po 'più complicato sulla piattaforma DOS, in quanto il terminatore di riga è CRLF.

In tal caso, utilizzare strstr per trovarlo e incrementare di 2 per trovare l'inizio della riga successiva.

+0

Questo funzionerà alla grande ed è veloce. Non penso che sia necessario - sta facendo qualcosa di male nel suo codice che probabilmente può essere risolto e ha un codice leggibile e semplice. – Tim

+0

La cosa che lo sta uccidendo è il costo di costruzione/distruzione con allocazione/deallocazioni di memoria. – EvilTeach

+0

Oppure (se Win32 supporta l'equivalente) mmap il file MAP_PRIVATE (copia su scrittura), che salva la copia. –

12

Questo suona molto simile al Raymond Chen rispetto al C++ vs C# cinese/inglese del dizionario di Rico Mariani. Ci sono volute diverse sequenze di Raymond per battere C#.

Forse ci sono idee che potrebbero aiutare.

http://blogs.msdn.com/ricom/archive/2005/05/10/performance-quiz-6-chinese-english-dictionary-reader.aspx

+0

+1 per il link interessante – EvilTeach

+0

Sì, non sai quanto tu sia giusto! :-) Ma una cosa più difficile per me è che voglio leggere ASCII, UTF8 e Unicode in una volta sola, quindi devo eseguire una traduzione dai dati del file ai dati che immagazzino. –

+1

@danbystrom, ASCII può essere considerato come un sottoinsieme di UTF-8. UTF-8 è un formato di codifica Unicode. Se hai i tuoi file in UTF-8 puoi usare qualsiasi carattere definito in Unicode e il testo ASCII esistente non ha bisogno di ricodificare. – CMircea

2

Caricare la stringa in un singolo buffer, analizzare il testo per sostituire interruzioni di riga con terminatori di stringa ('\ 0') e utilizzare i puntatori nel buffer da aggiungere all'insieme.

In alternativa - ad es. se devi eseguire una conversione ANSI/UNICODE durante il caricamento - usa un allocatore di blocchi, che sacrifica l'eliminazione di singoli elementi.

class ChunkAlloc 
{ 
    std::vector<BYTE> m_data; 
    size_t m_fill; 
    public: 
    ChunkAlloc(size_t chunkSize) : m_data(size), m_fill(0) {} 
    void * Alloc(size_t size) 
    { 
     if (m_data.size() - m_fill < size) 
     { 
      // normally, you'd reserve a new chunk here 
      return 0; 
     } 
     void * result = &(m_data[m_fill]); 
     m_fill += size; 
     return m_fill; 
    } 
} 
// all allocations from chuunk are freed when chung is destroyed. 

Non sarebbe hack che insieme a una decina di minuti, ma 30 minuti e alcuni test suona bene :)

+0

Si si si. Questo è quello che ho capito anche durante la notte! :-) –

+0

:) Vedi Raymond vs Rico (http://blogs.msdn.com/ricom/archive/2005/05/10/416151.aspx) è molto simile al tuo progetto, con risultati chiari: con C++ puoi risolvi il tuo problema più velocemente di quanto l'app C# carichi, ma ti costa. Un sacco. – peterchen

3

Grazie a tutti voi per i vostri commenti penetranti. Upvotes per te! :-)

Devo ammettere che non ero preparato per questo - C# avrebbe battuto in questo modo il buon vecchio C++. Si prega di non leggerlo come un reato per C++, ma invece quale ottimo gestore di memoria che si trova all'interno di .NET Framework.

Ho deciso di fare un passo indietro e combattere questa battaglia nell'arena di InterOp, invece! Cioè, terrò il mio codice C# e lascerò che il mio vecchio codice C++ parli al codice C# su un'interfaccia COM.

Un sacco di domande sono stati invitati circa il mio codice e cercherò di rispondere ad alcune di loro:

  • Il compilatore è stato Visual Studio 2008 e no, non era in esecuzione una build di debug.

  • Il file è stato letto con un lettore di file UTF8 che ho scaricato da un dipendente Microsoft che lo ha pubblicato sul loro sito. Ha restituito CStringW e circa il 30% del tempo è stato effettivamente speso lì solo leggendo il file.

  • Il contenitore in cui sono state memorizzate le stringhe era solo un vettore di dimensioni fisse di puntatori a CStringW e non è mai stato ridimensionato.

EDIT: Sono convinto che i suggerimenti mi è stata data sarebbe davvero lavorare, e che probabilmente potuto battere il codice C#, se ho investito abbastanza tempo in essa. D'altra parte, fare ciò non fornirebbe alcun valore per il cliente e l'unica ragione per farlo sarebbe solo dimostrare che si potrebbe fare ...

+0

grazie per la sinossi. – Tim

3

Il problema non è nel CString, ma piuttosto che stai allocando molti piccoli oggetti - l'allocatore di memoria predefinito non è ottimizzato per questo.

Scrivi il tuo allocatore personale - assegna una grande porzione di memoria e poi fai avanzare un puntatore al suo interno durante l'allocazione. Questo è ciò che effettivamente fa l'allocatore .NET. Quando sei pronto cancella l'intero buffer.

Credo che ci fosse campione di scrittura costume nuovo/DELETE operatori (More) Effective C++

+0

anche se non ho mai provato a usarlo, i contenitori della libreria Standard C++ accettano un Allocator come parametro, in modo da ottimizzare/ottimizzare le creazioni degli oggetti. Se necessario. –

0

Non c'è da meravigliarsi che la gestione della memoria di CLR è migliore rispetto al gruppo di trucchi vecchi e sporchi MFC si basa su: essa è almeno due volte più giovane di MFC stesso, ed è basato sul pool. Quando ho dovuto lavorare su un progetto simile con array di stringhe e WinAPI/MFC, ho appena usato std :: basic_string istanziato con TCHAR di WinAPI e il mio allocatore basato su Loki :: SmallObjAllocator. Puoi anche dare un'occhiata a boost :: pool in questo caso (se vuoi che abbia una "std feel" o usare una versione di compilatore VC++ più vecchia di 7.1).