mboost-dp1

MSChart og live data (C#)


Gå til bund
Gravatar #1 - Emil Melgaard
27. feb. 2011 19:40
Jeg er ved at lave noget der minder om et USB-Oscilloskop. Det består af en USB-enhed der hele tiden sender spændingen som en byte, og et C# program (Windows Forms) der bruger LibUsbDotNet til at modtage data (som en event) og MSChart med en FastLine Series til at vise dem.

Jeg har dog nogle problemer med performance:

1. Det ser ud til at jeg ikke kan tilføje punkter mens den opdaterer grafen. Det betyder at jeg ikke kan nå at gemme en masse datapunter mens jeg f.eks. maksimerer vinduet.

2. Den opdaterer grafen hver gang jeg tilføjer et nyt punkt. Dette er slet ikke nødvendigt i mit tilfælde og sammen med det andet problem gør det, at jeg ikke kan nå at gemme så meget data som jeg gerne ville.

Jeg tilføjer punkter med funktionen:

// Function for adding a point to a series on the chart.
private void AddPoint(int Series, byte Value)
{
double time = mWatch.Elapsed.TotalMilliseconds;
chart.Series[Series].Points.AddXY(time, Value);
}


Men da USB eventen sker i en anden thread, bliver jeg nødt til at gøre det med delegates og invoke:

// Thread safe function for adding a point to a series on the chart.
private void AddPointInvoke(int Series, byte Value)
{
// Check if this method is running on a different thread
// than the thread that created the control.
if (this.chart.InvokeRequired)
{
// It's on a different thread, so use Invoke.
AddPointCallback d = new AddPointCallback(AddPoint);
this.Invoke(d, new object[] { Series, Value });
}
else
{
// It's on the same thread, no need for Invoke.
AddPoint(Series, Value);
}
}


Jeg håber at I kan hjælpe mig med at løse problemerne. Det vigtigste er at den altid kan gemme punkterne så hurtigt som muligt. Jeg vil meget gerne op på 1000 punkter pr. sekund. Hvor hurtigt grafen bliver opdateret med punkterne er ikke så vigtigt.
Gravatar #2 - Windcape
27. feb. 2011 19:48
Emil Melgaard (1) skrev:
1. Det ser ud til at jeg ikke kan tilføje punkter mens den opdaterer grafen. Det betyder at jeg ikke kan nå at gemme en masse datapunter mens jeg f.eks. maksimerer vinduet.
Det lyder forkert i mine øre. At tilføje mere data til en liste, burde ikke blive påvirket af at UI tråden er ved at re-renderer.

Emil Melgaard (1) skrev:
2. Den opdaterer grafen hver gang jeg tilføjer et nyt punkt. Dette er slet ikke nødvendigt i mit tilfælde og sammen med det andet problem gør det, at jeg ikke kan nå at gemme så meget data som jeg gerne ville.
I så fald, kunne du jo bare lave en kø, og så kun opdatere grafen for hvert n antal ting?

Ellers skal MSChart have en explicit mulighed for ikke at invalidate (redraw) hver gang du tilføjer data. Men det tvivler jeg lidt på at det har.

Emil Melgaard (1) skrev:
Jeg vil meget gerne op på 1000 punkter pr. sekund.
Men ikke med 1000 FPS vel?
Gravatar #3 - Windcape
27. feb. 2011 19:55
Teoretisk løsning:

1) Begynd indlæsning af n (f.eks. 1000) punkter fra dit USB-Oscilloskop

2) Når points.Count == n, fyr en invoke af med alt dit data

3a) Ryd listen af indlæste punkter (Data tråden)
3b) Tilføj til grafen og render (UI tråden)

Måske skal dine n punkter kopieres over i et seperat set, før du sender dem til invoke. Det finder du nok ud af ved testing.
Gravatar #4 - Emil Melgaard
28. feb. 2011 08:38
Windcape (2) skrev:
Det lyder forkert i mine øre. At tilføje mere data til en liste, burde ikke blive påvirket af at UI tråden er ved at re-renderer.


Jeg kan vise dig et eksempel på hvorfor jeg tror det er sådan:

http://img819.imageshack.us/i/osc.png/

I eksemplet skifter jeg mellem at sende 2 værdier så hurtigt jeg kan efter hinanden.

Her er en forklaring på hvad der sker ved de forskellige markeringer i eksemplet:

1. Her ser der ud til at gå fint, bortset fra at jeg gerne ville have at punkterne lå tættere.

2. Her maksimerer jeg vinduet.

3. Her kører den med vinduet maksimeret. Som du kan se, får jeg endnu færre punkter her end i 1.

4. Her minimerer jeg vinduet igen.

5. Her kører den så som den gjorde i 1.

Windcape (2) skrev:
I så fald, kunne du jo bare lave en kø, og så kun opdatere grafen for hvert n antal ting?


Mange tak for forslaget. Jeg har nu lavet det så jeg samler 10 punkter sammen inden jeg sender dem med invoke, og det har fået min sample rate op på over 1000, men jeg har dog stadigvæk det problem der er vist i eksemplet fra før.

Windcape (2) skrev:
Ellers skal MSChart have en explicit mulighed for ikke at invalidate (redraw) hver gang du tilføjer data. Men det tvivler jeg lidt på at det har


Der findes funktionerne:

chart.Series[0].Points.SuspendUpdates();
chart.Series[0].Points.ResumeUpdates();

Men det løser ikke problemet at bruge dem, for den laver alligevel en invalidate hver gang man laver noget om på grafen (f.eks. maksimerer vinduet).

Windcape (2) skrev:
Men ikke med 1000 FPS vel?


Nej, bare 10-20 FPS, men med en opløsning på 1000 punkter pr. sekund. F.eks. hvis jeg definerer at grafen skal vise de sidste 3 sekunder, skal der være omkring 3000 punkter.
Gravatar #5 - Windcape
1. mar. 2011 15:36
Emil Melgaard (4) skrev:
men jeg har dog stadigvæk det problem der er vist i eksemplet fra før.
Men stopper den opsamlingen af punkerne mens du forstørre, eller glemmer den bare at renderer dem bagefter?

Det sidste lyder mere plausibelt i mine øre. Hvad har du på din første-akse? Er det tid?

Emil Melgaard (4) skrev:
Nej, bare 10-20 FPS, men med en opløsning på 1000 punkter pr. sekund.
Godt godt :)

Det var nu mere, at hvis du forsøge 1000 invokes i sekundet, ville det jo være 1000 redraws, og dermed 1000 fps.
Gravatar #6 - Emil Melgaard
1. mar. 2011 15:57
Windcape (5) skrev:
Men stopper den opsamlingen af punkerne mens du forstørre, eller glemmer den bare at renderer dem bagefter?

Det sidste lyder mere plausibelt i mine øre. Hvad har du på din første-akse? Er det tid?


Det ser ud til at den stopper opsamlingen af punkter når jeg forstørrer grafen eller ændrer på akserne. X-aksen er den tid jeg får fra mWatch (et Stopwatch objekt), så hvis jeg får tilføjet nogle punkter i tiden mens jeg forstørrer, burde den vel vise dem rigtigt på grafen når den begynder at render igen?

Jeg kan se at USB-enheden ikke sender nye data før den forrige event er færdigbehandlet, så hvis den er lang tid om at gemme punktet, vil jeg modtage færre punkter.

Nu siger du at du ikke mener at listen burde blive låst når grafen bliver forstørret, men kan der så være tilfælde hvor Stopwatch objektet kan være længe om at give tiden?

Findes der evt. en måde hvor jeg kan sætte et funktionskald "i kø" til når programmet ikke har noget at lave, således at jeg kan afslutte USB-eventens funktion med det samme?
Gravatar #7 - Windcape
1. mar. 2011 16:08
Emil Melgaard (6) skrev:
men kan der så være tilfælde hvor Stopwatch objektet kan være længe om at give tiden?
Ja, det synes jeg er mere sandsynligt.

I stedet vil jeg anbefale at du aflæser tiden samtidigt med du læser fra USB'en, dvs. fra for din nuværende: List<byte> points, laver du en Dictionary<DateTime, byte> points, og så mapper Keys til første-aksen, og Values til anden-aksen.

Emil Melgaard (6) skrev:
Findes der evt. en måde hvor jeg kan sætte et funktionskald "i kø" til når programmet ikke har noget at lave, således at jeg kan afslutte USB-eventens funktion med det samme?
Rent principelt kan du smide delegates ind i Queue<T>, og så eksekvere dem efterhånden.

Noget i stil med.


var actions = new Queue<Action>();
actions.Enqueue(() => { UIInvokeKodeHer });


http://msdn.microsoft.com/en-us/library/7977ey2c.a...
Gravatar #8 - onetreehell
1. mar. 2011 20:56
Hej

Jeg tænkte at det måske kunne være at linjen
chart.Series[Series].Points.AddXY(time, Value);
blokerer når UIet skal genoptegnes. Som jeg forstår det er det noget grafik-halløj der bliver kaldt Add.XY på, er det rigtigt?
I så fald kunne en løsning være, lidt ligesom windcape har foreslået lige ovenfor, at man har en tråd der læser fra USBen, tager tid og sætter det ind i en kø. I pseudo-kode:
Queue af (byte, time) par q;
while (true)
byte reading = usb.getReading();
time time = getTime();
q.put(reading, time);

Og så tager man parene ud i en anden tråd og giver dem videre til UIen:

while (true)
byte reading, time time = q.get();
chart.Series[Series].Points.AddXY(time, reading);

Da der kun er en producer og en consumer kan det gøres helt uden låse. Dog kan det være smart med en semafor så consumeren ikke behøver at busy-waite på at der kommer noget i køen.

Men det kan være det slet ikke er det der er problemet :P

Jeg har aldrig skrevet C# (arh, jeg skrev engang <100 linjer til mono for at finde ud af at mono ikke understøttede særligt meget dengang).
Gravatar #9 - Windcape
1. mar. 2011 21:06
#8

C# har desværre ikke (rigtige) tuples :((
Gravatar #10 - onetreehell
1. mar. 2011 21:09
#9
Det er sådanset også ligemeget.
Gravatar #11 - Windcape
1. mar. 2011 21:11
#10

Ja, det det nemt kan løses uden. Jeg overvejede dog at foreslå List<Tuple<DateTime, byte>> før jeg skrev dictionary.

Men jeg savner stadigvæk muligheden for at kunne hente tuples ud som a, b = tuple :(
Gravatar #12 - Emil Melgaard
2. mar. 2011 19:57
Tak for hjælpen begge to. Jeg endte med at lave det med en Queue af points der bliver fyldt op af USB-eventen og tømt af en Timer. Det ser ud til at være den hurtigste løsning (og virker thread-safe nok til mig).

Jeg har dog stadigvæk ikke helt løst problemet. Til gengæld har jeg fundet ud af, at det ikke kun er når jeg laver ting med grafen at jeg mister punkter. Selv hvis jeg slet ikke gemmer punkterne, får jeg væsentligt færre punkter hvis jeg f.eks. bare flytter rundt på et tilfældigt explorer vindue.

Det ser derfor ud til at være et problem med libusb eller windows' behandling af USB.

Den computer jeg arbejder på er en gammel bærbar med en Pentium M processor og Windows XP. Jeg har en anden væsentlig kraftigere bærbar med dual core og Windows 7 som jeg også har testet på engang imellem, og der kører den væsentligt bedre så længe jeg kører med kø-systemet og ikke invokes.
Gravatar #13 - onetreehell
4. mar. 2011 08:25
Jeg gad godt vide om du får samme problem hvis du f. eks, starter et program er går i uendelig løkke.
Gravatar #14 - Emil Melgaard
4. mar. 2011 18:57
onetreehell (13) skrev:
Jeg gad godt vide om du får samme problem hvis du f. eks, starter et program er går i uendelig løkke.


Det gør jeg.

Nu har jeg lige lavet en lignende test hvor jeg bare kører rundt i en løkke og tæller op til 100.000.000:

Stopwatch watch = new Stopwatch();
int samples = 0;
double startTime = 0;
watch.Start();
while (true)
{
samples++;
if (samples >= 100000000)
{
double stopTime = watch.Elapsed.TotalMilliseconds;
Console.WriteLine((100000.0 / (stopTime - startTime)).ToString());
samples = 0;
startTime = stopTime;
}
}


På min bærbare kører den med 200 Mloops/s når jeg ikke laver noget, men hvis jeg begynder at flytte rundt på et vindue med en mappe, kommer den helt ned på 40 Mloops/s.

På min stationære kører den med 2000 Mloops/s uanset hvad jeg rykker rundt på af vinduer.

Jeg prøvede også at lave et C-program der gjorde det samme, og der var problemet ikke helt så stort. Der fik jeg hhv. 1000 Mloops/s og 700 Mloops/s på min bærbare og 3000 Mloops/s på min stationære.
Gravatar #15 - Windcape
4. mar. 2011 19:17
Prøv at lave løsningen med manuel tidsudregning, frem for at bruge stopwatch klassen.
Gravatar #16 - onetreehell
4. mar. 2011 20:32
#14
Jeg tænkte nu mere på om du taber datapunkter hvis du kører en uendelig løkke på samme tid. Det jeg tænker på er om det skyldes noget OS-halløj eller bare om din process ikke får nok processortid til at fange alle punkterne.
Gå til top

Opret dig som bruger i dag

Det er gratis, og du binder dig ikke til noget.

Når du er oprettet som bruger, får du adgang til en lang række af sidens andre muligheder, såsom at udforme siden efter eget ønske og deltage i diskussionerne.

Opret Bruger Login