After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 675695 - High CPU on idle application, on Windows, signal_timeout
High CPU on idle application, on Windows, signal_timeout
Status: RESOLVED OBSOLETE
Product: glib
Classification: Platform
Component: mainloop
2.28.x
Other Windows
: Normal normal
: ---
Assigned To: gtkdev
gtkdev
Depends on:
Blocks: 675485
 
 
Reported: 2012-05-08 17:34 UTC by mathog
Modified: 2018-05-24 14:12 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Example case (1.39 KB, application/x-zip-compressed)
2012-05-09 16:15 UTC, mathog
Details

Description mathog 2012-05-08 17:34:06 UTC
This was originally posted under glibm as bug 675485, but I have since traced the problem into glib's gmain.c and gpoll.c, so it belongs here.  When built on mingw on XP SP3 (32 bit) the attached example runs at 17% CPU even when minimized.  

The problem is that when Glib::signal_timeout().connect is used, or equivalently, 

    source = g_timeout_source_new(50);
    g_source_set_callback(source,
                          (GSourceFunc)slot_timeout,
                          NULL,
                          NULL);
    g_source_attach(source,
                    NULL);

the polling method in glib goes nuts for the test program.  (Or any program, this was initially discovered on Inkscape.
  https://bugs.launchpad.net/inkscape/+bug/871968
)  Removing the signal_timeout from the test program lets it run normally (no idle CPU use).

For the simplest case, the test application window minimized (so no keyboard or mouse input): 

What it should do:  timeout every 50 msecs, run the 
slot_timeout function, and then wait another 50 msecs.

What it does: timeout once initially at 50 msecs, run the slot function, then set a timeout of 4 msecs or 0 msecs, both of which time out instantly. Eventually it gets back to 50 msec, which is probably why it only uses 17% of CPU and not 100%.   

If the timeout is set very long, say 200 msecs, then it does what it is supposed to do, without any of the timeout confusion.  It just does a succession of 0.2 sec waits and runs of slot_timeout().  It starts to become a problem at around 100 msecs and becomes pathological for timeouts <20 ms.  (Too short for the hardware, probably.)  

The same code run under wine (32 bit, on Ubuntu) behaves exactly the same way.

Near as I can tell the problem is in g_main_context_prepare, which comes up with these other timeout values.  My guess is that its original purpose was to
handle a case like:
  
MsgWaitForMultipleObjectsEx with wait of 100 msec
another event happens
set wait for next MsgWaitForMultipleObjectsEx for remainder of 100 msec

but it is making a total hash of the situation where there is no other event and the initial timeout is on the order of 50 msec.
Comment 1 mathog 2012-05-08 19:16:04 UTC
The change shown below resolves the problem for me, but at the expense of not being able to set a timer shorter than 10ms.  This solution seems to work OK with inkscape (drops CPU usage from 17% to 0% when idle) and the program "feels" the same as before.

In glib/gmain.c in the g_timeout_prepare function change:

  if (now < timeout_source->expiration)
    {
      /* Round up to ensure that we don't try again too early */
      *timeout = (timeout_source->expiration - now + 999) / 1000;
      return FALSE;
    }


to

  if (now < timeout_source->expiration
#ifdef G_OS_WIN32
      && timeout_source->expiration - now > 10000) //minimum of 10msec wait
#else
      )
#endif
    {
      /* Round up to ensure that we don't try again too early */
      *timeout = (timeout_source->expiration - now + 999) / 1000;
      return FALSE;
    }


This section of code is the same in 2.32.2 as it is in 2.28.8
Comment 2 mathog 2012-05-08 23:49:32 UTC
Hmm, this does not work as well under Wine.  On Windows/mingw idle CPU is down to <1%, but on Wine it varies between 2 and 9% - better than it was, but not fixed.
Comment 3 mathog 2012-05-09 16:15:41 UTC
Created attachment 213751 [details]
Example case

2nd try at getting attachment to upload, seems to have fallen off the first post.
Comment 4 Dan Winship 2012-05-14 13:51:10 UTC
the comments in the win32 implementations of g_get_monotonic_time() (in gmain.c) and g_poll() (in gpoll.c) are probably relevant; the timers we're using only have about a 15ms resolution, and our windows g_poll() explicitly ignores any timeout of less than 10ms.
Comment 5 mathog 2012-05-14 20:35:34 UTC
Exactly - poll_rest() is never called with a timeout value of <10ms.  So if timeout is <10ms the rest of the code will spin like crazy (for up to 10ms) burning up CPU time.  In fact, in the original test code the delay was 33ms and the observed idle CPU usage was 16-17%.  The jitter should be close to random.  If it ends up with a timeout >10ms it rests, so no CPU usage.  However, if it lands with a timeout <10ms, then on average it spins for 5ms (from a flat distribution within the 10 ms, where it never rests), and 5/33 = .15 = 15%.  15% is close enough for this type of hand waving to the observed 16-17% CPU usage. Set
the timeout in the program to <10ms and all it can do is spin, so 100% CPU usage.

It may well be that removing the 10ms condition here:

      if (retval == 0 && (timeout == INFINITE || timeout >= 10))
	retval = poll_rest (poll_msgs, handles, nhandles, fds, nfds, timeout);

would work as well as the change I suggested at comment 1.  Either way the timer is not going to be accurate to 10 ms, and given that, better not to spin and burn CPU needlessly.  (Unless some application really needs accurate timing, in which case spinning might be the only option, but that isn't the case with this application.  There is a nice discussion of high precision timers on Win32, more or less relevant to this point,  by the virtualdub folks here: http://www.virtualdub.org/blog/pivot/entry.php?id=272
Comment 6 mathog 2012-05-15 16:08:04 UTC
Confirmed.  In gpoll.c changing this:

      if (retval == 0 && (timeout == INFINITE || timeout >= 10))
    retval = poll_rest (poll_msgs, handles, nhandles, fds, nfds, timeout);

to this

      if (retval == 0)
    retval = poll_rest (poll_msgs, handles, nhandles, fds, nfds, timeout);

(while removing the change from comment 1) also eliminated the idle CPU usage, both in the test program, and in inkscape.  In limited testing I could not see any big difference in the way inkscape "felt" between the two forms.
Comment 7 mathog 2012-05-15 16:38:29 UTC
Note that while these changes result in ~0% idle CPU usage on Windows XP, in Wine
they both run around 8% (varies a lot).  Hardware the same in both cases:

AMD Athlon(tm) II X2 245 Processor (dual core)
Gigabyte MA785GMT-UD2H motherboard
Comment 8 GNOME Infrastructure Team 2018-05-24 14:12:59 UTC
-- GitLab Migration Automatic Message --

This bug has been migrated to GNOME's GitLab instance and has been closed from further activity.

You can subscribe and participate further through the new bug through this link to our GitLab instance: https://gitlab.gnome.org/GNOME/glib/issues/545.