Jump to content

Recommended Posts

Posted (edited)

TL;DR: Anyway to bypass built in sleep on TrayGetMsg() and GUIGetMsg() ?

 

I'm currently working on a project that does some heavy array/ GDI+ processing, which depending what I have it do can take between 4s - 1min. During this time, I'd like to have the Tray icon (mainly, possibly also the GUI) for the program be responsive, so that if someone wanted to change actions or exit the script during this, they can without closing the process.

 

Currently I have my TrayGetMsg and GUIGetMsg captures in my main program loop, working great, and once I go into the array/ GDI+ functions I tried switching it to an AdlibRegister call, unregistering it once completed. The problem with this is that it's adding ~10-20% more time into those functions, which I'd like to avoid. As I understand it, and from the helpfile: "This function automatically idles the CPU when required so that it can be safely used in tight loops without hogging all the CPU." From what I've read/ seen/ tested, this adds a 10ms sleep into the calls. I would rather avoid that sleep all together.

 

The array/ GDI+ function that I'm doing peg the CPU at 100% (for its core) anyways, so that's not a concern of mine. My only concern for this is speed (and having things be responsive to other actions).

 

Here's an example:

Local $iBlockSize = 20, $iWidth = 1920, $iHeight = 1080, $aBigArray[$iWidth * $iHeight], $iPercentDone, $sLastMsg
Local $aSmallerArray[Int(Ceiling($iWidth / $iBlockSize)) * Int(Ceiling($iHeight / $iBlockSize))]
Local $sAdlib[] = ["Registered: ", "UnRegistered: ", "Registered to _FakeTray: "], $timer, $iIndexLength = UBound($aBigArray)
Local $ixBlocks = Int(Ceiling($iWidth / $iBlockSize)), $iBlockIndex, $iBlockX, $iBlockY

For $j = 0 To 2
    If $j = 0 Then
        AdlibRegister("_CheckTray", 100)
    ElseIf $j = 1 Then
        AdlibUnRegister("_CheckTray")
    ElseIf $j = 2 Then
        AdlibRegister("_FakeTray", 100)
    EndIf

    $timer = TimerInit()

    For $i = 0 To $iIndexLength - 1 ; Loop through $aBigArray

        $aBigArray[$i] = Random(1, 10, 1)

        $iPercentDone = Floor(($i / $iIndexLength * 100)) ; Hopefully quick maths to get progress
        If $sLastMsg <> "We are " & $iPercentDone & "% done" Then ; Check if we're on a new percent
            $sLastMsg = "We are " & $iPercentDone & "% done" ; If so, update the msg
            ToolTip($sLastMsg, 0, 0) ; And display the current progress
        EndIf

        $y = Floor($i / $iWidth) ; Convert index to Y coordinate
        $x = Floor($i - ($y * $iWidth)) ; Convert index to X coordinate

        $iBlockX = Floor($x / $iBlockSize) ; Convert X coord to xBlock coord
        $iBlockY = Floor($y / $iBlockSize) ; Convert Y coord to yBlock coord

        $iBlockIndex = Int($iBlockX + ($iBlockY * $ixBlocks)) ; Convert into a blockIndex
;~      If Mod($i, 10000) = 0 Then
;~          ConsoleWrite($iBlockIndex & " - " & $i & @CRLF)
;~      EndIf

        $aSmallerArray[$iBlockIndex] += Int($aBigArray[$i]) ; Add into $aSmallerArray
    Next
    ConsoleWrite("Time to run with Adlib" & $sAdlib[$j] & TimerDiff($timer) & @CRLF)
Next

Func _CheckTray()
    Switch TrayGetMsg()
        Case "Meow"
            Return

        Case "Woof"
            Return

    EndSwitch
EndFunc   ;==>_CheckTray

Func _FakeTray()
    Local $sMeow = "Oink"
    Switch $sMeow
        Case "Meow"
            Return

        Case "Woof"
            Return

    EndSwitch
EndFunc   ;==>_FakeTray

Exit

On my system, this takes ~1 minute to run, output:
 

Time to run with AdlibRegistered: 19649.335
Time to run with AdlibUnRegistered: 16264.4124
Time to run with AdlibRegistered to _FakeTray: 16860.1283

>Exit code: 0    Time: 53.92

As you can see, it's ~20.8% faster without the Adlib check, and ~16.5% faster using a (hopefully) reproduction of TrayGetMsg() without the built in sleep. These timings vary, but it's consistently much faster without the TrayGetMsg() sleep (unless it's just that slow). I've used the OnEventModes, and those also slow down performance, more so than just using the GetMsgs, so those are out (but effective, and pretty easy to use).

Edited by mistersquirrle

We ought not to misbehave, but we should look as though we could.

Posted

I'd be OK with a 'No', I just want to know :lol:

 

Currently I'm using the Tray/GUIGetMsg in my program, which works fine, and I've tried the OnEvent's as well, so it'll come down to which has a smaller impact on performance. I've been doing a lot of performance testing of different things, and having the OnEvent modes on really slows things down. I'm thinking at the moment though that it may be best for performance to run the OnEventModes while in my main loop, and switch to the GetMsg's / adlib (at a higher interval) for my array/GDI functions, but I'm not sure if switching between those modes often would have any ill effect.

 

Again, if there's a way to bypass the sleep built into the *GetMsgs that would easily solve these problems, and CPU hogging isn't an issue for this/me.

 

Or -- if there's a different way that I get receive the GUI/ Tray msgs, such as with some other Windows functions/ DLLs, I just don't know enough in that area to figure out what I would need to look at. For GUIGetMsg, I was looking at GUIRegisterMsg(), but the main issue right now is the TrayGetMsg as that's always in the main loop, the GUI isn't always around.

We ought not to misbehave, but we should look as though we could.

Posted (edited)

Thanks for the reply Jos, but the OnEvent mode also has performance issues. Using that same test, but with the OnEvent modes on (all times in ms):

Time to run with Tray off, GUI off: 18216.3572
Time to run with Tray On, GUI off:  19452.3151
Time to run with Tray off, GUI On:  19975.8837
Time to run with Tray On, GUI On:   22121.3507

So running with both on in that test is slower, unfortunately. I was originally using the OnEvent modes until I discovered that they slowed everything down. I had done some other speed tests on Select vs Switch vs Ifs as well, and you can see it a lot there:

Tray and GUI OnEventMode OFF                |                       |   Tray and GUI OnEventMode ON     
------------------------------------------------------------------------------------------------------------------
Select      |   Switch      |   Ifs                                 |   Select      |   Switch      |   Ifs
28940.94468 |   19846.75708 |   34120.26846 |   10,000,000 times    |   39401.7251  |   29542.56247 |   62362.88815
-------------------------------------------------------------------------------------------------------------------
3140.979716 |   1956.855577 |   3363.471969 |   Time for 1mil       |   4024.597562 |   2930.187909 |   6344.789068

The above averages were over 6 runs of 1,000,000 times for each. This was a simple statement seeing if a variable = 1. I did a second run with checking against an array with two indexes values (ie. If $aTmp[0] = 1 And $aTmp[1] = 1 Then):

Tray and GUI OnEventMode OFF                |                       |   Tray and GUI OnEventMode ON
-------------------------------------------------------------------------------------------------------------------
Select      |   Switch      |   Ifs         |                       |   Select      |   Switch      |   Ifs
2983.793303 |   1481.685642 |   3709.658693 |   Time for 1mil       |   3703.603641 |   2753.622659 |   7324.438944

This again is the time to run each statement 1 million times, averaged over 6 runs. 

Edited by mistersquirrle

We ought not to misbehave, but we should look as though we could.

Posted (edited)

I have run the code in the first post with these results:

Time to run with AdlibRegistered: 14571.9029530593
Time to run with AdlibUnRegistered: 13025.2308415184
Time to run with AdlibRegistered to _FakeTray: 13365.1403195613

 

A few comments:

  • A large number of loops are much faster in 64 bit code.
  • The outer loop (j-loop) is always faster in second and third passes. In this code about 1 second faster. This gives incorrect results. It should be taken into account by introducing an initial first run.
  • $aSmallerArray is not be reset between each pass.
  • Tooltip: If Not Mod( $i, 20736 ) Then ToolTip("We are " & $i/20736 & "% done", 0, 0)
  • Added _ArrayDisplayEx that can display large arrays

New code:

#AutoIt3Wrapper_UseX64=y

#AutoIt3Wrapper_Au3Check_Parameters=-d -w- 1 -w 2 -w 3 -w 4 -w 5 -w 6

#include "Display\Functions\ArrayDisplayEx\ArrayDisplayEx.au3"

Global $iBlockSize = 20, $iWidth = 1920, $iHeight = 1080, $iIndexLength = $iWidth * $iHeight, $iPercentDone, $sLastMsg
Global $aBigArray[$iIndexLength],  $aSmallerArray[$iWidth / $iBlockSize * $iHeight / $iBlockSize]
Global $sAdlib = [" First run: ", "Registered: ", "UnRegistered: ", "Registered to _FakeTray: "]
Global $ixBlocks = $iWidth / $iBlockSize, $iBlockIndex, $iBlockX, $iBlockY, $timer, $x, $y

For $j = 0 To 3
    If $j = 1 Then
        AdlibRegister("_CheckTray", 100)
    ElseIf $j = 2 Then
        AdlibUnRegister("_CheckTray")
    ElseIf $j = 3 Then
        AdlibRegister("_FakeTray", 100)
    EndIf

    $aSmallerArray = 0
    Dim $aSmallerArray[$iWidth / $iBlockSize * $iHeight / $iBlockSize]

    $timer = TimerInit()

    For $i = 0 To $iIndexLength - 1 ; Loop through $aBigArray

        $aBigArray[$i] = Random(1, 10, 1)

        If Not Mod( $i, 20736 ) Then ToolTip("We are " & $i/20736 & "% done", 0, 0)

        $y = Floor($i / $iWidth) ; Convert index to Y coordinate
        $x = Floor($i - ($y * $iWidth)) ; Convert index to X coordinate

        $iBlockX = Floor($x / $iBlockSize) ; Convert X coord to xBlock coord
        $iBlockY = Floor($y / $iBlockSize) ; Convert Y coord to yBlock coord

        $iBlockIndex = Int($iBlockX + ($iBlockY * $ixBlocks)) ; Convert into a blockIndex

        $aSmallerArray[$iBlockIndex] += Int($aBigArray[$i]) ; Add into $aSmallerArray
    Next
    ConsoleWrite("Time to run with Adlib" & $sAdlib[$j] & TimerDiff($timer) & @CRLF)
Next

Global $aWidth = [ 0, 65 ]
Global $aAlign = [ [ 0, "l" ], [ 1, "r" ] ]
Global $aFeatures = [ [ "ColWidthMin", $aWidth ], _
                     [ "ColAlign",    $aAlign ] ]
_ArrayDisplayEx( $aBigArray, "$aBigArray", "", 0, $aFeatures )
_ArrayDisplayEx( $aSmallerArray, "$aSmallerArray", "", 0, $aFeatures )

Func _CheckTray()
    Switch TrayGetMsg()
        Case "Meow"
            Return

        Case "Woof"
            Return

    EndSwitch
EndFunc   ;==>_CheckTray

Func _FakeTray()
    Local $sMeow = "Oink"
    Switch $sMeow
        Case "Meow"
            Return

        Case "Woof"
            Return

    EndSwitch
EndFunc   ;==>_FakeTray

Exit

#cs
Time to run with Adlib First run: 9354.58487313101
Time to run with AdlibRegistered: 8572.65596364715
Time to run with AdlibUnRegistered: 8436.5804675481
Time to run with AdlibRegistered to _FakeTray: 8573.66191708517
#ce

And results:

Time to run with Adlib First run: 9354.58487313101
Time to run with AdlibRegistered: 8572.65596364715
Time to run with AdlibUnRegistered: 8436.5804675481
Time to run with AdlibRegistered to _FakeTray: 8573.66191708517

Now there is not much difference between the last 3 results.

 

For arrays with 2 million elements, it's a great advantage to use compiled code. Since UDFs already exist to execute VB code (and C# code) in an AutoIt script through the .NET Framework, it's not difficult.

c.vb:

Imports System
Class Au3Class
  Dim aSmallerArray As Integer()

  Public Function MyMethod1( iBlockSize As Integer, iWidth As Integer, iHeight As Integer ) As Integer()
    Dim ixBlocks As Integer = iWidth / iBlockSize
    ReDim aSmallerArray(ixBlocks * iHeight/iBlockSize - 1)
    Dim iIndexLength As Integer = iWidth * iHeight
    Dim aBigArray(iIndexLength-1) As Integer
    Dim oRnd As New Random()

    Dim x, y, iBlockX, iBlockY, iBlockIndex As Integer
    For i As Integer = 0 To iIndexLength - 1
      aBigArray(i) = oRnd.Next( 1, 10+1 )
      y = Math.Floor( i / iWidth ) : x = Math.Floor( i - y * iWidth )
      iBlockX = Math.Floor( x / iBlockSize ) : iBlockY = Math.Floor( y / iBlockSize )
      iBlockIndex = iBlockX + iBlockY * ixBlocks
      aSmallerArray(iBlockIndex) += aBigArray(i)
    Next

    Return aBigArray
  End Function

  Public Function MyMethod2() As Integer()
    Return aSmallerArray
  End Function
End Class

c.au3:

#AutoIt3Wrapper_UseX64=y

#AutoIt3Wrapper_Au3Check_Parameters=-d -w- 1 -w 2 -w 3 -w 4 -w 5 -w 6

#include "DotNet\DotNetAll.au3"
#include "Display\Functions\ArrayDisplayEx\ArrayDisplayEx.au3"

Opt( "MustDeclareVars", 1 )

Example()

Func Example()
  Local $oNetCode = DotNet_LoadVBcode( FileRead( "c.vb" ), "System.dll" )
  Local $oAu3Class = DotNet_CreateObject( $oNetCode, "Au3Class" )
  Local $timer = TimerInit()
  Local $aBigArray = $oAu3Class.MyMethod1( 20, 1920, 1080 )
  Local $aSmallerArray = $oAu3Class.MyMethod2()
  ConsoleWrite( TimerDiff( $timer ) & @CRLF ) ; 221.589018567889

  Local $aWidth = [ 0, 65 ]
  Local $aAlign = [ [ 0, "l" ], [ 1, "r" ] ]
  Local $aFeatures = [ [ "ColWidthMin", $aWidth ], _
                       [ "ColAlign",    $aAlign ] ]
  _ArrayDisplayEx( $aBigArray, "$aBigArray", "", 0, $aFeatures )
  _ArrayDisplayEx( $aSmallerArray, "$aSmallerArray", "", 0, $aFeatures )
EndFunc

Note the time: 221 milliseconds.

 

You cannot do anything about the Sleep function in TrayGetMsg() and GUIGetMsg(), but you can completely avoid the functions by using Windows messages directly through GUIRegisterMsg() (still in MessageLoop mode).

 

Tests.7z

Edited by LarsJ
Posted

@Jos I don't have the same code, but it was something like this (basic, and also taking a note from LarsJ about a first run):

;~ #AutoIt3Wrapper_UseX64=Y
Local $timer
Local $iMeow = 1

For $j = 2 To 0 Step -1

    If $j < 2 Then AutoItSetOption("TrayOnEventMode", $j)
    If $j < 2 Then AutoItSetOption("GUIOnEventMode", $j)

    ConsoleWrite("Tray/GUI OnEventMode: " & $j & @CRLF)

    $timer = TimerInit()
    For $i = 0 To 999999
        Select
            Case $iMeow = 0
            Case $iMeow = 2
            Case $iMeow = 1
        EndSelect
    Next
    ConsoleWrite("Select time:  " & TimerDiff($timer) & @CRLF)

    $timer = TimerInit()
    For $i = 0 To 999999
        Switch $iMeow
            Case 0
            Case 2
            Case 1
        EndSwitch
    Next
    ConsoleWrite("Switch time:  " & TimerDiff($timer) & @CRLF)

    $timer = TimerInit()
    For $i = 0 To 999999
        If $iMeow = 0 Then
        EndIf
        If $iMeow = 2 Then
        EndIf
        If $iMeow = 1 Then
        EndIf
    Next
    ConsoleWrite("Ifs time: " & TimerDiff($timer) & @CRLF)
Next


#cs
First run (Tray/GUI OnEvent not set/ changed [default])
  Select time:  966.3338
  Switch time:  898.1769
  Ifs time:     1543.816
Tray/GUI OnEventMode: 1
  Select time:  1642.5594
  Switch time:  1486.2358
  Ifs time:     3028.6555
Tray/GUI OnEventMode: 0
  Select time:  908.7544
  Switch time:  523.0475
  Ifs time:     1135.5656
#ce

----------

@LarsJ Thanks for the tips, trying your code, here's my results (note that I had to disable your included file/ functions, as even when I downloaded it and put a direct path to it, it said that it wouldn't include it and gave a lot of warnings/ error/ undefineds):

Time to run with Adlib First run: 12885.4199
Time to run with AdlibRegistered: 11540.2438
Time to run with AdlibUnRegistered: 11087.015
Time to run with AdlibRegistered to _FakeTray: 11600.5445

That does provide interesting results as the AdlibRegistered run with TrayGetMsg is slightly faster than without TrayGetMsg for both of us. I had also looked at your topic a bit before, and it's a bit much for me to get my head around, but I was going to look into that some more to speed up some data processing parts of my program like this, so I appreciate this example.

 

As for the GUIRegisterMsg, I know how to use it for an actual GUI, but I'm not sure how to use it for the Tray icon?

We ought not to misbehave, but we should look as though we could.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...