“…se hai il pc lento è perchè hai poca ram…”(Parte 2)

by Marco 18. febbraio 2009 10.17

(...continua da http://www.marcorossignoli.it/post/se-hai-il-pc-lento-e-perche-hai-poca-ram(Parte-1).aspx)

…quando un programma vuole allocare della memoria per poi utilizzarla deve prima o poi richiederla al sistema operativo attraverso delle API come ad esempio VirtualAlloc.
Quando parlo di programma, non intendo solamente il codice che noi scriviamo, ma anche ad esempio un qualsiasi runtime che effettua questa chiamata per noi.
Ad esempio il runtime .NET effettua questa operazione quando noi facciamo le new() sugli oggetti, ma fortunatamente tutta questa complessità ci è nascosta e la gestione della memoria viene fatta in modo trasparente dal garbage collector.
Per capire un pò meglio come funziona il tutto, scriviamo un pò di codice C#, precisamente una bella console application che farà in modo esplicito una richiesta di memoria virtuale al sistema operativo.

Intanto con un pò di P/Invoke permettiamo a CLR di chiamare l’API (vi consiglio questo tool per generare le firme in .NET Invoke Interop Assistant):

static class Native         
{        
      public const int MEM_RESERVE = 8192;        
      public const int PAGE_READWRITE = 4;        
      public const int MEM_COMMIT = 4096;        

      [System.Runtime.InteropServices.DllImportAttribute
        ("kernel32.dll",EntryPoint ="VirtualAlloc",SetLastError=true)]        
      public static extern System.IntPtr VirtualAlloc(
            [System.Runtime.InteropServices.InAttribute()] System.IntPtr lpAddress,
            uint dwSize,
            uint flAllocationType,
            uint flProtect);

}

 

Attraverso VirtualAlloc possiamo così richiedere memoria (virtuale, ricordiamoci che noi possiamo usare solo quella) al sistema operativo che poi utilizzeremo per fare qualcosa, come ad esempio metterci le nostre strutture dati. E adesso il main:

       static void Main(string[] args)
       {
           uint Megabyte = 1048576;           

           IntPtr memoryPointer = IntPtr.Zero;

           memoryPointer = Native.VirtualAlloc(memoryPointer,
                                                  Megabyte * 300,
                                                  Native.MEM_RESERVE,
                                                  Native.PAGE_READWRITE);

           if (memoryPointer == IntPtr.Zero )
               throw new Win32Exception();

           Console.ReadKey();
       }

Il codice richiede semplicemente 300 megabyte di memoria virtuale sulla quale il processo possa scrivere e leggere.
In caso l’API ritorni null (IntPtr.Zero) significa che non c’è sufficiente memoria e l’allocazione non è andata a buon fine (ad esempio se noi mettiamo 3000 invece che 300 l’API ci risponde picche in quanto abbiamo visto che lo spazio di indirizzamento di default per lo user mode è di 2GB quindi non è possibile fare un’allocazione superiore a quella).

Se guardiamo bene il codice ci accorgiamo che l’API ci richiede un parametro particolare nel quale io ho passato MEM_RESERVE, questo significa che il sistema operativo non mi mette a disposizione subito 300 megabyte di ram fisica, ma semplicemente richiedo che mi vengano riservati 300 megabyte dello spazio di indirizzamento che succesivamente renderò fisici richiedendolo attraverso la stessa api, ma passando il parametro MEM_COMMIT. Riservarsi memoria virtuale non significa che posso metterci qualcosa, ma semplicemente mi permette di creare un blocco di memoria e avere la sicurezza che quando ne farò il commit (quindi verrà fisicamente messa a disposizione) avrò un insieme di locazioni contigue in memoria, nelle quali potrò mettere quello che mi serve.

Detto questo si capisce che abbiamo 2 tipi memoria nel nostro processo, la memoria virtuale e la memoria fisica effettivamente utilizzabile, tecnicamente chiamata “committed memory” alla quale mi riferirò sempre attraverso indirizzi virtuali. Quindi la memoria effettiva consumata dal mio processo non è quella virtuale ma quella committed ovvero quella che è stata effettivamente mappata ad indirizzi fisici.
Per provare questo comportamento possiamo fare un test e verificare che riservare memoria virtuale non consuma effetivamente la memoria fisica del sistema. Per effettuare questo test dovete scaricarvi un tool Process Explorer di Mark Russinovich, un architetto del core di Windows che lavora nel team di microsoft e co-autore del libro che vi ho segnalato come fonte.
A questo punto eseguiamo il codice che segue e guardiamo un pò di numeri:

1 static void Main(string[] args)
2         {
3            uint Megabyte = 1048576;           

4            IntPtr memoryPointer = IntPtr.Zero;

5            memoryPointer = Native.VirtualAlloc(memoryPointer,
                                                    Megabyte * 300,
                                                    Native.MEM_RESERVE,
                                                    Native.PAGE_READWRITE);

6            if (memoryPointer == IntPtr.Zero )
                 throw new Win32Exception();

7           memoryPointer = Native.VirtualAlloc(memoryPointer,
                                                   Megabyte * 300,
                                                   Native.MEM_COMMIT,
                                                   Native.PAGE_READWRITE);

8            Console.ReadKey();
9        }


Tanto per cominciare vediamo a quanto ammonta la committed memory nel mio sistema nel momento in cui scrivo. Per vederla ho cliccato sull’icona  icona  nella barra sotto il menù classico.
Ecco i risultati:

  info

Circa 2.9 di memoria fisica allocata. Nel riquadro Commit Charge vediamo le stime meno approssimative.

Ora facciamo partire il nostro programma…mettiamo un bel breakpoint alla riga numero 6 e vediamo lo stato della memoria:

infoVirtual

come possiamo notare è cambiato poco niente…un pò di committed in più ma poca roba (qualcosa ha dovuto caricare, alla fine un processo in più in memoria c’è e fino al breakpoint ci siamo arrivati, da qualche parte quei byte li dobbiamo mettere). Quindi possiamo affermare che allocare memoria virtuale non consuma memoria fisica effettiva.

Ora mettiamo un’altro breakpoint alla riga 8 e vediamo:

InfoCommited

come possiamo vedere ora stiamo consumando circa 300 megabyte di memoria fisica (committed memory) in più (non sono precisamente 300 ma un pò di più…ma sul mio pc girano molti processi e quello è un counter globale, chissà quale altro processo ha richiesto qualcosa :-) ). Quindi possiamo affermare una volta per tutte che la memoria virtuale non è la memoria realmente utilizzata.

Vediamo ora il consumo di memoria dalla prospettiva del nostro processo. Per fare questo dovete impostare un paio di contatori utili. Andate sul menù View->Select Columns->Process Memory e fleggate Private Bytes e Virtual Size.
Il contatore Private Bytes tiene conto della committed memory consumata dal processo, mentre Virtual Size è semplicemente la memoria virtuale riservata per quel processo. Attenzione quello che ho scritto non è propriamente vero o meglio è impreciso, un processo può consumare memoria fisica privatamente (Private Bytes), ma potrebbe anche consumare memoria fisica attraverso l’utilizzo delle “page file backed section” che sono delle zone di committed memory condivise con altri processi(vengono chiamate così in quanto possono essere scritte sul file di paging se serve, vedi section objects) che non vengono conteggiate nel counter di un processo.

Passiamo ai numeri:

le colonne sono rispettivamente PID (id del processo) Virtual Size e Private Byte

Riga 6

Process1

Riga 8

Process2

Ancora una volta possiamo notare la differenza tra memoria virtuale e committed memory (memoria fisica allocata).

Quindi diciamo che la somma di tutta la committed memory di tutti i processi che girano nel nostro sistema ci da più o meno la stima di quanta RAM ci serve…(anche quì ho omesso le “page file backed section” e qualche altro dettaglio per semplicità).

Un ultimo cenno aspetta al paging file; in caso la memoria fisica fosse troppo poca per far girare i nostri programmi, windows può avvalersi del disco fisso come storage temporaneo, permettendoci così di aumentare il “commit limit” del nostro pc. Chiaramente il codice e i dati di un programma devono essere in RAM per essere utilizzati quindi sarà compito del sistema operativo usare policy di “caricamento” e “scaricamento” sul file di paging delle pagine di memoria meno utili. Perciò aumentare il file di pagin significa semplicemente avere più committed memory disponibile, certo se per esempio (esagerato ma è per rendere l’idea) abbiamo 2GB di ram e abbiamo bisogno di 6GB di page file per non andare in out of memory significa che la nostra attività consuma mediamente 4 volte la ram fisica che ho (2GB fisica + 6GB file di paging) e in questo modo l’attività di paging (scrivere sul file di paging e leggere da esso) potrebbe peggiorare seriamente le prestazioni della macchina. Quindi non esiste una formula assoluta per dimensionare il file di pagin, ma possiamo controllare il nostro utilizzo medio per esempio guardando per un periodo alla sera prima di andare a casa il contatore Peak (riquadro Commit Charge) con Process Explorer (vedi sopra) e fare la differenza con il contatore Total (riquadro Physical Memory) così da vedere di quanta memoria in più abbiamo mediamente bisogno, impostando il file di paging intorno a quel numero.

Spero con questi 2 blog di aver chiarito meglio il motivo per cui, se il pc è lento, non è sempre colpa della RAM, ma per saperlo con certezza servono come sempre i numeri…l’idea di questo post mi è venuta quando l’azienda che mi fa l’hosting di un server fisico mi ha consigliato di aumentare la ram se volevo passare da Sql Server 2005 a Sql Server 2008, ho risposto che per ora i numeri non mi dicono che serve un upgrade di RAM ;-)

…chissà se l’indovino aveva ragione…

Fonti: http://www.microsoft.com/learning/en/us/Books/6710.aspx

Correntemente valutato 5.0 da 2 utenti

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

Windows

“…se hai il pc lento è perchè hai poca ram…”(Parte 1)

by Marco 17. febbraio 2009 15.17

Il titolo del blog è volutamente provocatorio (anche se questa frase la sento spesso da persone tecniche e non), almeno che chi ha detto quella frase non sia un mago o un indovino e cercherò di chiarirlo in questo post e nei seguenti.

Quando lanciamo un programma più volgarmente detto “un exe” o “eseguibile”, generalmente usiamo direttamente o indirettamente una delle APIs forniteci dal sistema operativo le quali iniziano solitamente con ‘CreateProcess…’( http://msdn.microsoft.com/en-us/library/ms682425.aspx )
Creare un processo vuol dire tra le altre cose mettere a disposizione del nostro “exe” un insieme di indirizzi di memoria dove il codice del programma possa essere caricato ed eseguito (dalla CPU) e dove i dati gestiti dallo stesso possano essere immagazzinati e usati.
Il nostro programma farà inoltre utilizzo di determinati servizi messi a disposizione del sistema operativo come ad esempio una finestra o un file, il che comporta che lo stesso SO debba mettere da qualche parte le varie strutture di memoria che gli permettono di funzionare (drivers e varie parti del so stesso…etc…).
Tutta questa memoria per essere correttamente utilizzata dal processore deve essere chiaramente nella famosa memoria chiamata RAM.
Detto questo, abbiamo capito che un programma per funzionare, deve usare della memoria allocata dal processo stesso (l’ ‘exe’ lanciato) e un’altra parte di memoria già esistente e gestita dal sistema operativo. Per evitare che chi scrive programmi possa rendere il sistema instabile andando ad esempio a scrivere sulla memoria gestita dal sistema operativo, il sistema stesso con l’aiuto dell’hardware protegge le sue locazioni dall’accesso “non legale” attraverso il concetto di “user mode” e “kernel mode”. Quando il programma  “gira” in “user mode” significa che ha accesso solo alla parte di indirizzi che non sono gestiti dal sistema operativo e in caso venga toccato un indirizzo di questo tipo, il sistema operativo  stesso, con l’aiuto dell’hardware, solleva un’eccezione avvisando che il processo sta utilizzando una parte di memoria che in “user mode” non può toccare. Quando viene effettuata una chiamata ad una API del sistema operativo che fa utilizzo di risorse (memoria) che servono alla funzionalità stessa, con una particolare istruzione macchina, l’esecuzione del codice passa da “user mode” a “kernel mode” permettendo al codice (diciamo codice del sistema operativo…drivers, APIs etc…) di accedere a qualsiasi locazione di memoria disponibile. Quando l’API ha finito il lavoro per la quale è stata chiamata avviene il processo inverso, ovvero il codice ritorna a “girare” in “user mode”, così il nostro codice scritto “male” non può danneggiare la memoria “importante” evitandoci un bel pò di blue screen.
Ora il sistema operativo ci permette di eseguire più processi contemporaneamente (multitasking) quindi serve un modo per poter virtualizzare la memoria che un processo utilizza facendogli credere di aver a disposizione la sua parte della stessa e di poter diciamo chiamare delle APIs (del sistema operativo) che a sua volta useranno una parte di memoria a loro adibita. A questo scopo esiste proprio un tipo di memoria chiamata “memoria virtuale” , una sorta di interfaccia verso l’effettiva memoria…indirizzi relativi, ecco li potremmo definire così. Ogni volta che lanciamo un programma lo stesso è convinto di avere una memoria tutta per lui a cui si riferirà in modo virtuale, sarà poi compito del sistema operativo con la collaborazione dell’hardware tradurre quell’indirizzo virtuale in una cella di memeoria fisica. Per farvi un’idea un pò più reale dovreste immaginarvi la memoria come un array di byte, solo che se voi scrivete memoriaVirtuale[27] effettivamente state accedendo alla memoriaFisica(ram)[1434] (che a noi frega poco).

Tecnicamente parlando, per default Windows su un sistema x86 (senza particolari impostazioni che cambiano le cose, vedi PAE-Physical Address Extension) utilizza una struttura a 2 livelli per tradurre un indirizzo virtuale in un indirizzo fisico, un indirizzo a 32bit viene diviso in 3 componenti che sono indici che vengono usati nelle strutture che effettuano il mapping, 10bits per le "page directory index", 10bits per le "page table index" e 12bits come "byte index" per un totale di 32bits.

Questo meccanismo permette quindi al nostro programma di non dover sapere quanta memoria fisica è disponibile (magari molto meno di quella usata da tutti i processi insieme…mah potremmo buttarla sul disco fisso; sarà lento ma meglio che non far partire il programma…insomma la paginiamo e se serve la ricarichiamo e scarichiamo qualcosa d’altro che non è utilizzato) e inoltre possiamo far girare più processi completamente isolati tra loro (esclusa la parte di memoria del SO che tanto condividiamo, tanto le funzionalità messe a disposizione dal SO ai processi sono sempre quelle o no? Quella basta proteggerla dai programmi fatti male :-P cioè se memoriaVirtuale[1] magari memoriaFisica(ram)[23459] è del sistema operativo…ci prendiamo una bella eccezione, solo il codice che gira in kernel mode può usarla).

Per concludere questo primo post diciamo che ogni processo su una macchina a 32bit ha la propria memoria virtuale di 2^32; quindi 4GB divisi tra user mode e kerner mode in questo modo (per default, ma il layout potrebbe cambiare in caso di particolari impostazioni) :

                                                    image_thumb_12


Bene…prendete fiato…nel prossimo post continuiamo a cercare di capire se l’indovino è bravo o no :-)

Fonti: http://www.microsoft.com/learning/en/us/Books/6710.aspx

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Windows

In attesa delle TPL: Parallel.For me ;-)

by Marco 16. febbraio 2009 16.04

In attesa delle Task Parallel Library che saranno presenti nella versione 4.0 del Framework e che ora sono in CTP mi sono servito della sintassi che utilizzeremo in futuro per esprimere un ciclo For parallelo.

La firma di uno degli overload del For parallelo delle TPL ad oggi è questo:

public static void For(int fromInclusive,int toExclusive,Action<int> body)

Praticamente possiamo eseguire un’azione (Action<int> body) in parallelo più volte finchè la condizione fromInclusive < toExclusive viene verificata.

Il parametro di tipo intero che viene passato al body è l’indice del counter.

La sintassi con cui possiamo invocare il for parallelo è ad esempio questa:

System.Threading.Parallel.For(0,4,
               c =>
               {                                       
                   Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);                   
               });

il codice soprastante funziona se avete scaricato la CTP del giugno scorso e avete referenziato l’assembly System.Threading.
Il codice non fa niente di interessante al posto del Console.WriteLine(…) avreste dovuto mettere la parte di codice che volete venga eseguita in parallelo.

La mia versione del For parallelo fa uso della classe ThreadPool presente nel framework e che possiamo utilizzare già oggi, quindi utilizza un differente algoritmo di scheduling e non quello utilizzato dalle nuove TPL (molto più fine e complesso, magari vi spiegherò in qualche post futuro come funziona)…diciamo che hanno scritto qualche riga in più :-) e che risolvono molti più problemi di quelli che la mia classe risolve. Quindi il funzionamento beneficia delle gioie e soffre i dolori che il funzionamento della classe ThreadPool porta con se.

La classe che ho scritto e utilizzato è la seguente:

public class Parallel
{
    public static void For(int fromInclusive, int toExclusive, System.Action<int> body)
    {
        ThreadPool.QueueUserWorkItem(f =>
        {
            for (int i = fromInclusive; i < toExclusive; i++)
            {
                ThreadPool.QueueUserWorkItem(p =>
                    {
                        Data pl = (Data)p;
                        pl.Action(pl.Count);                   
                    }
                    , new Data() { Action = body, Count = i });
            }
        }, null);       
    }  
    class Data
    {
        public Action<int> Action { get; set; }
        public int Count { get; set; }
    }
}

Il codice di invocazione è il medesimo delle TPL basta evitare di usare la parte System.Threading, ma scrivere solamente:

Parallel.For(0,4,
               c =>
               {                                       
                   Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);                   
               });

la classe deve essere chiaramente visibile.
Il codice non è stato testato "seriamente" quindi potrebbe essere afflitto da bugs e non tratta problematiche come ad esempio gestione delle eccezioni, sospensione del task, sincronizzazione, partizionamento o altre problematiche legate al parallelismo che sono state gestite all’interno delle TPL…per il mio problema questa classe è più che sufficiente, il giorno che rilasceranno le TPL toglierò la mia classe e avrò bisogno solo di referenziare l’assembly con le nuove classi per sfruttare il lavoro del team di Microsoft.
Se trovate qualche bugs o questa soluzione non risolve il vostro problema o ne crea di nuovi contattatemi qui che magari ne parliamo.

 Scarica la solution di esempio

Essere architetti...

by Marco 16. febbraio 2009 09.46
Riporto un interessante blog di Joe Duffy (Task Parallel Library) sulla sua idea di "architetto software".

Tra tutte le sue interessanti opinioni, cito quella che ritengo più importante e che mi trova d'accordo:

"An architect’s success is measured by what he or she ships to customers, and not by the amazing ideas that were ultimately never realized."

Vi lascio il link del post: http://www.bluebytesoftware.com/blog/PermaLink,guid,727ecd54-2ccf-4fa0-a0e0-f27ee125f7ae.aspx

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Conoscere il tipo di binding da un [OperationContract] in WCF

by Marco 13. febbraio 2009 13.46

Un pò di tempo fa mi è stato chiesto se era possibile conoscere quale binding venisse utilizzato per il dispatch di una determinata Operation con WCF.
La soluzione più elegante IMHO è quella di utilizzare l'oggetto OperationContext che porta con se tutto il contesto di chiamata e ci permette di ispezionarne tutte le caratteristiche, comprese quelle del channel su cui è stata effettuata.
Precisamente per conoscere il tipo di binding possiamo affidarci alla proprietà Scheme del Listener che ha ricevuto la chiamata:

 OperationContext.Current.EndpointDispatcher.ChannelDispatcher.Listener.Uri.Scheme

la proprietà Scheme è di tipo string, ad esempio se stiamo ricevendo una chiamata su un binding di tipo http troveremo che la proprietà sarà "http".
Detto questo spetta a noi decidere dove controllare questa proprietà a seconda dell'utilizzo che ne vogliamo fare, potrebbe essere in uno dei tanti punti di estensione di WCF come direttamente nel corpo della chiamata al servizio (che non mi piace molto, ma dipende dal problema da risolvere)

Lascio lo snippet del codice completo creato con il tempate di VS 2008:

public class Service : IService

{

    public string GetScheme(){

                  return OperationContext.Current.EndpointDispatcher.ChannelDispatcher.Listener.Uri.Scheme; 

         }  

}

Ho chiaramente modificato il contratto.

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Wcf

Disclaimer
Le opinioni espresse in questo blog sono mie opinioni personali.

© Copyright 2012 Knowledge.CreateAsync()