2012-02-29 11 views
7

Quindi stavo giocando con del codice e volevo vedere quale metodo di conversione di uno std :: string in maiuscolo fosse più efficiente. Ho pensato che i due sarebbero stati in qualche modo simili per quanto riguarda le prestazioni, ma ero terribilmente in errore. Ora mi piacerebbe scoprire perché.Conversione da std :: string in maiuscolo: maggiore differenza di prestazioni?

Il primo metodo di conversione della stringa funziona come segue: per ogni carattere nella stringa (salva la lunghezza, itera da 0 alla lunghezza), se è tra 'a' e 'z', quindi spostalo in modo che sia invece tra 'A' e 'Z'.

Il secondo metodo funziona come segue: per ogni carattere nella stringa (a partire da 0, continuare fino a quando non si preme un terminatore null), applicare la funzione build toupper().

Ecco il codice:

#include <iostream> 
#include <string> 

inline std::string ToUpper_Reg(std::string str) 
{ 
    for (int pos = 0, sz = str.length(); pos < sz; ++pos) 
    { 
     if (str[pos] >= 'a' && str[pos] <= 'z') { str[pos] += ('A' - 'a'); } 
    } 

    return str; 
} 

inline std::string ToUpper_Alt(std::string str) 
{ 
    for (int pos = 0; str[pos] != '\0'; ++pos) { str[pos] = toupper(str[pos]); } 

    return str; 
} 


int main() 
{ 
    std::string test = " abcdefghijklmnopqrstuvwxyzABCDEFG[email protected]#$%^&*()_+=-`'{}[]\\|\";:<>,./?"; 

    for (size_t i = 0; i < 100000000; ++i) { ToUpper_Reg(test); /* ToUpper_Alt(test); */ } 

    return 0; 
} 

Il primo metodo ToUpper_Reg voluti circa 169 secondi per 100 milioni di iterazioni.
Il secondo metodo Toupper_Alt ha richiesto circa 379 secondi per 100 milioni di iterazioni.

Cosa dà?


Edit: ho cambiato il secondo metodo in modo che itera la stringa come il primo non (impostare la lunghezza da parte, ciclo while meno di lunghezza) ed è un po 'più veloce, ma ancora circa il doppio lento.


Edit 2: Grazie a tutti per i vostri contributi! I dati su cui lo userò sono garantiti per essere ascii, quindi penso che per il momento rimarrò con il primo metodo. Tengo a mente che toupper è specifico per le impostazioni locali quando/se necessario.

+7

toupper è più lento di quello che si fa in _Reg perché fa più di quello che si fa in Reg? – Almo

+4

Perché non aggiungete anche la trasformazione standard in C++, 'std :: transform (s.begin(), s.end(), s.begin(), (int (*) (int)) std :: toupper); '? (È necessario '#include ', '' e ''.) –

+0

Wow, questo è un boccone. Per curiosità, cosa c'è nella parte '(int (*) (int))'? –

risposta

13

std::toupper utilizza le impostazioni internazionali correnti per le conversioni del caso, che comprendono una chiamata di funzione e altre astrazioni. Quindi, naturalmente, sarà più lento. Ma funzionerà anche su testo non ASCII.

3

toupper() tiene conto delle impostazioni internazionali in modo che possa gestire (alcuni) caratteri internazionali ed è molto più complesso della semplice gestione dell'intervallo di caratteri 'a' - 'z'.

5

toupper() non si limita a spostare caratteri nell'intervallo [a-z]. Per prima cosa dipende dalle impostazioni internazionali e può gestire più di un semplice ASCII.

0

Il secondo ha una chiamata di funzione. una chiamata di funzione è un'operazione costosa in un loop interno. toupper usa anche locales per determinare come deve essere cambiato il personaggio.

I progressi della chiamata è che è di serie e funziona indipendentemente dalla codifica dei caratteri sulla macchina host

Detto questo, vi consiglio vivamente di utilizzare la funzione boost:

boost::algorithm::to_upper 

E ' un modello è quindi più che probabile che sia in linea, tuttavia include le impostazioni locali. Lo userei ancora.

http://www.boost.org/doc/libs/1_40_0/doc/html/boost/algorithm/to_upper.html

0

Credo che sia perché il secondo si chiama una funzione di libreria standard di C, che da un lato non è inline, così hai il sovraccarico di una chiamata di funzione. Ma ancora più importante, questa funzione probabilmente fa molto più di due soli confronti, due salti e due aggiunte di interi. Esegue controlli aggiuntivi sul personaggio e prende in considerazione le impostazioni locali correnti e tutto il resto.

3

Bene, ToUpper_Reg() non funziona. Ad esempio, non trasforma il mio nome in tutti i caratteri maiuscoli. Detto questo, anche ToUpper_Alt() non funziona perché il valore toupper() ottiene un valore negativo su alcune piattaforme, ovvero crea un comportamento non definito (normalmente un arresto anomalo) quando lo si utilizza con il mio nome. Questo è facilmente risolto, anche se, chiamando correttamente o meno così:

toupper(static_cast<unsigned char>(str[pos])) 

Detto questo, le due versioni del codice non sono equivalenti: la versione Onot utilizzando toupper() non sta scrivendo i personaggi per tutto il tempo, mentre quest'ultima versione è: una volta che tutto è convertito in maiuscolo, prende sempre lo stesso ramo dopo un test e quindi non fa nulla. Si potrebbe desiderare di cambiare ToUpper_Alt() a guardare come questo e ripetere il test:

inline std::string ToUpper_Alt(std::string str) 
{ 
    for (int pos = 0; str[pos] != '\0'; ++pos) { 
     if (islower(static_cast<unsigned char>(str[pos])) { 
      str[pos] = toupper(static_cast<unsigned char>(str[pos])); 
     } 
    } 

    return str; 
} 

direi la differenza è la scrittura: toupper() commercia il confronto per una serie di look-up. È possibile accedere rapidamente alle impostazioni internazionali e tutto ciò che fa toupper() è ottenere il puntatore corrente e accedere alla posizione a un dato offset. Con i dati nella cache questo è probabilmente veloce quanto il ramo.

+0

Bella presa sulla differenza nel numero di scritture. –

+0

È costoso costruire una nuova locale per ogni chiamata a 'std :: toupper'? Gli utenti dovrebbero in genere memorizzare nella cache un oggetto locale da inoltrare? – caps

+0

@caps: è piuttosto costoso creare un nuovo oggetto 'std :: locale'. Una copia richiede l'accesso a un incremento di conteggio di riferimento sincronizzato.La creazione di un nuovo 'std :: locale' e la modifica di un facet richiede un ulteriore accesso di conteggio dei riferimenti sincronizzati per facet. La costruzione di default di 'std :: locale' richiede l'accesso sincronizzato al globale' std :: locale' oltre al costo di una copia. Quindi, sì, dovresti mantenere gli oggetti 'std :: locale'. Si noti che la versione di 'std :: toupper()' che non usa un parametro 'std :: locale' non ne crea uno: accede invece all'entità locale di C! –

0

std :: toupper utilizza la localizzazione corrente e la ragione per cui questo è più lenta della funzione C è che la localizzazione corrente è condivisa e mutevole fili diversi, quindi è necessario bloccare l'oggetto locale quando è accessibile a assicurarsi che non sia cambiato durante la chiamata. Ciò accade una volta per chiamata a toupper e introduce un overhead piuttosto ampio (ottenere il lock potrebbe richiedere un syscall a seconda dell'implementazione). Una soluzione alternativa se si desidera ottenere le prestazioni e rispettare le impostazioni internazionali è ottenere prima l'oggetto locale (creando una copia locale) e quindi chiamare la faccetta toupper sulla copia, evitando così la necessità di bloccare per ogni chiamata toupper. Vedi il link sottostante per un esempio.

http://www.cplusplus.com/reference/std/locale/ctype/toupper/

0

La questione è già stata risolta, ma come un a parte, sostituendo il coraggio del vostro ciclo nel primo metodo con:

std::string::value_type &c = str[pos]; 
if ('a' <= c && c <= 'z') { c += ('A' - 'a'); } 

lo rende ancora più veloce. Forse il mio compilatore fa schifo.

Problemi correlati