#4 Classi | By-value VS by-reference

In questo post vediamo un altro importante argomento: le classi by-value e le classi by-reference.

Classi by-value

Una classe by-value segue il paradigma del dataflow. In LabVIEW le classi sono nativamente by-value e si comportano quindi come ogni altro tipo base di LabVIEW (esclusi i refnum): numeri, array, stringhe, cluster, ecc.
In LabVIEW i dati sono “trasportati” dai fili . Quando un filo si biforca, il dato di questo filo viene duplicato. I due dati trasportati seguono, di conseguenza, percorsi del tutto indipendenti. Le operazioni svolte cioè sul dato del primo filo non hanno effetto sul dato del secondo filo e viceversa. Questo comportamento prende il nome di “by-value” e segue le regole del dataflow di LabVIEW stesso.
Essendo le classi in LabVIEW nativamente by-value, quando istanziamo un oggetto, esso segue la regola scritta sopra. Se il filo che trasporta questo oggetto si biforca, viene fatta una copia in memoria dell’oggetto ed è come se ci fossero due oggetti indipendenti. I metodi e i setter applicati sul primo oggetto non hanno effetto sul secondo oggetto e viceversa.

Classi by-reference

Le classi by-reference si comportano come il tipo dato refnum. Un refnum è un riferimento ad una locazione in memoria condivisa, accessibile da più “punti” attraverso alcuni meccanismi di protezione. I tipi refnum in LabVIEW includono le code, file I/O, i reference dei controlli, il tipo VI Server, ecc.
Essendo un refnum un puntatore ad un’area di memoria, quando un filo che trasporta un refnum si biforca, viene fatta una copia del refnum stesso ma non del dato a cui punta. Il puntatore segue sì le regole della programmazione by-value ma non l’area di memoria (quindi il dato) a cui punta.
Una classe by-reference segue questo approccio. Questo tipo di classi, infatti, sfrutta strutture dato di tipo refnum e di conseguenza ne eredita il comportamento (nel prossimo post vedremo come si implementano le classi by-reference nel dettaglio).
In un oggetto gestito by-reference, i suoi dati sono memorizzati in un’area di memoria potenzialmente condivisa. Si considerino per esempio due while loop che accedono allo stesso oggetto by-reference. Nonostante non ci sia dipendenza di flusso dati tra i due loop, le modifiche fatte sull’oggetto condiviso all’interno di un loop impattano sull’altro loop.
In linguaggi di programmazione OOP come C#, le classi sono solo by-reference. Le operazioni svolte da un metodo o una funzione su un oggetto modificano l’oggetto stesso e fanno quindi sempre riferimento all’oggetto originale. Per duplicare un oggetto, è necessario clonarne uno nuovo usando l’originale come oggetto di partenza.

Vantaggi e svantaggi

Vediamo ora vantaggi e svantaggi di queste due tipologie di classi così da poter decidere con maggiore consapevolezza.

Le classi by-value hanno i seguenti vantaggi:

  • Utilizzo del concetto di data flow, un processo del tutto naturale nella programmazione in LabVIEW. Programmare usando una classe by-value non è diverso da utilizzare numeri, stringhe e ogni altra struttura dato base di LabVIEW (ad eccezione dei refnum).
  • L’oggetto by-value, non essendo “condiviso” tra sezioni diverse del codice, non può essere modificato in altri punti del codice se non nel filo che lo ospita. Questo rende il debug semplice e veloce.
  • L’utilizzo di classi by-value consente l’esecuzione di più thread nel codice senza preoccuparsi che una sezione di codice influenzi i valori in un’altra sezione.
  • Gli oggetti by-value, seguendo l’approccio naturale nell’ambiente LabVIEW, semplificano la suddivisione e condivisione del codice tra programmatori, soprattutto se questi hanno livelli di preparazione differenti.

Le classi by-value hanno le seguenti limitazioni:

  • Lo stesso oggetto non può essere condiviso su più biforcazioni.
    Supponiamo di avere un oggetto ReportSettings che contiene le impostazioni di creazione di un report e che è utilizzato da più while loop paralleli dell’applicazione. Un possibile approccio è quello di passare l’oggetto ReportSettings (by-value) ai loop che lo utilizzano. Si hanno quindi tante copie indipendenti dell’oggetto per ogni loop a cui viene passato. Ciò può essere tuttavia sconsigliato perché, se si devono fare delle modifiche sull’oggetto (per esempio chiamandone i relativi setter o metodi che ne modificano lo stato), avendo un oggetto indipendente per ogni “filo”, ogni modifica impatta solo sulla copia dell’oggetto del relativo “filo”. In questo scenario, se si vuole avere un unico oggetto, l’approccio corretto è quello di avere un unico attore (nel caso più semplice un while loop) in cui l’oggetto ReportSettings esiste e di prevedere un meccanismo di scambio di dati o comandi tra tale attore e tutti gli altri attori che devono eseguire operazioni su tale oggetto.
  • Possibili problemi nella gestione della memoria nel caso si abbiano oggetti di grandi dimensioni. Se un oggetto contiene molti dati ed occupa quindi una parte consistente della memoria dell’applicazione, è necessario assicurarsi di non copiarla spesso.
    La gestione della memoria e delle copie che LabVIEW fa rappresentano aspetti da tenere sempre sotto controllo. Questo vale anche per oggetti by-value di grandi dimensioni.
    Supponiamo di avere una classe TestResult che contiene i risultati di una sequenza di test e potenzialmente può contenere decine o centinaia di MB di dati. In questo caso, ogni volta che l’oggetto TestResult è diramato si crea una copia facendo rapidamente levitare la RAM occupata dall’applicazione.

Le classi by-reference hanno i seguenti vantaggi:

  • Condivisione di uno stesso oggetto tra più sezioni di codice paralleli (per esempio due while loop paralleli). Con un oggetto by-reference condiviso, le modifiche fatte in un punto sono “visibili” in ogni altro punto. Supponendo quindi di avere due loop paralleli che condividono lo stesso oggetto by-reference, se il primo loop modifica l’oggetto chiamandone un relativo setter o metodo, l’oggetto aggiornato è visibile anche dal secondo loop.
  • Possibile semplificazione del codice. Supponiamo di nuovo di avere un oggetto ReportSettings che contiene le impostazioni di creazione di un report e che è utilizzato in sola lettura da più while loop paralleli dell’applicazione. L’approccio by-reference permette ai vari loop coinvolti che ne leggono lo stato di avere una versione sempre aggiornata dell’oggetto senza dover implementare particolari meccanismi oltre a quelli per rendere l’oggetto da by-value a by-reference.
  • Migliore gestione della memoria nel caso di oggetti di grandi dimensioni. Si evitano infatti inutili copie in memoria dello stesso oggetto. La copia di un dato avviene non solo quando si biforca un filo ma anche quando il dato è passato ad un Sub-VI1Si può osservare dove LabVIEW esegue copie di un dato direttamente da LabVIEW Tools -> Profiles -> Show buffer allocations. Se si maneggiano oggetti di grandi dimensioni può essere più opportuno usare un approccio by-reference così da evitare inutili copie in memoria che aumentano l’occupazione di RAM dell’applicazione. Un esempio pratico è la libreria IMAQ dove le immagini (che possono occupare una considerevole quantità di memoria) sono gestite by-reference.

Le classi by-reference hanno invece i seguenti svantaggi:

  • Richiede l’implementazione di meccanismi di protezione. Con l’approccio by-reference infatti si ha una risorsa condivisa e tali meccanismi sono indispensabili per evitare race condition o altre condizioni indesiderate.
  • Mancato rispetto del paradigma del dataflow che va quindi contro il naturale metodo di programmazione usato in ambiente LabVIEW.
  • Debug più complicato. Fare debug su una classe by-reference e condivisa può essere lungo e complicato.
  • La realizzazione di classi by-reference non è nativamente supportata da LabVIEW. Si ha quindi un overhead dovuto all’implementazione dei meccanismi per rendere by-reference una classe che nativamente è by-value.
Conclusioni

In questo post abbiamo visto classi by-value e by-reference, le loro principali differenze, vantaggi e svantaggi. La domanda sorge quindi spontanea: le classi nei nostri applicativi dovrebbero essere by-value o by-reference? Come sempre, la risposta è… dipende. Dipende dall’architettura dell’applicazione, da come le classi devono interagire tra loro, dall’esperienza e dalle abitudini del programmatore e da eventuali ottimizzazioni di gestione della memoria che il programmatore vuole svolgere. Per esperienza, la prima scelta si orienta sempre verso le classi by-value. L’utilizzo però di classi by-reference è opportuno quando ciò permette di semplificare in modo significato il codice e quando si ha la necessità di maneggiare oggetti di grandi dimensioni.

Il prossimo post sarà la naturale prosecuzione di questo discorso. Vedremo infatti i principali meccanismi implementativi per ottenere una classe by-reference. Nel frattempo, per chi volesse approfondire le considerazioni di NI riguardo le classi by-value e by-reference vi lascio il seguente link:

LabVIEW Object-Oriented Programming: The Decisions Behind the Design

Grazie per il tempo dedicato alla lettura di questo articolo. Come sempre, nel caso di dubbi, richieste o semplici curiosità, sono disponibile qui.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Scroll to Top