![]() |
|
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) But I also send packets that I use to control the timing relationship between the master and the clients. So what I did was to continually look for and process packets in the wait time at the end of every frame. 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. So I looked into and implemented a separate thread to look for and process packets. 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! 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 Initially I just simply tried al_rest() Then I tried using a timer. 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? 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) 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
![]() |
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
![]() |
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
![]() |
al_rest(0)? |
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
![]() |
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: 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? The main thing I want to do is get the packets arrival time. 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: 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' 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: 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. I experimented with C++ 11 std:threads and got very similar results 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. 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.
|
|