Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Sleep - Programm läuft ohne Delphi viel langsamer!


galagher - Mo 25.07.16 20:54
Titel: Sleep - Programm läuft ohne Delphi viel langsamer!
Hallo!

Folgende for-Schleife:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
with ComputerPlayedCard do
  for i := ComputerLImage1.Left to iAbgelegtePosX-65 do
  begin
    SetBounds(i, iTalonPosY, Width, Height);
    if Application.Terminated then exit;
    Sleep(iDelay);  //Ist zB. 1
    if Odd(i) then Form1.Refresh;
  end;


Ja, ich weiss, das gehört in einen TTimer... :roll: Aber der Code ist alt und ich zu bequem, alles aufzudröseln und umzuschreiben, da ist ja noch mehr Code ausserhalb der Schleife!

Sleep dient nur dazu, die Schleife einzubremsen, um die Grafik als bewegte Animation darzustellen: Die Spielkarte wandert über das Spielfeld.
Wenn nun Delphi läuft, bewegt sie sich flott dahin, und zwar auch dann, wenn das Projekt gar nicht geladen ist und ich mein Programm ausserhalb von Delphi starte. Es genügt, dass Delphi im Speicher ist!
Läuft Delphi nicht, ist die Bewegung wesentlich langsamer! -> Wenn Delphi läuft, verändert sich das Verhalten des Programms?! :eyecrazy:

Ich weiss, diese Lösung ist grauenhaft, aber es würde mich interessieren, warum das so ist!


galagher - Mi 27.07.16 07:49

Wenn man nach "Delay" googelt, findet man diversen Code dazu. Auch damit zeigt sich der Effekt.
Die blosse Anwesenheit von Delphi beeinflusst das Verhalten eines Programmes. Seltsam!

Ich habe mir überlegt, ob mit Delphi vielleicht eine dll geladen ist, die das bewirkt, oder ob es am Delphi-Debugger liegt. Was auch immer, ich habe keine Erklärung.


GuaAck - Mi 27.07.16 20:12

Hallo,

mit folgenden Programm habe ich keinen Unterschied festgestellt:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
PROCEDURE TForm1.Button1Click(Sender: TObject);
VAR
  i, j: integer;
BEGIN

  Edit1.Text := '';
  application.ProcessMessages;
  FOR j := 0 TO 19 DO
    BEGIN
      FOR i := 1 TO 100 do
        sleep(10);
      Edit1.Text := Edit1.Text + 'X';
      application.ProcessMessages;
    END;
END;


Bei sleep (1) geht es viel langsamer, nach meiner Erinnerung ist 10 ms die kleinste Zeiteinheit.

Vielleicht hängt es irgendwie mit dem zusammen, was in der Delphi-Hilfe bei Sleep steht:

Zitat:
Remarks

A thread can relinquish the remainder of its time slice by calling this function with a sleep time of zero milliseconds.
You have to be careful when using Sleep and DDE. If a thread creates any windows, it must process messages. DDE sends messages to all windows in the system. If you have a thread that uses a wait function with no time-out interval, the system will deadlock. Therefore, if you have a thread that creates windows, use MsgWaitForMultipleObjects or MsgWaitForMultipleObjectsEx, rather than Sleep.



Viele Grüße
GuaAck


OlafSt - Do 28.07.16 14:06

IMHO schreit allein die Tatsache, das sich das Compilat anders verhält, nmehr als laut danach, es komplett zu überarbeiten...


galagher - Do 28.07.16 20:14

user profile iconGuaAck hat folgendes geschrieben Zum zitierten Posting springen:
Bei sleep (1) geht es viel langsamer, nach meiner Erinnerung ist 10 ms die kleinste Zeiteinheit.

Ich merke da keinen grossen Unterschied. Ausser, wenn Delphi läuft...

user profile iconOlafSt hat folgendes geschrieben Zum zitierten Posting springen:
IMHO schreit allein die Tatsache, das sich das Compilat anders verhält, nmehr als laut danach, es komplett zu überarbeiten...

Ich lasse den Code jetzt jeweils in eigenen Threads laufen (Komponente TJvThread von den JEDIs), und auch da brauche ich ein Sleep oder Delay, damit man die Animation sieht. Und auch da verhält sich alles wie beschrieben. Aber warum?
Den Umbau hätte ich mir sparen können.


GuaAck - Fr 29.07.16 00:08

Setze doch mal die Priorität Deines Programms von "normal" auf "high". Ändert sich was?

Gruß GuaAck


jaenicke - Fr 29.07.16 07:04

In einem Thread kannst du ja ohnehin nicht auf GUI Elemente zugreifen ohne zu synchronisieren. Das wiederum kostet Zeit.

Das Stichwort hier ist timebased movement. Im Moment ist es eben Zufall wie lange deine Bewegung dauert, da es auf jedem Rechner und je nach Auslastung anders ist. Sinnvoller ist festzulegen, dass die Bewegung z.B. 200ms dauern soll. Dann brauchst du lediglich solange bis die 200ms erreicht sind in einer while-Schleife jeweils die Position anhand der vergangenen Zeit zu setzen.

Sprich wenn 50ms vergangen und du eine Strecke von 20 Pixeln startend bei 75 Pixeln bewegen willst, setzt du die Position entsprechend:
Left = OldLeft + (NewPos - OldLeft) * (CurrentTime - StartTime) / 200 = 75 + 20 * 50 / 200 = 80


galagher - Fr 29.07.16 07:57

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Left = OldLeft + (NewPos - OldLeft) * (CurrentTime - StartTime) / 200 = 75 + 20 * 50 / 200 = 80
Kannst du das bitte in Delphi- oder Pseudocode giessen? :mrgreen:


galagher - Fr 29.07.16 20:43

Meine Ansätze:
- Ermittle die aktuelle Zeit (GetTickCount)
- Durchlaufe eine while-Schleife: while StartPos < EndPos do
- Stelle darin laufend die aktuelle Zeit fest
- und weiter?
oder:
- while-Schleife, solange StartTime < CurrTime

Ich habe keine konkrete Idee, wie ich das umsetzen soll.


Mathematiker - Fr 29.07.16 21:04

Hallo,
evtl. so, wobei ich nicht einschätzen kann, ob das bei deinem Problem hilft.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
procedure Zeitschleife(msZeit: DWord);
var Dauer: double;
    Anfang, Ende, Frequenz : TLargeInteger;
begin
  QueryPerformanceFrequency(Frequenz);
  QueryPerformanceCounter(Anfang);
  repeat
    Application.ProcessMessages;  ///je nachdem, ob es gebraucht wird
    QueryPerformanceCounter(Ende);
    Dauer:=(Ende-Anfang)/Frequenz*1000.0;
    ///hier irgendetwas machen, zeichnen ....
  until Dauer>=msZeit;
end;

Beste Grüße
Mathematiker


galagher - Sa 30.07.16 09:03

user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
Hallo,
evtl. so, wobei ich nicht einschätzen kann, ob das bei deinem Problem hilft.
Nun ja, das ist tatsächlich eine funktionierende Zeitschleife, aber um das TImage zu bewegen, muss ich doch Left+1 angeben. Das läuft dann in einem Rutsch durch, während die Zeitschleife weiterläuft, bis until erfüllt ist. Selbst, wenn ich bei msZeit 1000 angebe, also 1 Sekunde: Das Image bewegt sich zügig nach rechts, die Schleife läuft 1 Sekunde.

So gesehen nützt mir das leider nichts! Ich habe keine Idee, was ich tun soll!


jaenicke - Sa 30.07.16 13:04

Ich schreibe heute noch etwas dazu... Bin unterwegs...

Die Formel habe ich ja aufgeschrieben.

Bei dir in etwa... am Handy geschrieben...

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
MoveWidth := iAbgelegtePosX-65 - ComputerLImage1.Left; 
// Bewegungsweite

OriginalLeft := ComputerLImage1.Left;

// Und in der Schleife
ComputerLImage1.Left := OriginalLeft + VergangeneMillisekunden / DauerInMillisekunden * MoveWidth;


galagher - So 31.07.16 09:05

Das macht 2 Sprünge nach rechts, eine flüssige Bewegung ist das nicht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
var
  MoveWidth, OriginalLeft, VergangeneMillisekunden, DauerInMillisekunden: Integer;
//...
begin
  OriginalLeft := ComputerLImage1.Left;
  DauerInMillisekunden := 100;
  VergangeneMillisekunden := 0;

  while Left < iAbgelegtePosX do  //Left bezieht sich auf das TImage!
  begin
    Inc(VergangeneMillisekunden);  //Wie zähle ich VergangeneMillisekunden?

    //Hier kommt: "[DCC Fehler] CardsHandling.pas(884): E2010 Inkompatible Typen: 'Integer' und 'Extended'"
    {Left := OriginalLeft + VergangeneMillisekunden / DauerInMillisekunden * MoveWidth;}

    //Also mache ich es so:
    Left := OriginalLeft + VergangeneMillisekunden div DauerInMillisekunden * MoveWidth;

    Form1.Refresh;
  end;
end;


Wenn ich VergangeneMillisekunden und DauerInMillisekunden als Double angebe, ändert das nichts am Fehler - es klappt nicht.
Ich blicke da nicht durch.


jaenicke - So 31.07.16 14:37

Ich habe einmal meine Formel 1:1 in Quelltextform gebracht (das "1000 / Freq" gehört zur Ermittlung der Millisekunden)...
Das hatte ich dir eigentlich zugetraut. ;-)

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
var
  StartPos, EndPos: Integer;
  Start, Current, Freq: Int64;
begin
  StartPos := 8;
  EndPos := FGui.DisplayWidth - 8 - FGui.ImageWidth;
  QueryPerformanceFrequency(Freq);
  while not Terminated do
  begin
    FGui.ImageLeft := StartPos;
    QueryPerformanceCounter(Start);
    repeat
      QueryPerformanceCounter(Current);
      FGui.ImageLeft := StartPos + Round((EndPos - StartPos) * (Current - Start) * 1000 / Freq / FAnimationDuration);
    until (Current - Start) * 1000 / Freq >= FAnimationDuration;
    FGui.ImageLeft := EndPos;
    Sleep(100);
  end;

Im Anhang liegt ein Beispielprojekt, das dies aus einem Thread heraus macht.

Normalerweise macht man die Threadsynchronisation im Thread, nicht im Setter in der Gui. Das habe ich hier nur gemacht um den Threadquelltext für das Beispiel kurz zu halten.


galagher - Di 02.08.16 18:59

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Das hatte ich dir eigentlich zugetraut. ;-)

Ich habe die Stellen kommentiert, mit denen ich nichts anfangen konnte (und kann, solange ich keinen Quellcode dazu sehe!):

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
var
  StartPos, EndPos: Integer;
  Start, Current, Freq: Int64;
begin
  StartPos := 8;
  EndPos := FGui.DisplayWidth - 8 - FGui.ImageWidth;

  //Der Name erklärt zwar, was das macht, aber zunächst dachte ich,
  //an Freq wird ja gar nichts zugewiesen. Das macht aber wohl QueryPerformanceFrequency!
  QueryPerformanceFrequency(Freq);
  while not Terminated do
  begin
    FGui.ImageLeft := StartPos;
 
   QueryPerformanceCounter(Start);  //Siehe obigen Kommentar!

    repeat
      QueryPerformanceCounter(Current);  //Auch das war/ist mir nicht so ganz klar, wie das arbeitet!

      //Auf diese Formel (auch auf die Anweisung im until) wäre ich von selbst nie gekommen!
      FGui.ImageLeft := StartPos + Round((EndPos - StartPos) * (Current - Start) * 1000 / Freq / FAnimationDuration);
    until (Current - Start) * 1000 / Freq >= FAnimationDuration;
    FGui.ImageLeft := EndPos;
    Sleep(100);
  end;


Ich werde nach den entsprechenden Windows-API-Prozeduren googlen, um das besser zu verstehen!


jaenicke - Mi 03.08.16 02:15

(Current - Start) * 1000 / Freq ergibt die bisher abgelaufene Dauer in Millisekunden. Der Performance counter zählt in Ticks, da er sehr viel genauer als z.B. GetTickCount arbeitet, das hier aber auch gereicht hätte. Deshalb muss man diese Ticks umrechnen und da die Frequenz auf Sekunden läuft muss man das Ergebnis noch mit 1000 multiplizieren um Millisekunden zu bekommen.

Diese Formel findest du aber wie auch oben bei Mathematiker bei jedem Codeschnippsel zu genauer Zeitmessung.

Und die Formel für die aktuelle Position ist ein Dreisatz [https://de.m.wikipedia.org/wiki/Dreisatz]. Man setzt die in der abgelaufenen Zeit anteilig zurückgelegte Strecke proportional ins Verhältnis zu der Gesamtstrecke.


galagher - Do 04.08.16 08:59

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
und da die Frequenz auf Sekunden läuft muss man das Ergebnis noch mit 1000 multiplizieren um Millisekunden zu bekommen.
Soweit klar!

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Und die Formel für die aktuelle Position ist ein Dreisatz [https://de.m.wikipedia.org/wiki/Dreisatz].
Manchmal liegt die Lösung auf der Hand, und man kommt nicht drauf!

Ich habe den Code jetzt noch für Bewegungen von rechts nach links und senkrecht angepasst und möchte ihn noch weiter verallgemeinern.


Knulli - Mo 05.09.16 12:15

Mach mal

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
{**************************************************************************************************}
procedure SetSleepGranularity();
begin
  timeBeginPeriod(1);
end;
{**************************************************************************************************}
procedure ResetSleepGranularity();
begin
  timeEndPeriod(1);
end;
{**************************************************************************************************}

in den Initalzie / Finalize Teil deiner Unit.

Delphi macht sowas auch. Wenn Delphi nicht läuft, dann sind Deine Sleeps alle langsamer.
Es reicht also, wenn Delphi nur im Hintergrund läuft, damit dein Programm schneller ist.

Knulli


jaenicke - Mo 05.09.16 14:31

Danke, das kannte ich auch noch nicht. Und die Doku zeigt auch warum das alle Prozesse betrifft:
https://msdn.microsoft.com/de-de/library/windows/desktop/dd757624(v=vs.85).aspx hat folgendes geschrieben:
This function affects a global Windows setting. Windows uses the lowest value (that is, highest resolution) requested by any process.


Das zeigt einmal mehr weshalb der von mir gezeigte Ansatz für eine Animation sinnvoller ist.