Ottimizzare C++/Tecniche generali di ottimizzazione: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
Ramac (discussione | contributi)
m + categoria
Ampliata notevolmente la tecnica dei memory-mapped-files
Riga 11:
I numeri in formato binario occupano mediamente meno spazio, e quindi richiedono meno tempo per essere letti da disco e scritti su disco, ma, soprattutto, scrivendo e leggendo i dati nello stesso formato che hanno in memoria non c’è bisogno di nessuna conversione da o verso il formato testuale.
 
=== Eccetto che in una sezione critica di un sistema real-time, se devi accedere a gran parte di un file binario in modo non-sequenziale, invece di accedervi ripetutamente con operazioni di ''seek'', oppure di caricarlo tutto in un buffer dell’applicazione, usa i Memory Mappedun Filememory-mapped-file, se il sistema operativo fornisce tale strumento. ===
 
Quando si deve accedere a gran parte di un file binario in modo non-sequenziale, ci sono due tecniche alternative standard:
Mentre le operazioni di posizionamento (seek) richiedono un certo tempo, con i memory mapped file tali operazioni diventano semplici assegnazioni a un puntatore.
* Aprire il file senza leggerne il contenuto; e ogni volta che si deve leggere un dato, saltare al punto di interesse usando una operazione di posizionamento nel file (''seek''), e leggere il dato usando un'operazione di lettura.
* Allocare un buffer grande quanto tutto il file, aprire il file, leggere tutto il contenuto del file nel buffer, chiudere il file; e ogni volta che si deve leggere un dato, cercarlo nel buffer.
 
Rispetto alla prima tecnica, usando i memory-mapped-file, ogni operazione di posizionamento viene sostituita da una semplice assegnazione a un puntatore, e ogni operazione di lettura da file viene sostituita da una semplice copia da memoria a memoria. Anche supponendo che i dati siano già nella disk cache, entrambe le operazioni effettuate con i memory-mapped-files sono notevolmente più veloci delle operazioni effettuate sui file, in quanto queste ultime comportano altrettante chiamate di libreria, le quali a loro volta effettuano chiamate di sistema.
Per quanto riguarda il caricamento in memoria di un intero file per elaborazioni successive, usando le primitive di lettura di file i dati vengono letti prima nella cache del disco e poi nella memoria del processo, mentre con i memory mapped file si accede direttamente al buffer caricato dal disco, risparmiando così sia un’operazione di copia che lo spazio di memoria per la cache del disco. Analoga situazione si ha per la scrittura su disco.
 
Inoltre,Rispetto sealla piùtecnica processidi devono caricareprecaricare in memoria lo stessol'intero file, lo spazio di memoria viene allocato per ogni processo, mentre usando i memory -mapped -file il sistema operativo tiene in memoria una sola copia dei dati, condivisasi da tuttihanno i processi.seguenti vantaggi:
* Usando le primitive di lettura di file, i dati vengono normalmente letti prima nella cache del disco e poi nella memoria del processo, mentre con i memory-mapped-file si accede direttamente al buffer caricato dal disco, risparmiando così sia un'operazione di copia che lo spazio di memoria per la cache del disco. Analoga situazione si ha per la scrittura su disco.
 
* Leggendo tutto il file, il programma si blocca per un tempo significativo per leggere il file, mentre usando un memory-mapped-file tale tempo viene distribuito nel corso dell'elaborazione, man mano che si accede alle varie parti del file.
Infine, in condizioni di scarsità di memoria, il sistema operativo scrive nell'area di swap del disco anche la memoria del processo che non è stata modificata, mentre si limita a scartare le pagine non modificate del memory mapped file, senza scriverle sul disco.
* Se in alcune esecuzioni serve solo una piccola parte del file, il memory-mapped-file carica in memoria solo quelle parti.
* Se più processi devono caricare in memoria lo stesso file, lo spazio di memoria viene allocato per ogni processo, mentre usando i memory-mapped-file il sistema operativo tiene in memoria una sola copia dei dati, condivisa da tutti i processi.
* In condizioni di scarsità di memoria, il sistema operativo scrive nell'area di swap del disco anche la memoria del processo che non è stata modificata, mentre si limita a scartare le pagine non modificate del memory-mapped-file, senza scriverle sul disco.
 
Tuttavia, l’uso di memory mapped file non è appropriato in una porzione critica di un sistema real-time, in quanto l'accesso a tali dati ha una latenza fortemente variabile a seconda che il dato acceduto sia in cache o su disco.
 
A rigore, questa è una tecnica dipendente dalla piattaforma, in quanto la funzionalità dei memory mapped files non esiste in tutti i sistemi operativi. Tuttavia, dato che tale funzionalità esiste in tutti i principali sistemi operativi dotati di memoria virtuale, questa tecnica si può considerare di ampia applicabilità.
 
Ecco una classe che incapsula le primitive di accesso in sola lettura a un file tramite memory-mapped-file, utilizzabile sia nei sistemi operativi di tipo Posix (come Unix, Linux, e Mac OS X), sia in ambiente Microsoft Windows.
 
==== File ''memory_file.hpp'' ====
 
<source lang=cpp>
#ifndef MEMORY_FILE_HPP
#define MEMORY_FILE_HPP
 
/*
Wrapper di memory-mapped file per sola lettura.
Gestisce solo file che possono essere interamente caricati
nello spazio di indirizzamento del processo.
Il costruttore apre il file, il distruttore lo chiude.
La funzione "data" rende un puntatore all'inizio del file,
se il file e' stato aperto con successo, altrimenti rende 0.
La funzione "length" rende la lunghezza in byte del file,
se il file e' stato aperto con successo,
altrimenti rende 0.
*/
 
class InputMemoryFile {
public:
InputMemoryFile(const char *pathname);
~InputMemoryFile();
const void* data() const { return data_; }
unsigned long length() const { return length_; }
private:
void* data_;
unsigned long length_;
#if defined(__unix__)
int file_handle_;
#elif defined(_WIN32)
typedef void * HANDLE;
HANDLE file_handle_;
HANDLE file_mapping_handle_;
#else
#error Solo i sistemi Posix o Windows possono usare i memory-mapped files.
#endif
};
#endif
</source>
 
==== File ''memory_file.cpp'' ====
 
<source lang=cpp>
#include "memory_file.hpp"
#if defined(__unix__)
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#elif defined(_WIN32)
#include <windows.h>
#endif
 
InputMemoryFile::InputMemoryFile(const char *pathname):
data_(0),
length_(0),
#if defined(__unix__)
file_handle_(-1)
{
file_handle_ = open(pathname, O_RDONLY);
if (file_handle_ == -1) return;
struct stat sbuf;
if (fstat(file_handle_, &sbuf) == -1) return;
data_ = mmap(0, sbuf.st_size, PROT_READ, MAP_SHARED, file_handle_, 0);
if (data_ == MAP_FAILED) data_ = 0;
else length_ = sbuf.st_size;
#elif defined(_WIN32)
file_handle_(INVALID_HANDLE_VALUE),
file_mapping_handle_(INVALID_HANDLE_VALUE)
{
file_handle_ = ::CreateFile(pathname, GENERIC_READ,
FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (file_handle_ == INVALID_HANDLE_VALUE) return;
file_mapping_handle_ = ::CreateFileMapping(
file_handle_, 0, PAGE_READONLY, 0, 0, 0);
if (file_mapping_handle_ == INVALID_HANDLE_VALUE) return;
data_ = ::MapViewOfFile(
file_mapping_handle_, FILE_MAP_READ, 0, 0, 0);
if (data_) length_ = ::GetFileSize(file_handle_, 0);
#endif
}
 
InputMemoryFile::~InputMemoryFile() {
#if defined(__unix__)
munmap(data_, length_);
close(file_handle_);
#elif defined(_WIN32)
::UnmapViewOfFile(data_);
::CloseHandle(file_mapping_handle_);
::CloseHandle(file_handle_);
#endif
}
</source>
 
==== File ''memory_file_test.cpp'' ====
 
<source lang=cpp>
include "memory_file.hpp"
#include <iostream>
#include <iterator>
 
int main() {
InputMemoryFile imf("memory_file_test.cpp");
if (imf.data())
copy((const char*)imf.data(),
(const char*)imf.data() + imf.length(),
std::ostream_iterator<char>(std::cout));
else std::cerr << "Errore";
}
</source>
 
== Caching ==