[7.8] Processing events in a loop?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

[7.8] Processing events in a loop?

Carl Gundel-2
I'm trying to create a mechanism which will allow me to loop
continuously and still allow the user interface to service events from
the user and/or timer events.

So, I have event handlers to widgets that add user interface events to
a queue when the user interacts with them.  Then inside the loop I
examine the queue and do something if the queue can provide me with an
event from the user (or a timer tick, etc.).

So inside the loop I have code like the following:

    semaphore := Semaphore new.
    (newEvent := self nextEventFromQueue) isNil
    ifTrue: [
        Timer after: 100 microseconds do: [ semaphore signal ].
        self waitSemaphore wait
    ]
    ifFalse: [
        Timer after: 100 microseconds do: [ nextEvent applyTo: self ].
 "applyTo: will signal the semaphore"
        self waitSemaphore wait
    ].
    more stuff here...

So, the trouble I'm having is that the loop works only intermittently.
 It seems like sometimes the 100 microsecond timer simply misfires.
The loop will run along fine and then just stop.  Is there a bug in
the timer?  Am I seeing a race condition?

My reason for waiting on a semaphore even if newEvent is nil is
because I'm trying to give the UI a moment run.  Perhaps this isn't
necessary?

Any thoughts welcome.  Any other suggestions for doing this sort of
thing also welcome.

Thanks,

-Carl Gundel
http://www.libertybasic.com
http://www.runbasic.com
_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: [7.8] Processing events in a loop?

Randy Coulman
Carl,

Have you looked at the ExtraActivity package?  It basically allows you to run something periodically as an idle-loop process.  This might be what you're looking for.

Randy

On Wed, Nov 14, 2012 at 8:54 PM, Carl Gundel <[hidden email]> wrote:
I'm trying to create a mechanism which will allow me to loop
continuously and still allow the user interface to service events from
the user and/or timer events.

So, I have event handlers to widgets that add user interface events to
a queue when the user interacts with them.  Then inside the loop I
examine the queue and do something if the queue can provide me with an
event from the user (or a timer tick, etc.).

So inside the loop I have code like the following:

    semaphore := Semaphore new.
    (newEvent := self nextEventFromQueue) isNil
    ifTrue: [
        Timer after: 100 microseconds do: [ semaphore signal ].
        self waitSemaphore wait
    ]
    ifFalse: [
        Timer after: 100 microseconds do: [ nextEvent applyTo: self ].
 "applyTo: will signal the semaphore"
        self waitSemaphore wait
    ].
    more stuff here...

So, the trouble I'm having is that the loop works only intermittently.
 It seems like sometimes the 100 microsecond timer simply misfires.
The loop will run along fine and then just stop.  Is there a bug in
the timer?  Am I seeing a race condition?

My reason for waiting on a semaphore even if newEvent is nil is
because I'm trying to give the UI a moment run.  Perhaps this isn't
necessary?

Any thoughts welcome.  Any other suggestions for doing this sort of
thing also welcome.

Thanks,

-Carl Gundel
http://www.libertybasic.com
http://www.runbasic.com
_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc



--
Randy Coulman
[hidden email]
Twitter: @randycoulman


_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: [7.8] Processing events in a loop?

mkobetic
In reply to this post by Carl Gundel-2
Timers can be gc-ed if not held strongly (see more details in the class comment). If you replace

        Timer after: 100 microseconds do: [ semaphore signal ]

with

        Timer after: 100 microseconds signal: semaphore

it should be better.

HTH,

Martin

"Carl Gundel"<[hidden email]> wrote:

> I'm trying to create a mechanism which will allow me to loop
> continuously and still allow the user interface to service events from
> the user and/or timer events.
>
> So, I have event handlers to widgets that add user interface events to
> a queue when the user interacts with them.  Then inside the loop I
> examine the queue and do something if the queue can provide me with an
> event from the user (or a timer tick, etc.).
>
> So inside the loop I have code like the following:
>
>     semaphore := Semaphore new.
>     (newEvent := self nextEventFromQueue) isNil
>     ifTrue: [
>         Timer after: 100 microseconds do: [ semaphore signal ].
>         self waitSemaphore wait
>     ]
>     ifFalse: [
>         Timer after: 100 microseconds do: [ nextEvent applyTo: self ].
>  "applyTo: will signal the semaphore"
>         self waitSemaphore wait
>     ].
>     more stuff here...
>
> So, the trouble I'm having is that the loop works only intermittently.
>  It seems like sometimes the 100 microsecond timer simply misfires.
> The loop will run along fine and then just stop.  Is there a bug in
> the timer?  Am I seeing a race condition?
>
> My reason for waiting on a semaphore even if newEvent is nil is
> because I'm trying to give the UI a moment run.  Perhaps this isn't
> necessary?
>
> Any thoughts welcome.  Any other suggestions for doing this sort of
> thing also welcome.
>
> Thanks,
>
> -Carl Gundel
> http://www.libertybasic.com
> http://www.runbasic.com
> _______________________________________________
> vwnc mailing list
> [hidden email]
> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc


_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: [7.8] Processing events in a loop?

Carl Gundel-2
Ok, thanks that helped.  Now I see another problem though.  When I
have two timers operating at once I have problems with things
stalling.  Any ideas?

-Carl

On Thu, Nov 15, 2012 at 12:48 AM,  <[hidden email]> wrote:

> Timers can be gc-ed if not held strongly (see more details in the class comment). If you replace
>
>         Timer after: 100 microseconds do: [ semaphore signal ]
>
> with
>
>         Timer after: 100 microseconds signal: semaphore
>
> it should be better.
>
> HTH,
>
> Martin
>
> "Carl Gundel"<[hidden email]> wrote:
>> I'm trying to create a mechanism which will allow me to loop
>> continuously and still allow the user interface to service events from
>> the user and/or timer events.
>>
>> So, I have event handlers to widgets that add user interface events to
>> a queue when the user interacts with them.  Then inside the loop I
>> examine the queue and do something if the queue can provide me with an
>> event from the user (or a timer tick, etc.).
>>
>> So inside the loop I have code like the following:
>>
>>     semaphore := Semaphore new.
>>     (newEvent := self nextEventFromQueue) isNil
>>     ifTrue: [
>>         Timer after: 100 microseconds do: [ semaphore signal ].
>>         self waitSemaphore wait
>>     ]
>>     ifFalse: [
>>         Timer after: 100 microseconds do: [ nextEvent applyTo: self ].
>>  "applyTo: will signal the semaphore"
>>         self waitSemaphore wait
>>     ].
>>     more stuff here...
>>
>> So, the trouble I'm having is that the loop works only intermittently.
>>  It seems like sometimes the 100 microsecond timer simply misfires.
>> The loop will run along fine and then just stop.  Is there a bug in
>> the timer?  Am I seeing a race condition?
>>
>> My reason for waiting on a semaphore even if newEvent is nil is
>> because I'm trying to give the UI a moment run.  Perhaps this isn't
>> necessary?
>>
>> Any thoughts welcome.  Any other suggestions for doing this sort of
>> thing also welcome.
>>
>> Thanks,
>>
>> -Carl Gundel
>> http://www.libertybasic.com
>> http://www.runbasic.com
>> _______________________________________________
>> vwnc mailing list
>> [hidden email]
>> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
>
_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
jas
Reply | Threaded
Open this post in threaded view
|

Re: [7.8] Processing events in a loop?

jas
Carl,

1) Is the following a reasonably equivalent translation?
2) Where does the 'second timer' go, in this version?
    Just trying to understand what is interacting with what.

-Jim


 
eventLoop :=
        [ | isRunning uiSemaphore |
           isRunning := true.
           uiSemaphore := Semaphore new.
          self tellNonUiProcessToCoordinateVia: uiSemaphore.
          [ isRunning
          ] whileTrue:
                [
                self waitAtLeast: 100 us.
                (event := self nextEventFromQueue) isNil
                        or: [event applyTo: self].
                uiSemaphore signal.
                 possiblyMoreStuff here...
                ]
        ] fork

On 11/16/2012 1:12 PM, Carl Gundel wrote:

> Ok, thanks that helped.  Now I see another problem though.  When I
> have two timers operating at once I have problems with things
> stalling.  Any ideas?
>
> -Carl
>
> On Thu, Nov 15, 2012 at 12:48 AM,  <[hidden email]> wrote:
>> Timers can be gc-ed if not held strongly (see more details in the class comment). If you replace
>>
>>          Timer after: 100 microseconds do: [ semaphore signal ]
>>
>> with
>>
>>          Timer after: 100 microseconds signal: semaphore
>>
>> it should be better.
>>
>> HTH,
>>
>> Martin
>>
>> "Carl Gundel"<[hidden email]> wrote:
>>> I'm trying to create a mechanism which will allow me to loop
>>> continuously and still allow the user interface to service events from
>>> the user and/or timer events.
>>>
>>> So, I have event handlers to widgets that add user interface events to
>>> a queue when the user interacts with them.  Then inside the loop I
>>> examine the queue and do something if the queue can provide me with an
>>> event from the user (or a timer tick, etc.).
>>>
>>> So inside the loop I have code like the following:
>>>
>>>      semaphore := Semaphore new.
>>>      (newEvent := self nextEventFromQueue) isNil
>>>      ifTrue: [
>>>          Timer after: 100 microseconds do: [ semaphore signal ].
>>>          self waitSemaphore wait
>>>      ]
>>>      ifFalse: [
>>>          Timer after: 100 microseconds do: [ nextEvent applyTo: self ].
>>>   "applyTo: will signal the semaphore"
>>>          self waitSemaphore wait
>>>      ].
>>>      more stuff here...
>>>
>>> So, the trouble I'm having is that the loop works only intermittently.
>>>   It seems like sometimes the 100 microsecond timer simply misfires.
>>> The loop will run along fine and then just stop.  Is there a bug in
>>> the timer?  Am I seeing a race condition?
>>>
>>> My reason for waiting on a semaphore even if newEvent is nil is
>>> because I'm trying to give the UI a moment run.  Perhaps this isn't
>>> necessary?
>>>
>>> Any thoughts welcome.  Any other suggestions for doing this sort of
>>> thing also welcome.
>>>
>>> Thanks,
>>>
>>> -Carl Gundel
>>> http://www.libertybasic.com
>>> http://www.runbasic.com
>>> _______________________________________________
>>> vwnc mailing list
>>> [hidden email]
>>> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
> _______________________________________________
> vwnc mailing list
> [hidden email]
> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
>
>

_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: [7.8] Processing events in a loop?

Paul Baumann
In reply to this post by Carl Gundel-2
Carl,

I suspect Timer is doing exactly what it was asked to do. "100 microseconds" is a very high expectation. I don't have Timer and #microseconds in the VW release I use. I assume it some variant of doing "(Delay forMilliseconds: 1) wait" that works for extremely short delays.

I suspect the problem is related to the priority of the processes involved. The Timer is probably doing the #signal at TimingPriority (100) but the signaled process may be below that of another process (like the TimingPriority of your second timer). I suspect the approach is creating many process switches while not giving good time to any one process. I'm thinking that the second timer is causing a process switch (that that would be fast and do nearly nothing) but some other process is getting attention in between returning back to your signaled work process. "Some other process" can be equal to or greater than the priority of that your signaled process is running at. Your lower priority processes would be starved of any attention, and that might affect how quickly new work can come to the queue. You may be starving background GC activities until they they get emergency attention. The emergency GC would be a sudden interruption.

It seems you are trying to prioritize processing by way of rapidly process switching instead of by configuring process priority. You are forcing to ProcessorScheduler to behave different than it works best. A VM needs some time for background activities.

You can do better using an approach that doesn't need even one of these timers. Try an approach that signals a semaphore when adding to the queue so that your queue processor can wait patiently until something is there to work on. Your queue processor would just be in a loop between doing work and waiting (for a signal). If items have been added to the queue since processing started then you'll have excess signals and there will be no delay at all before getting to them. You can set the a high priority for the queue processor while waiting and then bring priority back down to the lowest reasonable priority once it starts doing work. The priority you need to do the work should probably be just one above the priority of the process that added the event. If you want to force a back-and-forth between processing the queue and other work then after each item processed from the queue you could temporarily lower priority of the active process and yield.

I recommend a semaphore based approach I just described, but you could even use a design that works without even a semaphore by just changing process priority and #yield for anything of higher or equal priority. You'd need to be careful with a #yield only based approach because your loop over a yield can starve lower priority processing too.

Paul Baumann



-----Original Message-----
From: [hidden email] [mailto:[hidden email]] On Behalf Of Carl Gundel
Sent: Friday, November 16, 2012 16:12
To: [hidden email]
Subject: Re: [vwnc] [7.8] Processing events in a loop?

Ok, thanks that helped.  Now I see another problem though.  When I
have two timers operating at once I have problems with things
stalling.  Any ideas?

-Carl

On Thu, Nov 15, 2012 at 12:48 AM,  <[hidden email]> wrote:

> Timers can be gc-ed if not held strongly (see more details in the class comment). If you replace
>
>         Timer after: 100 microseconds do: [ semaphore signal ]
>
> with
>
>         Timer after: 100 microseconds signal: semaphore
>
> it should be better.
>
> HTH,
>
> Martin
>
> "Carl Gundel"<[hidden email]> wrote:
>> I'm trying to create a mechanism which will allow me to loop
>> continuously and still allow the user interface to service events from
>> the user and/or timer events.
>>
>> So, I have event handlers to widgets that add user interface events to
>> a queue when the user interacts with them.  Then inside the loop I
>> examine the queue and do something if the queue can provide me with an
>> event from the user (or a timer tick, etc.).
>>
>> So inside the loop I have code like the following:
>>
>>     semaphore := Semaphore new.
>>     (newEvent := self nextEventFromQueue) isNil
>>     ifTrue: [
>>         Timer after: 100 microseconds do: [ semaphore signal ].
>>         self waitSemaphore wait
>>     ]
>>     ifFalse: [
>>         Timer after: 100 microseconds do: [ nextEvent applyTo: self ].
>>  "applyTo: will signal the semaphore"
>>         self waitSemaphore wait
>>     ].
>>     more stuff here...
>>
>> So, the trouble I'm having is that the loop works only intermittently.
>>  It seems like sometimes the 100 microsecond timer simply misfires.
>> The loop will run along fine and then just stop.  Is there a bug in
>> the timer?  Am I seeing a race condition?
>>
>> My reason for waiting on a semaphore even if newEvent is nil is
>> because I'm trying to give the UI a moment run.  Perhaps this isn't
>> necessary?
>>
>> Any thoughts welcome.  Any other suggestions for doing this sort of
>> thing also welcome.
>>
>> Thanks,
>>
>> -Carl Gundel
>> http://www.libertybasic.com
>> http://www.runbasic.com
>> _______________________________________________
>> vwnc mailing list
>> [hidden email]
>> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
>
_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc


This message may contain confidential information and is intended for specific recipients unless explicitly noted otherwise. If you have reason to believe you are not an intended recipient of this message, please delete it and notify the sender. This message may not represent the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and does not constitute a contract or guarantee. Unencrypted electronic mail is not secure and the recipient of this message is expected to provide safeguards from viruses and pursue alternate means of communication where privacy or a binding message is desired.


_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: [7.8] Processing events in a loop?

Paul Baumann
BTW, there are some creative ways to use set excessSignals of a semaphore so that it can be used to manage processing from a queue using more than one parallel process. I don't have example code handy to show you, but this is fairly close to that:


| results ratchetSem launchSem finishSem timeTotal repeat iterations |
results := Array new: 10.
iterations := 500000.
repeat := iterations // results size.
ratchetSem := Semaphore new.
launchSem := Semaphore new.
finishSem := Semaphore new initSignalsTo: results size negated.
1 to: results size do: [:i |
        [
                | t pos obj |
                pos := i.
                obj := Object new.
                ratchetSem signal.
                launchSem wait.
                t := Time microsecondsToRun: [repeat timesRepeat: [
                        "EventHandlers size == 0."
                        EventHandlers at: obj ifAbsent: [].
                        Processor activeProcess yield.
                ]].
                results at: pos put: t.
                finishSem signal.
        ] fork.
        ratchetSem wait.
].
timeTotal := Time microsecondClock.
results size timesRepeat: [launchSem signal].
finishSem signal.
finishSem wait.
timeTotal := Time microsecondClock - timeTotal.
Array
        with: (timeTotal / iterations) asFloat
        with: 1 // (timeTotal / 1000000 / iterations)
        with: timeTotal
        with: results


I used the code above to compare the performance of EphemeronDictionary>>at:ifAbsent: with an approach based on using a simple size check to determine when the cost can be avoided (I saved 92% with a few changes). I also wanted to see if process contention would degrade performance of the #at:ifAbsent: (it didn't). The test divides work into 10 processes that are ratched/scheduled into starting position and trigged (by launchSem) to start all at once so none gets an advantage over another. The 'finishSem' is the one with a trick that allows requires all 10 to finish before results are determined. excessSignals is initialized to start with a negative number of signals.

The trick you'd use if you wanted to get more than once process servicing a single queue would be more like initializing with extra signals rather than fewer. I've posted tricks that are probably closer to what you'd actually use, but this gives you some ideas of what is possible.

Paul Baumann


-----Original Message-----
From: [hidden email] [mailto:[hidden email]] On Behalf Of Paul Baumann
Sent: Monday, November 19, 2012 15:16
To: [hidden email]; [hidden email]
Subject: Re: [vwnc] [7.8] Processing events in a loop?

Carl,

I suspect Timer is doing exactly what it was asked to do. "100 microseconds" is a very high expectation. I don't have Timer and #microseconds in the VW release I use. I assume it some variant of doing "(Delay forMilliseconds: 1) wait" that works for extremely short delays.

I suspect the problem is related to the priority of the processes involved. The Timer is probably doing the #signal at TimingPriority (100) but the signaled process may be below that of another process (like the TimingPriority of your second timer). I suspect the approach is creating many process switches while not giving good time to any one process. I'm thinking that the second timer is causing a process switch (that that would be fast and do nearly nothing) but some other process is getting attention in between returning back to your signaled work process. "Some other process" can be equal to or greater than the priority of that your signaled process is running at. Your lower priority processes would be starved of any attention, and that might affect how quickly new work can come to the queue. You may be starving background GC activities until they they get emergency attention. The emergency GC would be a sudden interruption.

It seems you are trying to prioritize processing by way of rapidly process switching instead of by configuring process priority. You are forcing to ProcessorScheduler to behave different than it works best. A VM needs some time for background activities.

You can do better using an approach that doesn't need even one of these timers. Try an approach that signals a semaphore when adding to the queue so that your queue processor can wait patiently until something is there to work on. Your queue processor would just be in a loop between doing work and waiting (for a signal). If items have been added to the queue since processing started then you'll have excess signals and there will be no delay at all before getting to them. You can set the a high priority for the queue processor while waiting and then bring priority back down to the lowest reasonable priority once it starts doing work. The priority you need to do the work should probably be just one above the priority of the process that added the event. If you want to force a back-and-forth between processing the queue and other work then after each item processed from the queue you could temporarily lower priority of the active process and yield.

I recommend a semaphore based approach I just described, but you could even use a design that works without even a semaphore by just changing process priority and #yield for anything of higher or equal priority. You'd need to be careful with a #yield only based approach because your loop over a yield can starve lower priority processing too.

Paul Baumann



-----Original Message-----
From: [hidden email] [mailto:[hidden email]] On Behalf Of Carl Gundel
Sent: Friday, November 16, 2012 16:12
To: [hidden email]
Subject: Re: [vwnc] [7.8] Processing events in a loop?

Ok, thanks that helped.  Now I see another problem though.  When I
have two timers operating at once I have problems with things
stalling.  Any ideas?

-Carl

On Thu, Nov 15, 2012 at 12:48 AM,  <[hidden email]> wrote:

> Timers can be gc-ed if not held strongly (see more details in the class comment). If you replace
>
>         Timer after: 100 microseconds do: [ semaphore signal ]
>
> with
>
>         Timer after: 100 microseconds signal: semaphore
>
> it should be better.
>
> HTH,
>
> Martin
>
> "Carl Gundel"<[hidden email]> wrote:
>> I'm trying to create a mechanism which will allow me to loop
>> continuously and still allow the user interface to service events from
>> the user and/or timer events.
>>
>> So, I have event handlers to widgets that add user interface events to
>> a queue when the user interacts with them.  Then inside the loop I
>> examine the queue and do something if the queue can provide me with an
>> event from the user (or a timer tick, etc.).
>>
>> So inside the loop I have code like the following:
>>
>>     semaphore := Semaphore new.
>>     (newEvent := self nextEventFromQueue) isNil
>>     ifTrue: [
>>         Timer after: 100 microseconds do: [ semaphore signal ].
>>         self waitSemaphore wait
>>     ]
>>     ifFalse: [
>>         Timer after: 100 microseconds do: [ nextEvent applyTo: self ].
>>  "applyTo: will signal the semaphore"
>>         self waitSemaphore wait
>>     ].
>>     more stuff here...
>>
>> So, the trouble I'm having is that the loop works only intermittently.
>>  It seems like sometimes the 100 microsecond timer simply misfires.
>> The loop will run along fine and then just stop.  Is there a bug in
>> the timer?  Am I seeing a race condition?
>>
>> My reason for waiting on a semaphore even if newEvent is nil is
>> because I'm trying to give the UI a moment run.  Perhaps this isn't
>> necessary?
>>
>> Any thoughts welcome.  Any other suggestions for doing this sort of
>> thing also welcome.
>>
>> Thanks,
>>
>> -Carl Gundel
>> http://www.libertybasic.com
>> http://www.runbasic.com
>> _______________________________________________
>> vwnc mailing list
>> [hidden email]
>> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
>
_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc


This message may contain confidential information and is intended for specific recipients unless explicitly noted otherwise. If you have reason to believe you are not an intended recipient of this message, please delete it and notify the sender. This message may not represent the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and does not constitute a contract or guarantee. Unencrypted electronic mail is not secure and the recipient of this message is expected to provide safeguards from viruses and pursue alternate means of communication where privacy or a binding message is desired.


_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc


This message may contain confidential information and is intended for specific recipients unless explicitly noted otherwise. If you have reason to believe you are not an intended recipient of this message, please delete it and notify the sender. This message may not represent the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and does not constitute a contract or guarantee. Unencrypted electronic mail is not secure and the recipient of this message is expected to provide safeguards from viruses and pursue alternate means of communication where privacy or a binding message is desired.


_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: [7.8] Processing events in a loop?

Andres Valloud-6
Setting up semaphores with negative signals may or may not work that way...

On 11/19/2012 1:06 PM, Paul Baumann wrote:

> BTW, there are some creative ways to use set excessSignals of a semaphore so that it can be used to manage processing from a queue using more than one parallel process. I don't have example code handy to show you, but this is fairly close to that:
>
>
> | results ratchetSem launchSem finishSem timeTotal repeat iterations |
> results := Array new: 10.
> iterations := 500000.
> repeat := iterations // results size.
> ratchetSem := Semaphore new.
> launchSem := Semaphore new.
> finishSem := Semaphore new initSignalsTo: results size negated.
> 1 to: results size do: [:i |
>          [
>                  | t pos obj |
>                  pos := i.
>                  obj := Object new.
>                  ratchetSem signal.
>                  launchSem wait.
>                  t := Time microsecondsToRun: [repeat timesRepeat: [
>                          "EventHandlers size == 0."
>                          EventHandlers at: obj ifAbsent: [].
>                          Processor activeProcess yield.
>                  ]].
>                  results at: pos put: t.
>                  finishSem signal.
>          ] fork.
>          ratchetSem wait.
> ].
> timeTotal := Time microsecondClock.
> results size timesRepeat: [launchSem signal].
> finishSem signal.
> finishSem wait.
> timeTotal := Time microsecondClock - timeTotal.
> Array
>          with: (timeTotal / iterations) asFloat
>          with: 1 // (timeTotal / 1000000 / iterations)
>          with: timeTotal
>          with: results
>
>
> I used the code above to compare the performance of EphemeronDictionary>>at:ifAbsent: with an approach based on using a simple size check to determine when the cost can be avoided (I saved 92% with a few changes). I also wanted to see if process contention would degrade performance of the #at:ifAbsent: (it didn't). The test divides work into 10 processes that are ratched/scheduled into starting position and trigged (by launchSem) to start all at once so none gets an advantage over another. The 'finishSem' is the one with a trick that allows requires all 10 to finish before results are determined. excessSignals is initialized to start with a negative number of signals.
>
> The trick you'd use if you wanted to get more than once process servicing a single queue would be more like initializing with extra signals rather than fewer. I've posted tricks that are probably closer to what you'd actually use, but this gives you some ideas of what is possible.
>
> Paul Baumann
>
>
> -----Original Message-----
> From: [hidden email] [mailto:[hidden email]] On Behalf Of Paul Baumann
> Sent: Monday, November 19, 2012 15:16
> To: [hidden email]; [hidden email]
> Subject: Re: [vwnc] [7.8] Processing events in a loop?
>
> Carl,
>
> I suspect Timer is doing exactly what it was asked to do. "100 microseconds" is a very high expectation. I don't have Timer and #microseconds in the VW release I use. I assume it some variant of doing "(Delay forMilliseconds: 1) wait" that works for extremely short delays.
>
> I suspect the problem is related to the priority of the processes involved. The Timer is probably doing the #signal at TimingPriority (100) but the signaled process may be below that of another process (like the TimingPriority of your second timer). I suspect the approach is creating many process switches while not giving good time to any one process. I'm thinking that the second timer is causing a process switch (that that would be fast and do nearly nothing) but some other process is getting attention in between returning back to your signaled work process. "Some other process" can be equal to or greater than the priority of that your signaled process is running at. Your lower priority processes would be starved of any attention, and that might affect how quickly new work can come to the queue. You may be starving background GC activities until they they get emergency attention. The emergency GC would be a sudden interruption.
>
> It seems you are trying to prioritize processing by way of rapidly process switching instead of by configuring process priority. You are forcing to ProcessorScheduler to behave different than it works best. A VM needs some time for background activities.
>
> You can do better using an approach that doesn't need even one of these timers. Try an approach that signals a semaphore when adding to the queue so that your queue processor can wait patiently until something is there to work on. Your queue processor would just be in a loop between doing work and waiting (for a signal). If items have been added to the queue since processing started then you'll have excess signals and there will be no delay at all before getting to them. You can set the a high priority for the queue processor while waiting and then bring priority back down to the lowest reasonable priority once it starts doing work. The priority you need to do the work should probably be just one above the priority of the process that added the event. If you want to force a back-and-forth between processing the queue and other work then after each item processed from the queue you could temporarily lower priority of the active process and yield.
>
> I recommend a semaphore based approach I just described, but you could even use a design that works without even a semaphore by just changing process priority and #yield for anything of higher or equal priority. You'd need to be careful with a #yield only based approach because your loop over a yield can starve lower priority processing too.
>
> Paul Baumann
>
>
>
> -----Original Message-----
> From: [hidden email] [mailto:[hidden email]] On Behalf Of Carl Gundel
> Sent: Friday, November 16, 2012 16:12
> To: [hidden email]
> Subject: Re: [vwnc] [7.8] Processing events in a loop?
>
> Ok, thanks that helped.  Now I see another problem though.  When I
> have two timers operating at once I have problems with things
> stalling.  Any ideas?
>
> -Carl
>
> On Thu, Nov 15, 2012 at 12:48 AM,  <[hidden email]> wrote:
>> Timers can be gc-ed if not held strongly (see more details in the class comment). If you replace
>>
>>          Timer after: 100 microseconds do: [ semaphore signal ]
>>
>> with
>>
>>          Timer after: 100 microseconds signal: semaphore
>>
>> it should be better.
>>
>> HTH,
>>
>> Martin
>>
>> "Carl Gundel"<[hidden email]> wrote:
>>> I'm trying to create a mechanism which will allow me to loop
>>> continuously and still allow the user interface to service events from
>>> the user and/or timer events.
>>>
>>> So, I have event handlers to widgets that add user interface events to
>>> a queue when the user interacts with them.  Then inside the loop I
>>> examine the queue and do something if the queue can provide me with an
>>> event from the user (or a timer tick, etc.).
>>>
>>> So inside the loop I have code like the following:
>>>
>>>      semaphore := Semaphore new.
>>>      (newEvent := self nextEventFromQueue) isNil
>>>      ifTrue: [
>>>          Timer after: 100 microseconds do: [ semaphore signal ].
>>>          self waitSemaphore wait
>>>      ]
>>>      ifFalse: [
>>>          Timer after: 100 microseconds do: [ nextEvent applyTo: self ].
>>>   "applyTo: will signal the semaphore"
>>>          self waitSemaphore wait
>>>      ].
>>>      more stuff here...
>>>
>>> So, the trouble I'm having is that the loop works only intermittently.
>>>   It seems like sometimes the 100 microsecond timer simply misfires.
>>> The loop will run along fine and then just stop.  Is there a bug in
>>> the timer?  Am I seeing a race condition?
>>>
>>> My reason for waiting on a semaphore even if newEvent is nil is
>>> because I'm trying to give the UI a moment run.  Perhaps this isn't
>>> necessary?
>>>
>>> Any thoughts welcome.  Any other suggestions for doing this sort of
>>> thing also welcome.
>>>
>>> Thanks,
>>>
>>> -Carl Gundel
>>> http://www.libertybasic.com
>>> http://www.runbasic.com
>>> _______________________________________________
>>> vwnc mailing list
>>> [hidden email]
>>> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
>>
> _______________________________________________
> vwnc mailing list
> [hidden email]
> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
>
>
> This message may contain confidential information and is intended for specific recipients unless explicitly noted otherwise. If you have reason to believe you are not an intended recipient of this message, please delete it and notify the sender. This message may not represent the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and does not constitute a contract or guarantee. Unencrypted electronic mail is not secure and the recipient of this message is expected to provide safeguards from viruses and pursue alternate means of communication where privacy or a binding message is desired.
>
>
> _______________________________________________
> vwnc mailing list
> [hidden email]
> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
>
>
> This message may contain confidential information and is intended for specific recipients unless explicitly noted otherwise. If you have reason to believe you are not an intended recipient of this message, please delete it and notify the sender. This message may not represent the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and does not constitute a contract or guarantee. Unencrypted electronic mail is not secure and the recipient of this message is expected to provide safeguards from viruses and pursue alternate means of communication where privacy or a binding message is desired.
>
>
> _______________________________________________
> vwnc mailing list
> [hidden email]
> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
>
_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: [7.8] Processing events in a loop?

Paul Baumann
Andres wrote:
>>Setting up semaphores with negative signals may or may not work that way...

Andres,

Please do better than that. Your reply will be constructive when it contains an example of how the use of 'finishSem' that I showed, initialized with negative signals, does not work as a way to suspend the main process until all the child processes have signaled that they have finished.

Here are some contrivances from which you can search for a flaw...

You might modify the example to "not work" by setting the main process to run at a higher priority than the child processes. You might raise an error in one of the child processes so that the main process will wait indefinitely; and declare it does not work for your needs. You might attempt to make finishSem take the responsibility of either the ratchetSem or launchSem and use that design failure to suggest that how finishSem actually is used would not work. Or, you might remove the ratchetSem or launchSem to create a situation of a signal following the wait on finishSem that you are looking for. You might modify the implementation of the VM or core classes like Semaphore and ProcessorScheduler to break this alone while traditional uses of semaphores would continue to work.

As far as I can tell, it works perfectly (in VW 7.5 with a VW 7.6 VM) as I've shown and it does make an example of a creative way that semaphores can be used. I'll find it interesting to see how you show otherwise.

Paul Baumann


-----Original Message-----
From: [hidden email] [mailto:[hidden email]] On Behalf Of Andres Valloud
Sent: Monday, November 19, 2012 18:03
To: [hidden email]
Subject: Re: [vwnc] [7.8] Processing events in a loop?

Setting up semaphores with negative signals may or may not work that way...

On 11/19/2012 1:06 PM, Paul Baumann wrote:

> BTW, there are some creative ways to use set excessSignals of a semaphore so that it can be used to manage processing from a queue using more than one parallel process. I don't have example code handy to show you, but this is fairly close to that:
>
>
> | results ratchetSem launchSem finishSem timeTotal repeat iterations |
> results := Array new: 10.
> iterations := 500000.
> repeat := iterations // results size.
> ratchetSem := Semaphore new.
> launchSem := Semaphore new.
> finishSem := Semaphore new initSignalsTo: results size negated.
> 1 to: results size do: [:i |
>          [
>                  | t pos obj |
>                  pos := i.
>                  obj := Object new.
>                  ratchetSem signal.
>                  launchSem wait.
>                  t := Time microsecondsToRun: [repeat timesRepeat: [
>                          "EventHandlers size == 0."
>                          EventHandlers at: obj ifAbsent: [].
>                          Processor activeProcess yield.
>                  ]].
>                  results at: pos put: t.
>                  finishSem signal.
>          ] fork.
>          ratchetSem wait.
> ].
> timeTotal := Time microsecondClock.
> results size timesRepeat: [launchSem signal].
> finishSem signal.
> finishSem wait.
> timeTotal := Time microsecondClock - timeTotal.
> Array
>          with: (timeTotal / iterations) asFloat
>          with: 1 // (timeTotal / 1000000 / iterations)
>          with: timeTotal
>          with: results
>
>
> I used the code above to compare the performance of EphemeronDictionary>>at:ifAbsent: with an approach based on using a simple size check to determine when the cost can be avoided (I saved 92% with a few changes). I also wanted to see if process contention would degrade performance of the #at:ifAbsent: (it didn't). The test divides work into 10 processes that are ratched/scheduled into starting position and trigged (by launchSem) to start all at once so none gets an advantage over another. The 'finishSem' is the one with a trick that allows requires all 10 to finish before results are determined. excessSignals is initialized to start with a negative number of signals.
>
> The trick you'd use if you wanted to get more than once process servicing a single queue would be more like initializing with extra signals rather than fewer. I've posted tricks that are probably closer to what you'd actually use, but this gives you some ideas of what is possible.
>
> Paul Baumann
>
>
...

This message may contain confidential information and is intended for specific recipients unless explicitly noted otherwise. If you have reason to believe you are not an intended recipient of this message, please delete it and notify the sender. This message may not represent the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and does not constitute a contract or guarantee. Unencrypted electronic mail is not secure and the recipient of this message is expected to provide safeguards from viruses and pursue alternate means of communication where privacy or a binding message is desired.


_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: [7.8] Processing events in a loop?

Niall Ross
Dear Paul,
    what Andres means (I think) is that setting negative signals works
as you expect  in VW 7.8.1 and earlier, but in 7.9 and 7.9.1 the
behaviour has changed.  I had to deal with this in the
ProcrastinatingSemaphore class which is defined in the
SUnitXProcPatterns parcel (part of the distro since 7.7).

In the 7.9 version, I added a 'deficitSignals' instVar to
ProcrastinatingSemaphore in place of its earlier behaviour of using
negative excessSignals values.  This is because, in 7.9 and 7.9.1, the
superclass' (Semaphore's) #wait primitive treats a negative
excessSignals value identically to a zero excessSignals value.  This is
more in line with the Smalltalk-equivalent code shown in its method, but
is not what 7.8.1 and earlier did.

          HTH
                Niall Ross

>Andres wrote:
>  
>
>>>Setting up semaphores with negative signals may or may not work that way...
>>>      
>>>
>
>Andres,
>
>Please do better than that. Your reply will be constructive when it contains an example of how the use of 'finishSem' that I showed, initialized with negative signals, does not work as a way to suspend the main process until all the child processes have signaled that they have finished.
>
>Here are some contrivances from which you can search for a flaw...
>
>You might modify the example to "not work" by setting the main process to run at a higher priority than the child processes. You might raise an error in one of the child processes so that the main process will wait indefinitely; and declare it does not work for your needs. You might attempt to make finishSem take the responsibility of either the ratchetSem or launchSem and use that design failure to suggest that how finishSem actually is used would not work. Or, you might remove the ratchetSem or launchSem to create a situation of a signal following the wait on finishSem that you are looking for. You might modify the implementation of the VM or core classes like Semaphore and ProcessorScheduler to break this alone while traditional uses of semaphores would continue to work.
>
>As far as I can tell, it works perfectly (in VW 7.5 with a VW 7.6 VM) as I've shown and it does make an example of a creative way that semaphores can be used. I'll find it interesting to see how you show otherwise.
>
>Paul Baumann
>
>
>-----Original Message-----
>From: [hidden email] [mailto:[hidden email]] On Behalf Of Andres Valloud
>Sent: Monday, November 19, 2012 18:03
>To: [hidden email]
>Subject: Re: [vwnc] [7.8] Processing events in a loop?
>
>Setting up semaphores with negative signals may or may not work that way...
>
>On 11/19/2012 1:06 PM, Paul Baumann wrote:
>  
>
>>BTW, there are some creative ways to use set excessSignals of a semaphore so that it can be used to manage processing from a queue using more than one parallel process. I don't have example code handy to show you, but this is fairly close to that:
>>
>>
>>| results ratchetSem launchSem finishSem timeTotal repeat iterations |
>>results := Array new: 10.
>>iterations := 500000.
>>repeat := iterations // results size.
>>ratchetSem := Semaphore new.
>>launchSem := Semaphore new.
>>finishSem := Semaphore new initSignalsTo: results size negated.
>>1 to: results size do: [:i |
>>         [
>>                 | t pos obj |
>>                 pos := i.
>>                 obj := Object new.
>>                 ratchetSem signal.
>>                 launchSem wait.
>>                 t := Time microsecondsToRun: [repeat timesRepeat: [
>>                         "EventHandlers size == 0."
>>                         EventHandlers at: obj ifAbsent: [].
>>                         Processor activeProcess yield.
>>                 ]].
>>                 results at: pos put: t.
>>                 finishSem signal.
>>         ] fork.
>>         ratchetSem wait.
>>].
>>timeTotal := Time microsecondClock.
>>results size timesRepeat: [launchSem signal].
>>finishSem signal.
>>finishSem wait.
>>timeTotal := Time microsecondClock - timeTotal.
>>Array
>>         with: (timeTotal / iterations) asFloat
>>         with: 1 // (timeTotal / 1000000 / iterations)
>>         with: timeTotal
>>         with: results
>>
>>
>>I used the code above to compare the performance of EphemeronDictionary>>at:ifAbsent: with an approach based on using a simple size check to determine when the cost can be avoided (I saved 92% with a few changes). I also wanted to see if process contention would degrade performance of the #at:ifAbsent: (it didn't). The test divides work into 10 processes that are ratched/scheduled into starting position and trigged (by launchSem) to start all at once so none gets an advantage over another. The 'finishSem' is the one with a trick that allows requires all 10 to finish before results are determined. excessSignals is initialized to start with a negative number of signals.
>>
>>The trick you'd use if you wanted to get more than once process servicing a single queue would be more like initializing with extra signals rather than fewer. I've posted tricks that are probably closer to what you'd actually use, but this gives you some ideas of what is possible.
>>
>>Paul Baumann
>>
>>
>>    
>>
>...
>
>This message may contain confidential information and is intended for specific recipients unless explicitly noted otherwise. If you have reason to believe you are not an intended recipient of this message, please delete it and notify the sender. This message may not represent the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and does not constitute a contract or guarantee. Unencrypted electronic mail is not secure and the recipient of this message is expected to provide safeguards from viruses and pursue alternate means of communication where privacy or a binding message is desired.
>
>
>_______________________________________________
>vwnc mailing list
>[hidden email]
>http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
>
>
>  
>


_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: [7.8] Processing events in a loop?

Paul Baumann
Thanks Niall.

A primitive behaving different from Smalltalk code would explain why Andres would say "may or may not" (if the primitive were to fail). Several years ago I used Semaphore tricks more frequently than I have lately. The difference between code and primitive is starting to sound familiar. I wonder if it was a good thing that they made the primitive consistent with code rather than code consistent with the primitive.

It had been convenient that the original Semaphore implementation could do more than was customary. It would now take several variants of Semaphore to do tricks that had been possible with the original and now none would use the primitive. At least code intent can be clearer if Sempahore variants become common.

Paul


-----Original Message-----
From: Niall Ross [mailto:[hidden email]]
Sent: Friday, November 23, 2012 09:43
To: Paul Baumann
Cc: Andres Valloud; [hidden email]
Subject: Re: [vwnc] [7.8] Processing events in a loop?

Dear Paul,
    what Andres means (I think) is that setting negative signals works
as you expect  in VW 7.8.1 and earlier, but in 7.9 and 7.9.1 the
behaviour has changed.  I had to deal with this in the
ProcrastinatingSemaphore class which is defined in the
SUnitXProcPatterns parcel (part of the distro since 7.7).

In the 7.9 version, I added a 'deficitSignals' instVar to
ProcrastinatingSemaphore in place of its earlier behaviour of using
negative excessSignals values.  This is because, in 7.9 and 7.9.1, the
superclass' (Semaphore's) #wait primitive treats a negative
excessSignals value identically to a zero excessSignals value.  This is
more in line with the Smalltalk-equivalent code shown in its method, but
is not what 7.8.1 and earlier did.

          HTH
                Niall Ross

>Andres wrote:
>
>
>>>Setting up semaphores with negative signals may or may not work that way...
>>>
>>>

This message may contain confidential information and is intended for specific recipients unless explicitly noted otherwise. If you have reason to believe you are not an intended recipient of this message, please delete it and notify the sender. This message may not represent the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and does not constitute a contract or guarantee. Unencrypted electronic mail is not secure and the recipient of this message is expected to provide safeguards from viruses and pursue alternate means of communication where privacy or a binding message is desired.


_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc