Gestione della Memoria nella .NET Compact Framework e Windows Mobile (Parte 3 – Suggerimenti per SQLCE)

Con questo post vorrei concludere la saga sulla gestione della memoria, stavolta proponendo alcuni spunti di riflessione su un “corretto” utilizzo delle risorse consumate da un’applicazione che acceda ad un database locale di tipo SQL Compact 3.x (3.1 o 3.5). Anzitutto, facciamo un po’ di chiarezza con i nomi… I prodotti si chiamano:

  • SQL Server 2000 Windows CE Edition (aka “SQLCE v2.0”)
  • SQL Server 2005 Mobile Edition (aka “SQL Mobile” o “SQLCE v3.0”)
  • SQL Server 2005 Compact Edition (aka “SQL Compact 3.1” o “SSCE 3.1” – in un primo momento era noto come “Everywhere Edition” perchè gira sia sui PC che sugli Smart Device)
  • SQL Server Compact 3.5 (aka “SQL Compact 3.5” o “SSCE 3.5”)

Concentriamoci sugli ultimi due. Di quest’ultimo esiste anche la Service Pack 1 e con Visual Studio 2010 ne arriverà anche la SP2, per ora disponibile in Beta solo per i dektop qui). Anzitutto, perchè continuare a parlare della 3.1? Semplicemente perchè, come è possibile notare dagli emulatori di Windows Mobile 6.5 disponibili nel Windows Mobile 6.5 Developer Tool Kit, il motore e le altre librerie del SSCE v3.1 sono incluse nella ROM, quindi non è necessario distribuirlo con la propria applicazione – tra l’altro, le librerie in questione hanno nomi fuorvianti del tipo sql*30.dll (anzichè ad esempio sql*31.dll), come è possibile notare anche dopo aver installato il Microsoft SQL Server 2005 Compact Edition Developer Software Development Kit nella cartella C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v3.1.

L’aspetto di cui vorrei “scalfire la superficie” Nerd è quello delle risorse associate all’utilizzo del database, intese come memoria utilizzata dal processo. E’ bene sottolineare fin da subito che non esiste una soluzione adatta in ogni caso, anche perchè il compromesso da ricercare è tra la quantità di memoria utilizzata e le performance percepite dall’utente finale. Oltre a ciò, ci sono diversi fattori che influiscono sulle “performance” – quindi non solo la richiesta di memoria virtuale – come ad esempio (dall'articolo INFO: SQL Server CE Performance Tips and Efficient Memory Handling):

CPU speed.

CPU instruction set.

Network speed (for connectivity applications).

Memory speed.

Memory size.

Database size.

Query complexity.

Use of indexes.

Other database issues.

Pertanto, è bene fin da subito fissare specifiche legate alle performance desiderate (“quanti secondi\minuti sono accettabili per un utente per poter recuperarare i dati? ” etc..), ed in base a queste eseguire il tuning dell’applicazione, come suggerito anche in Query Performance Tuning (SQL Server Compact Edition). Un aspetto importante non menzionato in quell’articolo e sul quale ho avuto il piacere (Confused) di lavorare, riguarda la memoria associata alle risorse utilizzate e cachate dal motore del database (il cosiddetto “SQLCE Engine”).

In particolare mi riferisco alla SqlCeConnection verso il database: l’idea migliore, dal punto di vista della memoria utilizzata, sarebbe quella di aprire e chiudere la connessione ogni volta sia necessaria. Questo perchè, mentre la connessione è attiva, alcune risorse NATIVE (quindi nemmeno reperibili attraverso il Remote Performance Monitor – supponendo di parlare di un’applicazione NETCF) sono utilizzate dall’applicazione, riducendo quindi lo spazio di indirizzamento (il process slot da 32MB). E’ anche vero che in alcuni casi l’apertura di una connessione non cachata può richiedere del tempo, ma come dicevo queste sono percezioni dell’utente cui spesso è possibile porre rimedio.

Le risorse cui mi riferisco sono query-plan, dati temporanei, etc utilizzati dal SQLCE Engine il quale, ricordo, non è un servizio: è un set di librerie caricate dall’applicazione che accede ai dati. Quindi, quando si apre la connessione verso il database, l’engine viene inizializzato e a mano a mano che la connessione è sfruttata per eseguire query o altro, richiede sempre più memoria, che non viene rilasciata immediatamente ma utilizzata in una sorta di cache. Chiudere la connessione significa liberare questa cache. La dimensione di queste risorse (native) dipende in gran parte dalla dimensione del database e NON dalla complessità della query.

Domanda: è assolutamente necessario chiudere la SqlCeConnection per usare meno memoria? NO, spesso è sufficiente ridurre il valore del Max Buffer Size nella stringa di connessione (ad esempio, da 640KB a 256KB), ma questo ovviamente significa che le modifiche sono “serializzate” verso il database più spesso, il che comporta un effetto sulle performance percepite dall’utente. Quindi questo è un altro parametro di cui fare il tuning, in genere sulla query più costosa in termini di dati che devono essere processati. Se si considera che è possibile avere più connessioni contemporaneamente, ridurre il buffer size di ciascuna può comportare un notevole risparmio di memoria.

Tra l’altro, abbiamo parlato di risorse native, ma da non dimenticare il fatto che, supponendo l’applicazione sia NETCF, anche le strutture-dati usate programmaticamente non possono superare alcuni limiti… (chiamamola “cache applicativa”) Spesso mi è capitato di supportare sviluppatori che utilizzavano i *Dataset* in un’applicazione NETCF come fosse un’applicazione Desktop, e in questi casi ho ricordato che i Dataset sono nati per essere sfruttati soprattutto in ambienti disconnessi (vedi un’applicazione web, ad esempio), perchè sono una copia locale in memoria dei dati sul server – e per applicazioni per Smart Device, dove la memoria a disposizione è veramente scarsa, non è in genere opportuno “sprecare” anche solo qualche centinaio di KB, perchè, come sappiamo dagli alti post della serie, il Garbage Collector parte non appena si supera una certa soglia (dinamica) e per farlo deve aspettare che tutti i thread si trovino in uno stato "sicuro" (con impatto sulle performance percepite): quindi, altro compromesso da valutare è la “cache applicativa”, intesa come struttura dati di tipo dataset o SqlCeResultSet da tenere in memoria nell’applicazione. Ricordo che in generale una GC Heap per applicazioni reali ha dimensioni inferiori a 1MB (può superarlo ovviamente, ma tenerlo sotto il MB comporta avere meno GC).

Sicuramente c’è molto altro da dire sull’argomento, mi limito a citare 2 post del MVP João Paulo Figueira:

ma soprattutto un post, un po’ datato (è su SQL Mobile 2005!) ma veramente interessante, di Roberto Brunetti (quindi in italiano) – che ho avuto il piacere di ascoltare esattamente sull’argomento circa 2 anni fa – proprio a proposito della scelta della struttura dati usare nella propria applicazione (il post si potrebbe riassumere con lo slogan: “Dataset? No grazie!Tongue out ): http://thinkmobile.it/blogs/rob/archive/2006/09/18/SQL-2005-Mobile_3A00_-Accesso-ai-dati.aspx.

HTH, ciao!

Raffaele Limosani
Senior Support Engineer
Windows Mobile & Embedded Developer Support
My Blog around Mobile Development