Jump to content

pyscripter

Members
  • Content Count

    785
  • Joined

  • Last visited

  • Days Won

    42

Posts posted by pyscripter


  1. Your loop calls ExecStrings which means you have the overhead of string compilation. in every iteration  

     

    What you can do instead is to wrap you python code in a function:

     

    def calc_outflow(inflow)
      if (inflow > 6) or (inflow < 2) :
        return 40
      else:
        return 20

    and call the function from Delphi.

     

    Uses 
      VarPyth;
     
          script.LoadFromFile('loop.py');
          PythonEngine1.ExecStrings(script);
          var CalcOutFlow: Variant := MainModule.calc_outflow;
    
          for i:=1 to i_end do
           begin
             Output := CalcOutFlow(Random * 10.0);        
           end;

     

    You will get extra speed if instead of 

     

    Output := CalcOupFlow(Random * 10.0);

     

    you call the function using low-level PythonEngine routines e.g.  PythonEngine.PyObject_CallObject


  2. The PythonVersions unit is of little value in Linux, where there is no Registry and no registered Python versions.  You need to manually provide the DLLName and DLLPath.

     

    You can use some code that searches possible file locations.  For example in the Mac you can do, something like:

    {$ifdef MACOS}

    for N:= 5 to 9 do begin

      S:= Format('/Library/Frameworks/Python.framework/Versions/3.%d/lib/libpython3.%d.dylib', [N, N]);

      if FileExists(S)

        then exit(S);

    end;

    {$endif}

     

    In Debian Linux you can search for file locations such as:

    /usr/lib/x86_64-linux-gnu/libpython3.7m.so
    /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1
    /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0

     

    but it may vary by python distribution.

    • Like 1

  3. On 3/10/2021 at 5:55 PM, J. Robroeks said:

    Thank you for your quick and elaborate answer. The py_subject was indeed freed at the wrong place. 

     

    In case someone else wonders the following:

    There are several funtions in Python that are non-blocking. For example:

    1. sleep
    2. urlopen

     

    None of the python code is blocking other threads.  But the following functions are blocking:

    • PyGILState_Ensure
    • PyEval_RestoreThread

    In other words you need to get and hold to the GIL to execute any python code.  So take for example sleep.  It works like windows sleep.   But to let other threads to execute python code you need to use Py_Begin_Allow_Threads /Py_End_Allow_Threads   before/after the sleep.  Python deals with the threads it creates.  (threading module).


  4. In your code above you should destroy PY_SUBJECT before releasing the GIL

    Python has a lesser known feature called sub-interpreters, which allows you to use the interpreter from a clean state.  This is what emNewInterpreter does.  Normally I would not bother with that.

     

    The pattern you need to follow using the latest version of P4D:

     

    In your main thread to release the GIL after loading the Python dll:  

    TPythonThread.Py_Begin_Allow_Threads  (calls PyEval_SaveThread)

     

    In your Delphi threads including the main thread (or if you use ITask or  TParallel) that execute python code (this is what TPythonThread does):

      fGILState := .PyGILState_Ensure;
      try
        Do python staff
      finally
         PyGILState_Release(fGILState);
      end;

     

    In your main thread before unlolading Python

    TPythonThread.Py_End_Allow_Threads  (calls PyEval_RestoreThread)

     

    if you have a long running thread that does python stuff and you want allow other threads to do python stuff as well then the pattern is:

     

      fGILState := .PyGILState_Ensure;
      try
        Do python staff
        TPythonThread.Begin_Allow_Threads;
        try
          Other threads can run python code
        finally
          TPythonThread.End_ALlow_Threads;
        end;
        Do more python stuff.
      finally
         PyGILState_Release(fGILState);
      end;
    

     

    In PyScripter I have a utility function:

    type
      IPyEngineAndGIL = interface
        function GetPyEngine: TPythonEngine;
        function GetThreadState: PPyThreadState;
        property PythonEngine: TPythonEngine read GetPyEngine;
        property ThreadState: PPyThreadState read GetThreadState;
      end;
    
    function SafePyEngine: IPyEngineAndGIL;
    begin
      Result := TPyEngineAndGIL.Create
    end;
    
    
    type
      TPyEngineAndGIL = class(TInterfacedObject, IPyEngineAndGIL)
      fPythonEngine: TPythonEngine;
      fThreadState: PPyThreadState;
      fGILState: PyGILstate_STATE;
      private
        function GetPyEngine: TPythonEngine;
        function GetThreadState: PPyThreadState;
      public
        constructor Create;
        destructor Destroy; override;
      end;
    
    { TPyEngineAndGIL }
    
    constructor TPyEngineAndGIL.Create;
    begin
      inherited Create;
      fPythonEngine := GetPythonEngine;
      fGILState := fPythonEngine.PyGILState_Ensure;
      fThreadState := fPythonEngine.PyThreadState_Get;
    end;
    
    destructor TPyEngineAndGIL.Destroy;
    begin
      fPythonEngine.PyGILState_Release(fGILState);
      inherited;
    end;
    
    function TPyEngineAndGIL.GetPyEngine: TPythonEngine;
    begin
      Result := fPythonEngine;
    end;
    
    function TPyEngineAndGIL.GetThreadState: PPyThreadState;
    begin
      Result := fThreadState;
    end;

    which is used in the main or other threads as:

    var Py: IPyEngineAndGIL;

    begin

       Py := SafePyEngine;

       Py.Engine.

     

    whenever I need to execute Python code

    • Like 1

  5. 9 hours ago, balabuev said:

    I've replaced the treeview with a frame to remove the mess with the second pair of unneeded scrollbar windows. I've simulated some resource allocation on WM_CREATE in a frame, which is then freed on WM_DESTROY, similar to tree view.

     

    I guess the issue would still be present if you now replace the ListView with anything that has scrollbars including a treeview.  Is that right?


  6. How about this one?

     

    class procedure TStyleEngine.DoRemoveControl(Control: TWinControl);
    begin
      if not (csDestroying in Control.ComponentState) and (FControls <> nil) and FControls.ContainsKey(Control) then
      begin
        var Hook := FControls.Items[Control];
        TThread.ForceQueue(nil, procedure
        begin
          Hook.Free;
        end);
        FControls.Remove(Control);
      end;
    end;

    There may be more efficient solutions, such as adding the hooks for deletion to a List and process that list on idle time or on Hook creation.

     

    • Like 1

  7. In light of @David Heffernan remarks here is a shorter version of @Stephen Ball code:


     

    type
      TForm1 = class(TForm)
        procedure CreateWnd; override;
        procedure WMWTSSessionChange (var Message: TMessage); message WM_WTSSESSION_CHANGE ;
        procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY;
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm1.WMDestroy(var Message: TWMDestroy);
    begin
      inherited;
      WTSUnRegisterSessionNotification(Handle);
    end;
    
    procedure TForm1.WMWTSSessionChange(var Message: TMessage);
    begin
      case Message.wParam of
        WTS_SESSION_LOCK,
        WTS_REMOTE_DISCONNECT: Application.UpdateMetricSettings := False;
        WTS_REMOTE_CONNECT,
        WTS_SESSION_UNLOCK:
          TThread.ForceQueue(nil, procedure
          begin
            Application.UpdateMetricSettings := True;
          end, 30000);
      end;
    end;
    
    procedure TForm1.CreateWnd;
    begin
      inherited;
      WTSRegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION);
    end;

    Instead of CreateWnd you could override the CreateWindowHandle method.


  8. 36 minutes ago, David Heffernan said:

    My answer to this old SO question presents a different way that may be simpler in the long run. 

    Thanks!  DestroyWnd is not called when the application exits.  Is this an issue here?   You could call the WTSUnRegisterSessionNotification at the WM_DESTROY handler instead. 


  9. 2 hours ago, Stephen Ball said:

    As @David Millington mentioned, we have been working with the R&D team to share tips learned to help speed up VCL applications under remote desktop - to read about it, including a code sample see:

    https://blogs.embarcadero.com/how-to-speed-up-remote-desktop-applications/ 

    @Stephen BallThank you very much!

     

    Is there a need to have a separate window to receive the notifications?   Isn't the code below equivalent to yours?

    type
      TForm1 = class(TForm)
        procedure FormDestroy(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure WMWTSSessionChange (var Message: TMessage); message WM_WTSSESSION_CHANGE ;
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    var
      Form1: TForm1;
    implementation
    {$R *.dfm}
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
        WTSUnRegisterSessionNotification(Handle);
    end;
    
    procedure TForm1.WMWTSSessionChange(var Message: TMessage);
    begin
      inherited;
      case Message.wParam of
        WTS_SESSION_LOCK,
        WTS_REMOTE_DISCONNECT: Application.UpdateMetricSettings := False;
        WTS_REMOTE_CONNECT,
        WTS_SESSION_UNLOCK:
          TThread.ForceQueue(nil, procedure
          begin
            Application.UpdateMetricSettings := True;
          end, 30000);
      end;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      WTSRegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION);
    end;

     


  10. 11 minutes ago, balabuev said:

    First one will be deleted by window manager, but the second - not.

    This is not true.  StyleEngine.Destroys calls FreeControlHooks which destroys all remaining hooks.   However what @Attila Kovacs suggests is problematic, since you keep accumulating unreleased hooks if the application creates and destroys styled controls.

    • Like 1

  11. I did a bit of research to assess whether other parts of Vcl could be suffering from the same issue.  The only controls that recreate themselves in response to the CM_STYLECHANGED message are the TListView and the ActionManager controls (ActionToolbar etc.).  I think there are not scrollbars and the like there, but I wonder why there is a need to do that.  So the issue may be confined to the ListView and if the call to RecreateWnd can be avoided that would be the simplest solution.


  12. Update:  This does not work well.:classic_blush:

     

    One suggestion I have is the following:

     

    Change TStyleEngine.DoRemoveControl(Control: TWinControl) from:

     

    class procedure TStyleEngine.DoRemoveControl(Control: TWinControl);
    var
      I: Integer;
      LControl: TControl;
    begin
      if (FControls <> nil) and FControls.ContainsKey(Control) then
      begin
        for I := 0 to Control.ControlCount - 1 do
        begin
          LControl := Control.Controls[I];
          if LControl is TWinControl then
            DoRemoveControl(TWinControl(LControl));
        end;
        FControls.Items[Control].Free;
        FControls.Remove(Control);
      end;
    end;

    to

    class procedure TStyleEngine.DoRemoveControl(Control: TWinControl);
    var
      I: Integer;
      LControl: TControl;
    begin
      if not (csRecreating in Control.ControlState) and (FControls <> nil) and FControls.ContainsKey(Control) then
      begin
        for I := 0 to Control.ControlCount - 1 do
        begin
          LControl := Control.Controls[I];
          if LControl is TWinControl then
            DoRemoveControl(TWinControl(LControl));
        end;
        FControls.Items[Control].Free;
        FControls.Remove(Control);
      end;
    end;

    or even to:

    class procedure TStyleEngine.DoRemoveControl(Control: TWinControl);
    begin
      if not (csRecreating in Control.ControlState) and (FControls <> nil) and FControls.ContainsKey(Control) then
      begin
        FControls.Items[Control].Free;
        FControls.Remove(Control);
      end;
    end;

    There seems to be no reason to destroy the hook if the control is recreating. Also hooks of child controls would be removed when they get destroyed, so there is no need to do this in advance.

    Do you see anything wrong with the above?  With the above change the test app runs fine and without memory leaks.  And the change would save a lot of hook destruction and reconstruction.

     

    To test copy Vcl.Styles.pas, StyleAPI.inc and StyleUtils.inc to the project source directory, make the change and re-build the project.

     

×