2009-07-29 10 views
5

Durante la risoluzione di alcuni problemi di prestazioni nelle nostre app, ho scoperto che le funzioni di C stdio.h (e, almeno per il nostro fornitore, fstream del C++) sono thread-safe. Di conseguenza, ogni volta che faccio qualcosa di semplice come fgetc, RTL deve acquisire un blocco, leggere un byte e rilasciare il blocco.I/O file non protetto in C/C++

Questo non va bene per le prestazioni.

Qual è il modo migliore per ottenere I/O di file non protetti in C e C++, in modo da poter gestire il blocco e ottenere prestazioni migliori?

  • MSVC fornisce _fputc_nolock, e GCC fornisce unlocked_stdio e flockfile, ma non riesco a trovare alcuna funzione simili nella mia compilatore (CodeGear C++ Builder).
  • Potrei usare l'API di Windows non funzionante, ma non è portabile e presumo che sarebbe più lento di un fgetc sbloccato per l'I/O caratteriale alla volta.
  • Potrei passare a qualcosa di simile allo Apache Portable Runtime, ma potrebbe essere molto lavoro.

Come gli altri si avvicinano a questo?

Modifica: Poiché alcune persone si chiedevano, l'avevo provato prima di postare. fgetc non effettua chiamate di sistema se è in grado di soddisfare le letture dal suo buffer, ma continua a bloccare, quindi il blocco termina con un'enorme percentuale di tempo (centinaia di blocchi da acquisire e rilasciare per un singolo blocco di dati letti dal disco). Non fare l'I/O di carattere alla volta sarebbe una soluzione, ma le classi fstream del C++ Builder sfortunatamente usano fgetc (quindi se voglio usare le classi iostream, sono bloccato con esso), e ho un sacco di codice legacy che utilizza fgetc e amici per leggere i campi da file in stile record (che sarebbe ragionevole se non fosse per problemi di blocco).

+0

Le funzioni stdio.h di C non sono thread-safe; anche quello è il tuo venditore. – MSalters

+0

Non solo il mio venditore; POSIX, per esempio, lo richiede. –

risposta

3

Semplicemente non eseguo l'IO di un carattere alla volta se è sensato dal punto di vista delle prestazioni.

+0

tutte le operazioni sugli stream hanno come risultato un char IO. È necessario lavorare con i buffer del flusso. Ho postato sotto come ... – ovanes

1

fgetc quasi certamente non legge un byte ogni volta che lo chiami (dove con 'lettura' intendo invocare una chiamata di sistema per eseguire I/O). Guardare da qualche altra parte per il collo di bottiglia delle prestazioni, in quanto questo non è probabilmente il problema, e l'utilizzo di funzioni non sicure non è certamente la soluzione. Qualsiasi manipolazione del blocco che farai sarà probabilmente meno efficiente della gestione eseguita dalle routine standard.

+1

Non sta leggendo un byte alla volta, ma può benissimo prendere una serratura ogni volta. BTW, prendere un blocco è obbligatorio in POSIX, c'è getc_unlocked() (e _unlocked variant di char per le funzioni char IO e le funzioni di blocco in modo che possano essere protette). – AProgrammer

1

Il modo più semplice sarebbe quello di leggere l'intero file in memoria e quindi fornire la propria interfaccia simile a fgetc a quel buffer.

1

Perché non solo la memoria mappa il file? La mappatura della memoria è portatile (tranne che in Windows Vista che richiede di saltare le speranze di usarlo ora, i coglioni). In ogni caso, mappa il tuo file in memoria, e sei proprio bloccato/non-bloccante sulla posizione di memoria risultante.

Il sistema operativo gestisce tutto il blocco richiesto per la lettura effettiva dal disco - non sarà MAI possibile eliminare questo sovraccarico. Ma l'overhead di elaborazione, d'altra parte, non sarà influenzato da un blocco estraneo diverso da quello che fai tu stesso.

1

l'approccio multipiattaforma è piuttosto semplice. Evita funzioni o operatori in cui lo standard specifica che dovrebbero usare sentinella. sentry è una classe interna in classi iostream che garantisce la coerenza del flusso per ogni carattere di output e in ambiente multi-threading blocca il mutex relativo al flusso per ciascun carattere in uscita.Questo evita condizioni di corsa a bassa quota, ma rende comunque l'uscita illeggibile, poiché stringhe due fili possono essere emessi simultaneamente come il seguente esempio afferma:

filo 1 deve scrivere: abc
filo 2 deve scrivere: def

L'output potrebbe essere simile a: adebcf anziché abcdef o defabc. Questo perché sentry è implementato per bloccare e sbloccare per carattere.

Lo standard lo definisce per tutte le funzioni e gli operatori che si occupano di istream o ostream. L'unico modo per evitare ciò è utilizzare i buffer di flusso e il proprio blocco (per stringa, ad esempio).

Ho scritto un'app, che emette alcuni dati in un file e misura la velocità. Se aggiungi qui una funzione che usa direttamente il flusso senza usare il buffer e lo scarico, vedrai la differenza di velocità. Usa boost, ma spero che non sia un problema per te. Prova a rimuovere tutti i streambuffers e vedi la differenza con e senza di loro. Nel mio caso, l'inconveniente delle prestazioni era il fattore 2-3 circa.

Il numero following article di N. Myers spiegherà come funzionano locales e sentry in C++ IOStreams. E di sicuro dovresti cercare in documento ISO C++ Standard, che usa la sentinella.

Good Luck,
Ovanes

#include <vector> 
#include <fstream> 
#include <iterator> 
#include <algorithm> 
#include <iostream> 
#include <cassert> 
#include <cstdlib> 

#include <boost/progress.hpp> 
#include <boost/shared_ptr.hpp> 

double do_copy_via_streambuf() 
{ 
    const size_t len = 1024*2048; 
    const size_t factor = 5; 
    ::std::vector<char> data(len, 1); 

    std::vector<char> buffer(len*factor, 0); 

    ::std::ofstream 
    ofs("test.dat", ::std::ios_base::binary|::std::ios_base::out); 
    noskipws(ofs); 

    std::streambuf* rdbuf = ofs.rdbuf()->pubsetbuf(&buffer[0], buffer.size()); 

    ::std::ostreambuf_iterator<char> oi(rdbuf); 

    boost::progress_timer pt; 

    for(size_t i=1; i<=250; ++i) 
    { 
    ::std::copy(data.begin(), data.end(), oi); 
    if(0==i%factor) 
     rdbuf->pubsync(); 
    } 

    ofs.flush(); 
    double rate = 500/pt.elapsed(); 
    std::cout << rate << std::endl; 
    return rate; 
} 

void count_avarage(const char* op_name, double (*fct)()) 
{ 
    double av_rate=0; 
    const size_t repeat = 1; 
    std::cout << "doing " << op_name << std::endl; 
    for(size_t i=0; i<repeat; ++i) 
     av_rate+=fct(); 

    std::cout << "average rate for " << op_name << ": " << av_rate/repeat 
      << "\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n" 
      << std::endl; 
} 


int main() 
{ 
    count_avarage("copy via streambuf iterator", do_copy_via_streambuf); 
    return 0; 
} 
1

Una cosa da considerare è quello di costruire un runtime personalizzato. La maggior parte dei compilatori fornisce l'origine alla libreria di runtime (sarei sorpreso se non fosse nel pacchetto C++ Builder).

Questo potrebbe finire per essere un sacco di lavoro, ma forse hanno localizzato il supporto del thread per rendere facile una cosa del genere. Ad esempio, con il compilatore di sistema incorporato che sto usando, è progettato per questo: hanno ganci documentati per aggiungere le routine di blocco. Tuttavia, è possibile che questo potrebbe essere un mal di testa di manutenzione, anche se inizialmente risulta relativamente facile.

Un altro percorso simile sarebbe quello di parlare con qualcuno come Dinkumware sull'utilizzo di un runtime di terze parti che fornisce le funzionalità necessarie.