Newsgroup: it.comp.lang.c
Titolo: Multi thread

Ciao a tutti.
Avrei bisogno di un suggerimento.
 
PREMESSA
Ho una piastra SDR che utilizzo per acquisire un segnale radio.
La piastra imposta tutti i parametri caratteristici e mi restituisce il segnale numerico in banda base.
 
Le capacita' eleborative della piastra sono un compromesso legato alla necesitta' di avere un dispositivo leggero e poco ingombrante pur mantenendo le capacita' elaborative.
 
Allo stato attuale attualmente il codice lavora allocandosi il vettore "piu' lungo" che non causi un crash del codice, immagazzina i dati ricevuti in questo vettore ed alla fine li scrive su file.
 
In questo contesto, al massimo, riesco ad ottenere spezzoni di segnale lunghi 43 secondi.
 
Chiedendo a persone con maggior esperinza, una possibile soluzione per superare questo vincolo dei 43 secondi e' quella di ricorrere ad un sistema multi-thread seguendo la filosofia dei due processi Produttore-Consumatore.
Ossia il produttore acquisisce sequenzialmente blocchi di lunghezza prefissata di dati e li immagazzina in un buffer circolare ed il Consumatore accede alla medesima area di memoria e scrive questi blocchi su file.
 
Il buffer circolare lo ho implementato semplicemente indirizzando l'elemento i-esimo del vettore come
Vect[i%lenVect] = x;
in questo modo, facendo crescere i all'infinito, si accede sempre al medesimo vettore, lasciando il tempo al consumatore di svuotare le aree del buffer che saranno riempite dal produttore al prossimo giro.
Ho una variabile mutex che controlla se i due processi stanno accedendo contemporaneamente alla stessa area di memoria e li blocca in modo mutuamente esclusivo.
 
Prima di implementare il codice complessivo, per separare i problemi ed essere sicuro che la parte multi-thread funzionasse correttamente, ho sviluppato una versione "simile" che non utilizzasse le feature della piastra ma semplicemente riempisse blocchi del vettore con numeri sequenziali nel produttore e che detti blocchi fossero salvati nel consumatore, riuscendo a salvare spezzoni equivalenti a circa 87 secondi (sono consapevole che non si puo' arrivare all'infinito perche comunque la scrittura su file e' un'operazione molto piu' lenta dell'acquisizione dei dati)
 
Il codice reale invece, funziona correttamente per sequenze molto corte ma quando le allungo alla fine la piastra va in over-run ed il codice va in segmentation fault. E' come se le capacita' elaborative richieste per effettuare tutte le operazioni relative alla ricezione del segnale radio, vadano in conflitto con il thread che deve scrivere su file.
FINE PREMESSA
 
Dopo questa lunga premessa la domanda:
Esiste un criterio per stabilire se oramai ho raggiunto la massima capacita' elaborativa della macchina di calcolo in un sistema multi thread oppure ho ancora margine per ottimizzare il codice per superare le attuali prestazioni?
 
E da una settimana che ci provo senza successo e non vorrei "star solo raschiando il fondo del barile" 
 
Grazie

 lasciando il tempo al consumatore di svuotare le aree del buffer che saranno riempite dal produttore al prossimo giro.
 
A occhio, farei ricorso ad un segmento di shared-memory utilizzando le 
Interprocess Communication API (vedi sys/ipc.h e sys/shm.h) 
dipendentemente dal fatto che siano supportate dal tuo compilatore/sistema.
Evitando quindi il vettore, 'attaccando' semplicemente il tuo elemento 
alla shared-memory.
Guarda qua 
https://users.cs.cf.ac.uk/Dave.Marshall/C/node27.html
 c'e' 
anche un esempietto di utilizzo di server (Produttore) e 
client(Consumatore).
In sostanza, il tuo vettore viene sostituito dal segmento di shared 
memory, che non dovrebbe allargarsi piu' di tanto se entrambi i processi 
Produttore-Consumatore sono attivi contemporaneamente.
 
 
---
Questa e-mail è stata controllata per individuare virus con Avast antivirus.
https://www.avast.com/antivirus
 
 


[X-X]
Manca qualche informazione, come ad esempio quale sistema operativo ti
supporta, per darti dei suggerimenti validi. In ogni caso potrei
proporti di evitare mutex, signal e buffer circolari in favore di una
decisa semplificazione aprendo un canale di pipe tra produttore e
consumatore che ti offrirà sia il passaggio dei dati tra i 2 task sia la
sincronizzazione in un unica soluzione.
 
Un saluto.

 Un saluto.
 
il sistema oprativo è linux.
Uno dei problemi è la temporizzazione.
I dati sono ricevuti in blocchi di 1016 ad 1.92MSPS.
Quindi ogni blocco viene ricevuto circa ogni 529 microsecondi.

 Il codice reale invece, funziona correttamente per sequenze molto corte ma quando le allungo alla fine la piastra va in over-run ed il codice va in segmentation fault. E' come se le capacita' elaborative richieste per effettuare tutte le operazioni relative alla ricezione del segnale radio, vadano in conflitto con il thread che deve scrivere su file.
 
i%lenVect non e' un gran sistema, soprattutto lasciare che i vada all'infinito (e quindi prima o poi vada in overflow).
Spero che i e lenVect sono dichiarati unsigned di grandezza opportuna.
Meglio un 
 
i++;
if(i >= lenVect) i = 0;
 
Oppure fai in modo che lenVect sia una potenza di 2 e quindi:
 
i++;
i &= lenVect-1;
 
cioe' se ad esempio lenVect == 256 -> i &= 255;
 
Cmq prima di sbattere la testa con shared memory &c., fai due calcoli per essere sicuro che ce la fai a gestire il tutto: quanti byte/sec in entrata dalla radio? quanti byte/sec riesci a scrivere sul disco? nel mezzo devi fare calcoli? se si quanti byte/sec riesci a processare?
 
Ciao Jack


beh... allora con la soluzione che ti ho proposto dovresti starci 
veramente comodo...
 
crei un canale di pipe: r, w
 
if(fork())
    t2();
else
    t2();
 
t1()
{
    while(!STOP)
    {
       Acquisizione();
       Write(w);
    }
}
t2()
{
    while(Read(r))
       Write(file);
}
 
dovrebbe essere tutto ciò che ti serve o, comunque, una prova val la 
pena farla.

 sincronizzazione in un unica soluzione.
 
Al pipe avevo pensato in prima istanza, ma mi e' sembrato vincolante, in 
quanto i due processi, producer e consumer, dovrebbero essere sincroni, 
quindi il producer non sarebbe libero di ascoltare se non quando il 
consumer abbia liberato il pipe.
Ho trovato, invece che il sistema della shared-memory sia altrettanto 
semplice, sicuramente applicabile in un sistema linux e soprattutto 
asincrono. Inoltre, qualora un unico consumer non fosse abbastanza 
rapido a rendere persistenti i buffer, tale architettura potrebbe 
implementare piu' consumer, rispettando anche l'ordine dei buffer ricevuti.



Più costumer implicherebbe un'architettura più complicata perché siano 
uno o molti sempre con un disco hanno a che fare e, colmati i relativi 
buffer il sistema andrebbe comunque in overrun a meno di non scrivere su 
dischi diversi e poi ricomporre le informazioni successivamente.


> Il 10.05.2017 10.15, jak ha scritto:
[snip]
ps:
con questo non sto assolutamente affermando che la tua soluzione sia 
meno valida... anzi. Forse solo un pochino più complicata da realizzare.

 dischi diversi e poi ricomporre le informazioni successivamente.
 
Sarebbe peggio se il producer, bloccato sul pipe, perdesse i buffer in 
arrivo.
Tutto sommato, se i buffer da 1016 byte arrivano cadenzati ogni 500 
microsecondi, come dice l'OP, sara' difficile che non si faccia a tempo 
a scriverli su disco.
L'OP sostiene che la velocita' di ricezione e' maggiore di quella del 
disco, secondo me un disco sas, la cui velocita' di trasferimento puo' 
arrivare a 6Gbps, in 500 microsecondi di buffer da 1016 ne scrive almeno 
350.
Poi magari ho sbagliato a fare i conti!
:-)



Ci mancherebbe, ho capito la tua obiezione.
Ovvio che c'e' la 'complicazione' della creazione e condivisione della 
shared memory, ma dal punto di vista del programma, invece di scrivere 
su un pipe, scrive su una shm: praticamente cambia il nome della funzione.



XD
sas? esagerato! ;-) Non occorre nemmeno quello se si pensa al buffering 
del controller del disco, quello del sistema, quello della funzione di 
scrittura, quello del...
...in realtà l'unico caso che vedo in cui porebbe verificarsi un 
problema è quello in cui il producer trovi il buffer del canale di pipe 
pieno ed in quel caso rimarrebbe appeso ma succederebbe anche nel caso 
della shared memory.
 

 :-)
 
non e' detto che l'OP possa collegare un disco SAS o un SSD al sistema, magari e' una cosa tipo una Raspberry Pi...
 
Ciao Jack


Eh va beh... farà un investimento per una sd in classe 10 :-)
 


Non e' una Rasberry Pi ma una Ettus E310 
https://www.ettus.com/product/details/E310-KIT
 che effittivamente ho come memoria su cui salvare una SD.
Mi era stato ventilato il problema che il controller della SD potesse rallentare e causare un collo di bottiglia nella scrittura ma nella prova che ho raccontato fatta solo per verificare il multi thread tale collo di bottiglia non c'e' stato.
Potebbe anche essere legato al fatto che il device fosse impegnato meno e potesse piu' agilmente gestire la scrittura su sd.
La piastra ha due porte usb2. Collegargli un hd esterno pero' tipo un SSD non sarebbe comunque penalizzato dal non avere un'interfaccia piu' veloce (SATA, USB3 etc.)?

 della shared memory.
Dici?
Non ho controllato, per quanto mi ricordi:
- il pipe (che non uso praticamente mai) e' sicuramente bloccante, 
ovvero non riesci a scrivere finche' dall'altro lato del pipe non e' 
stato letto il buffer presente,
- la scrittura di un segmento di shm non e' mai bloccante.
 
Il caso sicuramente in comune alle due soluzioni, e' che il processo 
consumer cada. Per questo ipotizzavo piu' consumer, facile da 
implementare con shm, un po' piu' complicato con il pipe.
Poi se vuoi possiamo discuterne, che un sano confronto arricchisce sempre.



La SD e' di 32GB e di classe U 3 (con il 3 dentro la U) superiore alla 10

Il giorno mercoledì 10 maggio 2017 11:51:29 UTC+2, jak ha scritto:

eh ma il problema non e' solo la SD, e' anche l'interfaccia, il filesystem, ecc.

 eh ma il problema non e' solo la SD, e' anche l'interfaccia, il filesystem, ecc.
 
Sacrosantamnete vero


ma l'OS e' sull'SD card?
Se si io proverei ad attaccarci un HD USB (non deve per forza essere SSD) e scrivere i dati li.
Inoltre visto che hai linux, ci pensa lui a bufferizzare le scritture, non serve sbattersi a fare scritture bloccanti.
L'unica cosa e' controllare che il buffer dell'OS non sia pieno quando fai un write() (mi pare che si possa controllare, ma e' un sacco di tempo che non programmo in linux, quindi...)
 
Ciao Jack


In realtà la scrittura nel pipe non è bloccante anche se appare tale in 
determinate condizioni; nel senso che dipende da come la utilizzi. Il 
canale di pipe ha un buffer interno e, se questo viene riempito, la 
scrittura viene sospesa (si può anche decidere di configurarla in modo 
che non sia sospensiva), quindi è ovvio che, finché il buffer o parte di 
esso non viene svuotato da una lettura si venga sospesi in scrittura. Il 
gioco sta proprio lì... cioè si cambia la dimensione del buffer interno 
della pipe (che di default dovrebbe essere di 4 kb) in modo che ci sia 
spazio per un paio di scritture asincrone, ad esempio all'OP potrebbe 
andare bene 1016 * 4 bytes. Se, come i nostri calcoli ci indicano, il 
customer riesce e superare in velocità il producer, questo, in teoria, 
non dovrebbe mai rimanere sospeso nella write.
Con 'succederebbe anche nel caso della shm' intendevo dire che se il 
producer fosse abbastanza in ritardo da non aver ancora letto nessuna 
parte della memoria sharata, il producer dovrebbe esser messo in attesa 
per evitare la sovrascrittura proprio come accade se si è riempito il 
buffer della pipe.


Ho fatto alcune prove su scrittura su USB ma il processo di arresta subito.
Riesco ad aprire il file e subito muore.
Soluzione piu' interessante e' quella di montare un'area di memoria esterna via sshnf e quindi scrivere attraverso la porta ethernet ma comunque sono ben lontatno dai 43 secondi



Per pura curiosità: ma cosa stai campionando a 1.92MS? Un segnale video?

 Soluzione piu' interessante e' quella di montare un'area di memoria esterna via sshnf e quindi scrivere attraverso la porta ethernet ma comunque sono ben lontatno dai 43 secondi
 
dividi il buffer da 43 secondi in due da 21 secondi, quando uno e' pieno, inizia a riempire il secondo e il primo lo scrivi su file (su disco USB, sd card,...). Poi scrivi il secondo su file e riempi il primo e cosi via.
 
Ciao Jack


1.92 Mega Samples al secondo e' la freqeunza a cui qualsiasi terminale mobile inizia a campionare un segnale LTE.
Dopo una prima fase di decodifica, il ricevitore scopre (legge dai dati ricevuti) qual e' il cell Id della cella, quante porte stanno trasmettendo (caso MIMO), e la larghezza di banda della cella a cui si e' attaccato (a priori non si puo' sapere perche' l'LTE puo' avere una largezza di banda di 1.4MHz, 3MHz, 5MHz, 10MHz, 15MHz e 20 MHz.
Sulla base della larghezza di banda cambia la frequenza di campionamento 
1.4 --> 1.92
3   --> 3.84
5   --> 7.68
10  --> 15.36
15  --> 23.04
20  --> 30.72MSPS
 
Non mi dilungo sul perche' a me basti restare a 1.92MSPS.
 
fino a 20MHz che e 30.72MSPS
 
 

> fino a 20MHz che e 30.72MSPS
" Questa frase e' rimasta ma dovevo concellarla - Scusatemi "


Infatti non lo so. Il senso era che qualsiasi cosa che abbia una 
velocita' di trasferimento inferiore ai 2MB/sec sarebbe insufficiente. 
Una usb 2.0, per esempio, potrebbe essere piu' che sufficiente, poi 
dipende da cosa ci attacchi. Spesso accade che per certi dispositivi, la 
velocita' in scrittura sia di molto inferiore a quella di lettura.
Fatti i suoi conti e i suoi test, l'OP dovra' pur scegliere 
un'architettura che gli permetta di fare quel che vuole fare.



Una precisazione: quando parlo di burts di 1016 elementi, bisogna considerare 2 flussi complessi dove parte reale e parte immaginaria sono sotto forma di short  ossia 2 byte.
Quindi facendo la somma
 
Freqeunza di campionamento 1.92 MSPS ==> 1/fs * 1016 = 529 Micosecondi
Lunghezza burst             1016
byte totali 1016 * 2 * 4 = 8128 Byte ogni 529 microsecondi

Volevo fare una richiesta a Dr.UgoGagliardelli relativo alle shared memory.
 
Nella mia esperienza di programmatore c, le sm le ho usate solo una volta in cui in un sistema di misura, dovevo effettuare delle acquisizioni nella tratta di ricezione e trasmissione contemporaneamente. 
Ho allora usato una fork che generava due processi e per poter scambiari i dati questi processi usavano appunto la shared memory.
Io poi probabilmente non le usavo nel modo piu' furbo possibile ed ogni variabile che dovevo condividere tra i due processi era diventata una shared memory.
 
Quello pero' era un programma lungo con tante subroutine e complesso.
 
Quello di cui invece sto parlando e' molto piu' semplice ed e' costituito da un unico main con un centinaio ri righe di codice.
Mettendo allora le variabile che devo condividere tra i due thread in global non e' come se stessi usando una shared memory?
 
Cosa mi sfugge.
 
Nel link che proponi il problema e' far dialogare due programmi distinti tra di loro per mezzo di una shared memory ma nel mio caso, forse e sicuramente per la mia scarsa conoscenza della materia, non ne riesco a capire l'utilita'.
Ti ringrazion per ogni chiarimento che vorrai darmi
Daniele 

 conoscenza della materia, non ne riesco a capire l'utilita'.
 
L'utilita' e' quella di avere due processi separati, producer e 
consumer, che non si pestano i piedi, ovvero che l'attivita' dell'uno 
non rallenti l'attivita' dell'altro.
La stessa cosa la puoi realizzare anche con il pipe, come dice jak, ma 
anche con una coda IPC. Ho valutato che la shm sia preferibile per 
evitare eventuali perdite di dati se il processo consumer cade ed 
implementare piu' consumer concorrenti con shm sia piu' semplice, almeno 
per me.
Non so quanto sia importante, a me sta solo di segnalarlo.


Prima di tuuto vorrei ringraziare TUTTI per gli spunti di riflessione che mi avevte dato ma vorrei riportare la discussione al tema originale.
 
Quello che avevo chiesto al gruppo era se, avendo implementato un processo, era possibile verificare se quello che chiedo al processore è superiore alle sue forze oppure ho ancora spazio per cercare di ottimizzare.
 
Relativamente poi all'uso di pipe e fork, cercando in internet ho letto che la differenza tra un thread ed un processo generato da una fork sta nel fatto che questo ultimo è essenzialmnete una copia del processo che lo ha generato mentre un thread è qualcosa di molto più leggero (questo il succo non mi sono segnato tutti i particolari). Ora essendo nella condizione di sospettare di sforzare troppo il processore l'idea di fare una fork non dovrebbe essere sconsigliata?
grazie Daniele


yes, usare dei thread nello stesso processo è molto più performante che
attivare processi diversi.
La shared ram è la modalità con la più ampia banda di comunicazione tra
processi *diversié: se lavori con UN solo processo e usando thread al 
suo interno, non serve, basta allocare lo spazio che ti serve e 
condividere un puntatore globale: questo massimizza le prestazioni
rispetto all' overhead aggiunto dalle chiamate di sistema.
Per quando riguarda la gestione, io implementerei un buffer circolare
di N elementi, ognuno con granularità pari ai tuoi 8K, il thread
che scrive e quello che legge si sincronizzano con i due puntatori
e non c'e' conflitto. Per sicurezza puoi mettere un mutex per proteggere
le operazioni di accesso concorrente ai pointer in scrittura.
Tieni conto che Linux non è un sistema operativo real-time, per cui
puoi avere latenze introdotte dagli altri porocessi attivi.
Bye
 
 
 


 
In realtà questa è una prova che dovrai fare perché ciò che ottieni da 
una fork e ciò che ottieni da un thread ha un'altra differenza da non 
sottovalutare e cioè un thread condivide lo stesso 'core' con il 
programma originante mentre un processo fork no.

 Bye
 
Grazie CortexA57 per la sua risposta puntuale.
Questo pero' mi porta a concludere che ho poche speranze e che mi devo rasseggnare ai 43 secondi.
Difatti, con il programmino "fake", simulando il ritardo di 529 micorsecondi nella subroutine di lettura con un usleep, riuscivo a salvare su sd dati equivalenti a circa 84 secondi utilizzando un "vettore indicizzato a buffer circolare" lungo 83000 * 1016 che corrisponde a circa 84 Milioni di campioni e facendo 84 / 1.92 = 43.75 ossia la capacita' che usavo nella versione senza thread. Serve cosi' lungo anche perche' il puntatore che scrive e quello che legge si ricorrono lungo il vettore ed alla fine, inesorabilmente, quello che legge raggiunge quello che scrive. Se indico con iRead ed iWrite gli indici che indicano quanti elementi sono stati letti e quanti scritti, l'incontro tra i due avviene quanto (iRead-iWrite)>= lenVect ed allora devo stoppare il tutto altrimenti sovrascriverei elementi non ancora salvati. Far funzionare tutto il sistema embedded, evidentemente, mi limita le risorse che sono disponibili e non posso fare di piu'.
Pazienza. Basta saperlo per non continuare a cercare di superare un limite al momento invalicabile.

Z se xxx aww sss a Sees d

 
Sospetto che il tuo gatto abbia camminato sulla tua tastiera.
:-)