58b4e07369
QTimer::singleShot is optimized for zero timeouts when using the API taking a string method name. This optimization was not used for the API taking a PMF or functor. This patch adds it, making the various API calls behave similarly from a performance point of view. The approach taken here requires a QObject context object. If none is available, e.g. a nullptr was passed explicitly, or the QTimer::singleShot(O, Functor) API was used, the optimization could not easily be applied. This is not only bad from a performance POV, but also poses as a potential source for heisenbugs: Using the different API versions of QTimer::singleShot would use different code paths internally, which then would not ensure the expected slot call order. This problem actually existed already when mixing the string-based slot syntax with PMF/functors in the QTimer::singleShot API. This patch overcomes this hurdle and fixes all of the above: When we encounter a 0ms single shot timer, and no QObject context object is available, we fall back to the main thread, or create a temporary QObject for any other thread. The updated and extended benchmark shows that this is still a significant performance improvement over using a timer: ********* Start testing of qtimer_vs_qmetaobject ********* Config: Using QtTest library 5.14.0, Qt 5.14.0 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 8.2.1 20181127) PASS : qtimer_vs_qmetaobject::initTestCase() PASS : qtimer_vs_qmetaobject::bench(singleShot_slot) RESULT : qtimer_vs_qmetaobject::bench():"singleShot_slot": 7.48 msecs per iteration (total: 748, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(singleShot_pmf) RESULT : qtimer_vs_qmetaobject::bench():"singleShot_pmf": 7.20 msecs per iteration (total: 720, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(singleShot_functor) RESULT : qtimer_vs_qmetaobject::bench():"singleShot_functor": 6.79 msecs per iteration (total: 679, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(singleShot_functor_noctx) RESULT : qtimer_vs_qmetaobject::bench():"singleShot_functor_noctx": 6.92 msecs per iteration (total: 693, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(invokeMethod_string) RESULT : qtimer_vs_qmetaobject::bench():"invokeMethod_string": 7.34 msecs per iteration (total: 735, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(invokeMethod_pmf) RESULT : qtimer_vs_qmetaobject::bench():"invokeMethod_pmf": 6.90 msecs per iteration (total: 690, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(invokeMethod_functor) RESULT : qtimer_vs_qmetaobject::bench():"invokeMethod_functor": 6.62 msecs per iteration (total: 662, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(singleShot_slot) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"singleShot_slot": 7.45 msecs per iteration (total: 745, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(singleShot_pmf) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"singleShot_pmf": 7.46 msecs per iteration (total: 747, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(singleShot_functor) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"singleShot_functor": 6.70 msecs per iteration (total: 671, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(singleShot_functor_noctx) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"singleShot_functor_noctx": 13.75 msecs per iteration (total: 1,376, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(invokeMethod_string) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"invokeMethod_string": 7.05 msecs per iteration (total: 706, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(invokeMethod_pmf) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"invokeMethod_pmf": 6.70 msecs per iteration (total: 670, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(invokeMethod_functor) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"invokeMethod_functor": 6.58 msecs per iteration (total: 658, iterations: 100) PASS : qtimer_vs_qmetaobject::cleanupTestCase() Totals: 16 passed, 0 failed, 0 skipped, 0 blacklisted, 20977ms ********* Finished testing of qtimer_vs_qmetaobject ********* Without the change to qtimer.cpp, the results are: ********* Start testing of qtimer_vs_qmetaobject ********* Config: Using QtTest library 5.14.0, Qt 5.14.0 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 8.2.1 20181127) PASS : qtimer_vs_qmetaobject::initTestCase() PASS : qtimer_vs_qmetaobject::bench(singleShot_slot) RESULT : qtimer_vs_qmetaobject::bench():"singleShot_slot": 7.45 msecs per iteration (total: 745, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(singleShot_pmf) RESULT : qtimer_vs_qmetaobject::bench():"singleShot_pmf": 112.84 msecs per iteration (total: 11,285, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(singleShot_functor) RESULT : qtimer_vs_qmetaobject::bench():"singleShot_functor": 115.62 msecs per iteration (total: 11,563, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(singleShot_functor_noctx) RESULT : qtimer_vs_qmetaobject::bench():"singleShot_functor_noctx": 110.81 msecs per iteration (total: 11,082, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(invokeMethod_string) RESULT : qtimer_vs_qmetaobject::bench():"invokeMethod_string": 7.04 msecs per iteration (total: 704, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(invokeMethod_pmf) RESULT : qtimer_vs_qmetaobject::bench():"invokeMethod_pmf": 6.62 msecs per iteration (total: 662, iterations: 100) PASS : qtimer_vs_qmetaobject::bench(invokeMethod_functor) RESULT : qtimer_vs_qmetaobject::bench():"invokeMethod_functor": 6.62 msecs per iteration (total: 662, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(singleShot_slot) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"singleShot_slot": 7.45 msecs per iteration (total: 746, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(singleShot_pmf) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"singleShot_pmf": 118.42 msecs per iteration (total: 11,842, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(singleShot_functor) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"singleShot_functor": 119.35 msecs per iteration (total: 11,936, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(singleShot_functor_noctx) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"singleShot_functor_noctx": 130.96 msecs per iteration (total: 13,096, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(invokeMethod_string) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"invokeMethod_string": 8.08 msecs per iteration (total: 808, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(invokeMethod_pmf) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"invokeMethod_pmf": 6.79 msecs per iteration (total: 680, iterations: 100) PASS : qtimer_vs_qmetaobject::benchBackgroundThread(invokeMethod_functor) RESULT : qtimer_vs_qmetaobject::benchBackgroundThread():"invokeMethod_functor": 7.49 msecs per iteration (total: 749, iterations: 100) PASS : qtimer_vs_qmetaobject::cleanupTestCase() Totals: 16 passed, 0 failed, 0 skipped, 0 blacklisted, 153995ms ********* Finished testing of qtimer_vs_qmetaobject ********* Additionally, this patch adds a unit test to verify that the slot call order for 0ms single shot timers is followed while mixing the various API versions. It fails without this patch but passes now. Finally, another test is added to verify that using QTimer::singleShot before a QCoreApplication was constructed is still working properly. Change-Id: I0d6211554b6198cb3e527be9ec3adc572b1b54ee Reviewed-by: Thiago Macieira <thiago.macieira@intel.com> |
||
---|---|---|
.. | ||
corelib | ||
dbus | ||
gui | ||
network | ||
opengl | ||
plugins/imageformats/jpeg | ||
sql | ||
testlib | ||
widgets | ||
benchmarks.pro | ||
README | ||
trusted-benchmarks.pri |
The most reliable way of running benchmarks is to do it in an otherwise idle system. On a busy system, the results will vary according to the other tasks demanding attention in the system. We have managed to obtain quite reliable results by doing the following on Linux (and you need root): - switching the scheduler to a Real-Time mode - setting the processor affinity to one single processor - disabling the other thread of the same core This should work rather well for CPU-intensive tasks. A task that is in Real- Time mode will simply not be preempted by the OS. But if you make OS syscalls, especially I/O ones, your task will be de-scheduled. Note that this includes page faults, so if you can, make sure your benchmark's warmup code paths touch most of the data. To do this you need a tool called schedtool (package schedtool), from http://freequaos.host.sk/schedtool/ From this point on, we are using CPU0 for all tasks: If you have a Hyperthreaded multi-core processor (Core-i5 and Core-i7), you have to disable the other thread of the same core as CPU0. To discover which one it is: $ cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list This will print something like 0,4, meaning that CPUs 0 and 4 are sibling threads on the same core. So we'll turn CPU 4 off: (as root) # echo 0 > /sys/devices/system/cpu/cpu4/online To turn it back on, echo 1 into the same file. To run a task on CPU 0 exclusively, using FIFO RT priority 10, you run the following: (as root) # schedtool -F -p 10 -a 1 -e ./taskname For example: # schedtool -F -p 10 -a 1 -e ./tst_bench_qstring -tickcounter Warning: if your task livelocks or takes far too long to complete, your system may be unusable for a long time, especially if you don't have other cores to run stuff on. To prevent that, run it before schedtool and time it. You can also limit the CPU time that the task is allowed to take. Run in the same shell as you'll run schedtool: $ ulimit -s 300 To limit to 300 seconds (5 minutes) If your task runs away, it will get a SIGXCPU after consuming 5 minutes of CPU time (5 minutes running at 100%). If your app is multithreaded, you may want to give it more CPUs, like CPU0 and CPU1 with -a 3 (it's a bitmask). For best results, you should disable ALL other cores and threads of the same processor. The new Core-i7 have one processor with 4 cores, each core can run 2 threads; the older Mac Pros have two processors with 4 cores each. So on those Mac Pros, you'd disable cores 1, 2 and 3, while on the Core-i7, you'll need to disable all other CPUs. However, disabling just the sibling thread seems to produce very reliable results for me already, with variance often below 0.5% (even though there are some measurable spikes). Other things to try: Running the benchmark with highest priority, i.e. "sudo nice -19" usually produces stable results on some machines. If the benchmark also involves displaying something on the screen (on X11), running it with "-sync" is a must. Though, in that case the "real" cost is not correct, but it is useful to discover regressions. Also; not many people know about ionice (1) ionice - get/set program io scheduling class and priority