Dopasowanie częstości chodzenia wątku

0

Może to powinno być w Delphi (bo w Delphi piszę) ale problem jest raczej natury ogólno-Windowsowej, więc daję tutaj.

Otóż mam program komunikujacy się z otoczeniem przez port LPT (lub w przyszłości kartę I/O jakąś może). Istotny jest timing - zmiany na porcie muszą być wykrywane z dużą precyzją czasową. Tzn. program nie musi wiedziec o nich natychmiast (choć bez przesady z opóźnieniami), ale musi wiedzieć możliwe dokładnie kiedy zaszły.

Zorganizowałem sobie to tak. Jest główny program, wątek obsługi portu i struktura komunikacji będącą dwoma buforami FIFO z dostępem chronionym sekcją krytyczną. Jeden bufor (nazwijmy go A) służy do komunikacji z programu do wątku, drugi (B) do komunikacji z wątku do programu.

Wątek obsługi portu chodzi w niedużej pętli, permanentnie przegląda port, i zagląda do bufora A, z którego zdejmuje "komendy" - odczytaj port, obserwuj piny takie-a-takie i zgłaszaj zmiany, itp. Jak ma odczytać, to wpisuje wynik do bufora B, jak zobaczy zmianę na obserwowanych pinach do też wpisuje ja do bufora B. Powtarza też "komendy" z A wpisując je do B dla kontroli czasu. Wszystkie "komendy" w buforach A i B mają zapisany czas z QueryPerformanceCounter, więc mogę miec pojęcie ile co zajmuje.
No a główny program jak ma czas zagląda do bufora B i zdejmuje odpowiedzi z atku przegladającego i robi z nimi co trzeba, w tym pisze do pliku, oraz wysyła komendy do bufora A.

Problem mam z sensownym dopasowaniem obciążenia procesora przez wątek przegladający port. Dałem go z wysokim priorytetem (tpTimeCritical, próbowałem też tpHighest) żeby nic go nie wywałaszczyło kiedy powienien zadziałać. Ale oczywiście wtedy użycie procesora skacze do 100% (w tej chwili 50% bo mam procesor HT). Dałem w pętli SwitchToThread; (oddaj do innego wątku) żeby nie zawieszał systemu - nadal 100(50)%. I nie dziwne, bo zwykle żaden inny wątek nie czeka, więc nie ma komu oddac procesora. Tak samo jak się da SleepEx(0,True) - który zasadniczo też powoduje oddanie time slice przez wątek. SleepEx(1, True) niby rozwiązuje sprawę - ale przesadnie, bo wartość 1 jest DWORD w milisekundach. A ja wolałbym, żeby wątek chodził gęściej, powiedzmy co 10-50 mikrosekund.

Macie jakiś pomysł jak go zmusić żeby chodził tak często bez przeciążania procka?

A jeśli nie, to czy ustawienie ze SwitchToThread lub SleepEx(1,True); i procesor wypełniony na maksa czymś grozi? czym?

A może jakis pomysł na inną organizację programu? (choć wolałbym nie, bo rzecz jest już napisana ;) )

0

Hmmm... Może przetestuj coś takiego:

{-----------------------------------------------------------------------------
  Procedure: usleep
  Author:    Qyon
  Date:      04-wrz-2005
  Arguments: usec:integer; bAlertable: boolean
  Result:    None
-----------------------------------------------------------------------------}
procedure usleep(usec:integer; bAlertable: boolean);
var WT: THandle;
    WaitResult : DWord;
    T64: Int64;
    st:SYSTEMTIME;
begin
  WT := CreateWaitableTimer(nil, false, 'WaitClock');
  GetSystemTime( st );
  SystemTimeToFileTime(st,_FILETIME(T64));
  t64:=t64+usec*10;
  SetWaitableTimer(WT, T64, 0, nil, nil, false);
  WaitForSingleObjectEx(WT, Infinite, bAlertable);
  CloseHandle(WT);
end;
0

Wygląda obiecujaco, popróbuję.

Mam jeszcze dwa pomysły co do uproszczenia i przyspieszenia.

  1. Spróbuję stworzyć obiekt Timera tylko raz (jako pole obiektu wątku) i tylko włączac i wyłączać.
  2. SetWaitableTimer może przyjmować czasy względne. Trzeba tylko podać wartość ujemną pDueTime. Uniknęłoby się pobierania czasu systemowego i konwertowania go na FileTime. Spróbuje tak zrobić.

Zobaczę co wyjdzie, dzięki.

0

Jak osiągniesz dobre wyniki daj znać - zrobi się jakieś faq z tego czy coś

0

Można ustawić odpowiednią klasę priorytetową procesu na RealTime, jest to najwyższa klasa priorytetowa, która "zawłaszcza" procesor niezależnie od innych wątków - niestety spowoduje dość znaczne utrudnienia np. szybka reakcja na zdarzenia myszki, klawiatury.

function SetPriorityClass(hProcess: THandle; dwPriorityClass:DWORD): BOOL; stdcall

dwPriorityClass na $100; // klasa RealTime.

0

Deti - nie doczytałeś dokładnie. Chodzi mi o stworzenie wątku chodzącego bardzo często, ale nie za często. Głównie marzę o instrukcji SleepEx mierzonej w mikrosekundach a nie w milisekundach. Najbardziej byłbym szczęśliwy, gdyby udało mi zmusić wątek do chodzenia z częstością zapewnioną w pewnym zakresie np żeby na pewno procedura wykonała się co 10 do 50 mikrosekund. He, he, pod Windows - realistą to ja nie jestem :-)

Ustawienie klasy priorytetu procesu na RealTime pomaga utrzymać górną granicę w rozsądnych wartościach o ile nie ma SwitchToThread. Mocniejszy wpływ ma istnienie bądź nie SwitchToThread - patrz testy poniżej.

Zrobiłem wątek testowy:

procedure TSThread.Execute;

begin
repeat
 QueryPerformanceCounter(fCurTime);
 if fPointer>0 then
   fDelayArray[fPointer]:=fCurTime-fPrevTime;
  inc (fPointer);
  fPrevTime:=fCurTime;
  SwitchToThread;  //czasem włączane czasem nie
until fPointer>=fArraySize;

i puściłem go na Pentium 4 2.8GHz HT (może potem wyłączę HT i popróbuję jak wtedy). W systemie było otwarte dużo programów, ale nic szczególnie obciążającego.

Wątek chodził 10 mln razy i zapisywał (jak widać powyżej) czasy pomiędzy obrotami.

Wyniki (rozkład czasów między obrotami, w sekundach)
najpierw ze SwitchToThread

normalna Priority class

<2.154E-007 0
<4.642E-007 0
<1.000E-006 9791910
<2.154E-006 199566
<4.642E-006 2870
<1.000E-005 1945
<2.154E-005 1463
<4.642E-005 1044
<1.000E-004 663
<2.154E-004 359
<4.642E-004 92
<1.000E-003 86
<2.154E-003 1
<4.642E-003 0
<1.000E-002 0
<2.154E-002 0
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

high

<2.154E-007 0
<4.642E-007 0
<1.000E-006 9746645
<2.154E-006 239144
<4.642E-006 7229
<1.000E-005 2451
<2.154E-005 1701
<4.642E-005 1362
<1.000E-004 856
<2.154E-004 410
<4.642E-004 115
<1.000E-003 80
<2.154E-003 5
<4.642E-003 1
<1.000E-002 0
<2.154E-002 0
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

realtime

<2.154E-007 0
<4.642E-007 0
<1.000E-006 9766323
<2.154E-006 219155
<4.642E-006 7963
<1.000E-005 2640
<2.154E-005 1437
<4.642E-005 1166
<1.000E-004 723
<2.154E-004 368
<4.642E-004 150
<1.000E-003 70
<2.154E-003 3
<4.642E-003 1
<1.000E-002 0
<2.154E-002 0
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

a bez SwitchToThread

normalna
<2.154E-007 0
<4.642E-007 8545466
<1.000E-006 1444239
<2.154E-006 8452
<4.642E-006 1651
<1.000E-005 40
<2.154E-005 78
<4.642E-005 49
<1.000E-004 21
<2.154E-004 3
<4.642E-004 0
<1.000E-003 0
<2.154E-003 0
<4.642E-003 0
<1.000E-002 0
<2.154E-002 0
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

high

<2.154E-007 0
<4.642E-007 8551371
<1.000E-006 1439348
<2.154E-006 7263
<4.642E-006 1640
<1.000E-005 33
<2.154E-005 204
<4.642E-005 109
<1.000E-004 25
<2.154E-004 6
<4.642E-004 0
<1.000E-003 0
<2.154E-003 0
<4.642E-003 0
<1.000E-002 0
<2.154E-002 0
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

realtime

<2.154E-007 0
<4.642E-007 8543219
<1.000E-006 1449336
<2.154E-006 6280
<4.642E-006 1093
<1.000E-005 53
<2.154E-005 18
<4.642E-005 0
<1.000E-004 0
<2.154E-004 0
<4.642E-004 0
<1.000E-003 0
<2.154E-003 0
<4.642E-003 0
<1.000E-002 0
<2.154E-002 0
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

Jak widać, w wersji z SwitchToThread, większość obrotów odbywa się między 0.46 a 1 us, ale zdarzają się też między 2 a 4 ms.

Bez SwitchToThread większość obrotów była miedzy 0.2 a 0.46 us, najdluzsze byly miedzy 100 a 200us dla Normal i High i miedzy 10 a 20 us dla RealTime (tu byl zysk z realtime).

Qyon: nawet z wyrzuceniem tworzenia timera poza pętlę nie udało mi się zejść nawet w pobliże pożądanych czasów. Wygląda, że procedura ustawienia timera trwa jakoś długo. Albo czegoś nie umiem. Może jeszcze popróbuję.

0

Rozważań ciąg dalszy:

Oto wyniki bez HT. Ogólnie są podobne. Różnica jest taka, że częściej obrót trwa minimalny czas (ponad 99.9% w najkrótszym przedziale, z HT było ok. 97% z SwitchToThread i ok. 85% bez SwitchToThread). Ale jak już coś mój watek wywłaszczy, to opóźnienia są z włączonym SwitchToThread są duże - 20-40 ms (z HT były 2-4 ms), bez SwitchToThread sa 100-200 us, czyli podobnie jak były, z wyjątkiem RealTime, gdzie z HT bylo 10-20 us. Wyniki:

Ze SwitchToThread

norm
<2.154E-007 0
<4.642E-007 0
<1.000E-006 9994933
<2.154E-006 1718
<4.642E-006 250
<1.000E-005 734
<2.154E-005 686
<4.642E-005 1058
<1.000E-004 283
<2.154E-004 169
<4.642E-004 54
<1.000E-003 42
<2.154E-003 32
<4.642E-003 12
<1.000E-002 21
<2.154E-002 7
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

high
<2.154E-007 0
<4.642E-007 0
<1.000E-006 9995158
<2.154E-006 1746
<4.642E-006 195
<1.000E-005 689
<2.154E-005 635
<4.642E-005 1094
<1.000E-004 216
<2.154E-004 89
<4.642E-004 61
<1.000E-003 57
<2.154E-003 20
<4.642E-003 8
<1.000E-002 27
<2.154E-002 4
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

realtime
<2.154E-007 0
<4.642E-007 0
<1.000E-006 9995415
<2.154E-006 1690
<4.642E-006 285
<1.000E-005 839
<2.154E-005 697
<4.642E-005 429
<1.000E-004 347
<2.154E-004 111
<4.642E-004 66
<1.000E-003 57
<2.154E-003 20
<4.642E-003 13
<1.000E-002 26
<2.154E-002 4
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

bez SwitchToThread:

norm
<2.154E-007 0
<4.642E-007 9998891
<1.000E-006 659
<2.154E-006 28
<4.642E-006 5
<1.000E-005 137
<2.154E-005 128
<4.642E-005 120
<1.000E-004 24
<2.154E-004 7
<4.642E-004 0
<1.000E-003 0
<2.154E-003 0
<4.642E-003 0
<1.000E-002 0
<2.154E-002 0
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

high
<2.154E-007 0
<4.642E-007 9998800
<1.000E-006 710
<2.154E-006 20
<4.642E-006 2
<1.000E-005 117
<2.154E-005 116
<4.642E-005 54
<1.000E-004 165
<2.154E-004 15
<4.642E-004 0
<1.000E-003 0
<2.154E-003 0
<4.642E-003 0
<1.000E-002 0
<2.154E-002 0
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

realtime
<2.154E-007 0
<4.642E-007 9998893
<1.000E-006 658
<2.154E-006 18
<4.642E-006 9
<1.000E-005 126
<2.154E-005 115
<4.642E-005 67
<1.000E-004 96
<2.154E-004 17
<4.642E-004 0
<1.000E-003 0
<2.154E-003 0
<4.642E-003 0
<1.000E-002 0
<2.154E-002 0
<4.642E-002 0
<1.000E-001 0
<2.154E-001 0
<4.642E-001 0
<1.000E+000 0

=1.000E+000 0
Total: 9999999

No to teraz: dobre jest to HT czy złe? :-)

Dobre było oczywiście to, że z HT obciążenie procka było rzędu 50% i system chodził gładko, bez HT dochodziło do 100% czasem i mysz skakała.

Chyba najlepiej miec osobne procesiry.

0

Przyznam się, że nie wiem jaki jest cel (może bardziej konkretne wyjaśnienie ?).

Chcesz jak najszybciej czytać z portu i nie obciążać procesora?

SetPriorityClass(GetCurrentProcess,REALTIME_PRIORITY_CLASS);
SetThreadPriority((tu watek),THREAD_PRIORITY_TIME_CRITICAL);
// SetThreadAffinityMask(watek,2); (to na drugi proc. ? maska bitowa !)

pomagało mi w niektórych przypadkach i udawało się uzyskiwać dość dobre rezultaty, jednak w pracy krokowej tj:

  1. czytanie informacji kilka razy (w moim przypadku z karta advantecha ? ale można powiedzieć z portu LPT ? nie ma znaczenia)

  2. obróbka sygnału

  3. i powrót do pkt 1)

Przy czym podczas 1) komp był zamrożony. No, ale to dobre dla okresowych zjawisk.

0
reichel napisał(a)

Przyznam się, że nie wiem jaki jest cel (może bardziej konkretne wyjaśnienie ?).

Chcesz jak najszybciej czytać z portu i nie obciążać procesora?

Chcę czytać szybko z portu - ale nie najszybciej. Tak, żeby nie obciążać na full procesora.

0

To ciężki temat, bo jest Ci potrzebna funkcja, która dokładnie odmierza czas.

Pewnie trzeba zrobić podobnie jak w przypadku mikroprocesorów (tam chyba łatwiej) np. w BASCOM jest waitms i tez jest ona potwornie niedokładna wiec robi się tak, że stosuje się wewnętrzny timer (kroki zależne od kwarcu).

No ale jak to zrobić w MS Windows - własny ster obsługujący coś 'pewnego' - timer?

spróbowałem coś takiego

var
 ts1,te1,ts,te,tick:int64;
 i:integer;
const
 cnt = 10000000;
begin
//ile na seknunde
 SetPriorityClass(GetCurrentProcess,REALTIME_PRIORITY_CLASS);
 SetThreadPriority(GetCurrentThread,THREAD_PRIORITY_TIME_CRITICAL);
 QueryPerformanceFrequency(tick);

//rozpedzamy procek :)
 for i:=1 to 100*cnt do
 asm
  mov ax,bx
 end;

 QueryPerformanceCounter(ts1);
 for i:=1 to cnt do
 begin
 end;
 QueryPerformanceCounter(te1);

 QueryPerformanceCounter(ts);
 for i:=1 to Cnt do
 asm
   nop
 end;
 QueryPerformanceCounter(te);

 caption := format('%17.17f',[(te-ts-te1+ts1)/(tick*cnt)]);

Niestety HT jest tak wredne, że zawsze ma siłe (chyba, że zapchać drugi 'proc' ale to się mija z celem) i coś może przeszkodzić.

jesli

cnt = 10000000000; (duze)

to wyniki są powtarzalne, jeśli cnt (100000) małe zmieniają się (che che od 1-9)
natomiast jeśli rozpędzić procek (mov ax,bx) to stają się znów powtarzalne (proces musi mieć czas na wywłaszczenie sobie czasu procesora i te pierwszych kilka wyników znacznie się różni - przynajmniej u mnie).

jestem bardzo ciekaw czy da się zrobić SleepUS(mikrosekundy), teoretycznie takt procesora to cos koło 1/x(GHz), nie wiem dokładnie ile na instrukcje potrzebują nowe proce no ale to i tak spory zapas.

0

To może taki z innej beczki trochę, ale czy przypadkiem, przy zmianie stanów na LPT nie może zostać wyzwolone przerwane? Wiem, że do tego potrzeba by napisać sterownik, ale gra byłaby chyba warta świeczki?

0

Też mi to przyszło do głowy, ale na razie nawet nie zacząłem myśleć ani czytać na ten temat.

Na razie najbardziej zaprząta mnie kwestia synchronizacji między kanałami wielokanałowej karty dźwiękowej :-).

0
pq napisał(a)

Na razie najbardziej zaprząta mnie kwestia synchronizacji między kanałami wielokanałowej karty dźwiękowej :-).

O! Wreszcie coś w tym wątku, co mnie ruszyło :D Możesz rozwinąć temat?

Jacek

1 użytkowników online, w tym zalogowanych: 0, gości: 1