Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Using very short allegro timers in separate thread and getting lots of cpu usage

This thread is locked; no one can reply to it. rss feed Print
Using very short allegro timers in separate thread and getting lots of cpu usage
Michael Weiss
Member #223
April 2000

I am trying to use an allegro timer to control how often I check for packets in a thread dedicated just to checking for and reading packets into a buffer.

It works as expected, but takes up almost 100% cpu for all 4 cores (I'm looking in windows task manager)

The whole reason I started this....

I used to check for packets once per frame (in my case 25ms)
This was/is fine for game state info, as I can't really use it any earlier anyway.

But I also send packets that I use to control the timing relationship between the master and the clients.
These packets I want to receive and process immediately, not wait until the end of the 25ms game loop.

So what I did was to continually look for and process packets in the wait time at the end of every frame.
This improved the accuracy of timing when I received the packets.

loop()
{
   if (game_loop_event_timer)
   {
      get input
      move
      draw
   }

   fast_packet_proc()

}

But there was still a problem.

When the time it takes to process and draw a frame increases, the wait time at the end of the frame decreases.
And that is the only time I was checking for new packets.
If it took 20ms to process a frame, I only had 5ms left to receive packets.
And then my timing was considerably less accurate.

So I looked into and implemented a separate thread to look for and process packets.
If it is a timing packet, it is processed immediately.
Otherwise is is added to a buffer and processed at the start of the next frame.

That worked, but I am having some issues about timing.

Initially the thread ran as fast as it could:

void *mwPacketBuffer::rx_thread_func(ALLEGRO_THREAD *thr, void *arg)
{
   mPacketBuffer.add_to_rx_buffer();
}

Sometimes it would run 10,000 times per frame!
Besides the wasted time and resources, it also ran so fast, it barely left any time the mutexs were unlocked.

So I experimented with ways to throttle it down to a more reasonable time

Instead of 10,000 times per frame, I would be happy if it only ran 250 times
That way I could measure time to .1ms or 100us

Initially I just simply tried al_rest()

Then I tried using a timer.

#SelectExpand
1thr_timer = al_create_timer(0.0001); // .1ms 2al_register_event_source(event_queue, al_get_timer_event_source(thr_timer)); 3al_start_timer(thr_timer); 4if (ev.timer.source == thr_timer) thr_proc = 1; 5 6void *mwPacketBuffer::rx_thread_func(ALLEGRO_THREAD *thr, void *arg) 7{ 8 while (1) 9 { 10// al_rest(0.001); 11// mPacketBuffer.add_to_rx_buffer(); 12 13 if (mEventQueue.thr_proc) 14 { 15 mPacketBuffer.add_to_rx_buffer(); 16 mEventQueue.thr_proc = 0; 17 } 18 } 19}

It seems to work and my game seems to runs fine, but in windows task manager all four cores peg out at nearly 100%

Is this normal for allegro timers?
Am I using them wrong?
Is it because the timer interval is so small? (100us)

I really want to find a way to limit the thread from running wide open at 10,000 iterations per frame, that also doesn't take up so much cpu time.

Also because my project (Purple Martians) is cross platform (windows and linux)
I do not want any platform specific solutions. That is why I am using allegro threads and allegro timers, etc...

Also any other advice or pointers would be greatly appreciated!

I am really new to threads, this is the first time I have actually used them for something other than simple example programs.

DanielH
Member #934
January 2001
avatar

I would expect that with while(1). The thread needs to cede remaining time by sleeping, but sleep after whatever you need to do first.

Michael Weiss
Member #223
April 2000

How do I cede the remaining time?

If I exit my while loop, my thread will be done, right?

My thread is always running, waiting for something to process.

Am I thinking about this the wrong way?

Basically I want the thread to always be looking for new packets to process.

DanielH
Member #934
January 2001
avatar

What happens when you add the al_rest?

while (1)
{
     // mPacketBuffer.add_to_rx_buffer();

     if (mEventQueue.thr_proc)
     {
          mPacketBuffer.add_to_rx_buffer();
          mEventQueue.thr_proc = 0;
     }
     al_rest(0.001);
}

Michael Weiss
Member #223
April 2000

I never did both at the same time, It was always one or the other.

void *mwPacketBuffer::rx_thread_func(ALLEGRO_THREAD *thr, void *arg)
{
   while (1)
   {
      al_rest(0.01);
      mPacketBuffer.add_to_rx_buffer();
   }
}

or

void *mwPacketBuffer::rx_thread_func(ALLEGRO_THREAD *thr, void *arg)
{
   while (1)
   {
      if (mEventQueue.thr_proc)
      {
         mPacketBuffer.add_to_rx_buffer();
         mEventQueue.thr_proc = 0;
      }
   }
}

I just ran some tests:

Timer    expected    al_rest                  al_timer
value    calls/sec   cpu  actual calls/sec    cpu  actual calls/sec
-------------------------------------------------------------------
0.01     100         25   90-99               50%  100
0.005    200         25   160-199             50%  200
0.001    1000        50   500-1000            75%  1000
0.0005   2000        75   1,000,000           75%  2000
0.0001   10000       75   1,000,000           70%  9000

I seems like I use less cpu with al_rest, but the problem with al_rest is that it does not seem to work well with short times.

I gave up on it quite early in testing because of that.

I read the manual for time routines many times:

void al_rest(double seconds)

Waits for the specified number of seconds. This tells the system to pause the
current thread for the given amount of time. With some operating systems, the
accuracy can be in the order of 10ms. That is, even

al_rest(0.000001)

might pause for something like 10ms. Also see the section on Timer routines for
easier ways to time your program without using up all CPU.

That led me to trying to use timers....

al_create_timer

ALLEGRO_TIMER *al_create_timer(double speed_secs)

Allocates and initializes a timer. If successful, a pointer to a new timer object
is returned, otherwise NULL is returned. speed_secs is in seconds per tick, and
must be positive. The new timer is initially stopped and needs to be started with
al_start_timer before ALLEGRO_EVENT_TIMER events are sent.

Usage note: typical granularity is on the order of microseconds, but with some
drivers might only be milliseconds.

So i think I might be just running into the limits of what al_rest() can do.

But why does al_timer seem to be able to handle shorter times than al_rest()?

Are they not both limited by the underlying system?

DanielH
Member #934
January 2001
avatar

Michael Weiss
Member #223
April 2000

I will try that later, I ran out of time this morning and have to go to work....

What I did try was:

void *mwPacketBuffer::rx_thread_func(ALLEGRO_THREAD *thr, void *arg)
{
   while (1)
   {
      static double t0 = al_get_time();
      double t1 = al_get_time();
      if (t1-t0>0.0001)
      {
         t0 = t1;
         mPacketBuffer.add_to_rx_buffer();
      }
   }
}

I seem to be getting a solid 50% cpu usage and the expected number of packets.

I will test more later.

Thank you for your suggestions, I will try al_rest(0) later......

DanielH
Member #934
January 2001
avatar

#SelectExpand
1thr_timer = al_create_timer(0.0001); // .1ms 2al_register_event_source(event_queue, al_get_timer_event_source(thr_timer)); 3al_start_timer(thr_timer); 4if (ev.timer.source == thr_timer) ++thr_proc; 5 6void *mwPacketBuffer::rx_thread_func(ALLEGRO_THREAD *thr, void *arg) 7{ 8 while (1) 9 { 10 while (mEventQueue.thr_proc) 11 { 12 mPacketBuffer.add_to_rx_buffer(); 13 --mEventQueue.thr_proc; 14 } 15 al_rest(0.01); 16 } 17}

Polybios
Member #12,293
October 2010

Just a thought, but how about pushing the packets (or rather, their availability state) through an (user) event source so you could simply wait until a new packet is available?

Edit: Ah, okay, didn't read the thread right I think. How about resting the remainder of the time that you didn't need to consume the packet? Or have you tried that already? Do you need to poll for packets or is there a way to wait for them (I don't know what networking lib you are using)?

Michael Weiss
Member #223
April 2000

DanielH

Your last suggestion:

#SelectExpand
1 2thr_timer = al_create_timer(0.0001); // .1ms 3al_register_event_source(event_queue, al_get_timer_event_source(thr_timer)); 4al_start_timer(thr_timer); 5if (ev.timer.source == thr_timer) ++thr_proc; 6 7void *mwPacketBuffer::rx_thread_func(ALLEGRO_THREAD *thr, void *arg) 8{ 9 while (1) 10 { 11 while (mEventQueue.thr_proc) 12 { 13 mPacketBuffer.add_to_rx_buffer(); 14 --mEventQueue.thr_proc; 15 } 16 al_rest(0.01); 17 } 18}

Would it not cause bursts?

While al_rest(0.01), would not thr_proc increment to 100?
Then a burst of 100 'add_to_rx_buffer' while thr_proc decrements back to zero?

The main thing I want to do is get the packets arrival time.
I am not actually processing them yet, I am just sticking them in a buffer for later processing.

I have to poll to check if there are any packets waiting.

This is what I want to do at a regular interval.

I have thought about it today at work and I realize a mistake I made.

Using event timers like I do, depends on how often I process the event queue.

And I am still processing that like this:

#SelectExpand
1 2loop() 3{ 4 proc_event_queue() 5 6 if (game_loop_event_timer) 7 { 8 get input 9 move 10 draw 11 } 12 13 14}

When the code in the game loop takes a significant time I am not processing the event queue, so I am not setting the variable that triggers the 'add_to_rx_buffer'
in the other thread.

This is exactly the problem I was trying to solve in the first place!

So I think I will abandon allegro timers for this case and tentatively use my own custom self contained al_rest:

#SelectExpand
1 2void *mwPacketBuffer::rx_thread_func(ALLEGRO_THREAD *thr, void *arg) 3{ 4 while (1) 5 { 6 static double t0 = al_get_time(); 7 double t1 = al_get_time(); 8 if (t1-t0>0.0001) 9 { 10 t0 = t1; 11 mPacketBuffer.add_to_rx_buffer(); 12 } 13 } 14}

I still have plenty of testing to do though...

Polybios:

I have to actively poll to see if a new packet is available.

I am using an ancient library: libnet 0.10.2 from 1999

Thank you both for your time and suggestions.

I need to do much more experimenting and testing....

EDIT

I have done much more experimenting and here is what I have found:

Allegro threads with al_rest() work like I would expect. They do not spin lock and waste cpu time.
The down side is that I cannot seem to get lower than 1 ms with al_rest() and 2ms on a windows 10 machine.

I experimented with C++ 11 std:threads and got very similar results
I used std::this_thread::sleep_for() but I was again not able to get better than 1ms.

I am pretty sure its limited by the underlying system.

My method of:

static double t0 = al_get_time();
double t1 = al_get_time();
if (t1-t0 > 0.001)

turned out to be the worst of all.
It could actually do times less than 1ms, but the high cpu usage caused everything else to break.

So I will probably stick with running the packet checking thread at 1000Hz, which is not so bad.

Or I will go back to the old method, of checking as fast as it will go, but only in the wait time after processing the game loop.

I have actually added a few more manual packets checks in the middle of the game loop to help.

It is really a shame though that it does not seem possible to throttle the speed of the thread past a certain point.

1000Hz (or 1ms) seems to be the limit. But yet when there is no limit it can run at 200kHz (5us) to over 1MHz (<1us)

That seems like such a huge difference.

Go to: