Priority(1) interacts with GetMouse on some Windows XP systems

The documention for Priority() warns, within the Windows section, that:
"Currently, no tests had been done to see what tasks are preempted by
setting the Matlab process to real-time priority." I have found that, on 2
of 3 Windows XP systems I have tested, all with the latest version of PTB,
but somewhat different version of Matlab), that setting Priority to 1
dramatically slows the updating of the mouse positions obtained by GetMouse
-- i.e., GetMouse runs normally but returns the same value for seconds at a
time even though the mouse is moving. If the system cursor has not been
hidden, its position is updated normally by the operating system at Priority
1; at Priority 2 the system cursor also stops moving completely.

At the end of this post I have appended a test program that illustrates this
behavior. It opens the PTB window, puts up a simple display, and then, for 5
seconds, calls GetMouse at 1 ms intervals recording the time of the call and
the x/y values returned by the call. This process is repeated twice, once at
Priority 0 and once at Priority 1. For this script to generate useful
results, it is important that the mouse be moving constantly during the 10
seconds of sampling -- it is easier to do this by making a circular motion
than by simply moving back and fourth. Once the sampling is complete, the
script prints four sets of quantiles. Two of these look at the time between
successive samples for Priority 0 and 1. These values are typically slightly
longer than 1 ms and change little depending on Priority -- as one might
expect, occasionally there are some longer delays at Priority 0 and these
show up in the 95th or 99th percentiles. The other two sets of quantiles
look at the time intervals between mouse samples that differ -- i.e. have
different x or y values -- again for Priority 0 and 1. For Priority 0 these
quantiles differ across the 3 systems (although all are running with 60 HZ
refresh rates). For one of the systems, System 1 below, the quantiles of
this distribution are essentially the same for Priority 0 and 1. For the
other two systems, illustrated by the values for System 2 below, they are
dramatically different -- in essence the mouse values were only updated once
or twice during the 5 second interval even though the mouse was moving
constantly and this was reflected in the movement of the system cursor.
(BTW, hiding the cursor does not change these results.)

System 1 -- Here the sampling works "normally" at Priority 1
Quantiles of raw inter-samples times - Priority 0
0.25%=1.2 0.50%=1.2 0.75%=1.2 0.90%=1.2 0.95%=1.3 0.99%=1.5
Quantiles of raw inter-samples times - Priority 1
0.25%=1.2 0.50%=1.2 0.75%=1.2 0.90%=1.2 0.95%=1.2 0.99%=1.3
Quantiles of inter-samples times between changed samples - Priority 0
0.25%=15.3 0.50%=16.1 0.75%=16.4 0.90%=16.5 0.95%=16.6 0.99%=17.1
Quantiles of inter-samples times between changed samples - Priority 1
0.25%=15.3 0.50%=16.4 0.75%=16.4 0.90%=16.5 0.95%=16.5 0.99%=17.0

System 2 -- Here the samples are collected normally but change VERY slowly
Quantiles of raw inter-samples times - Priority 0
0.25%=1.2 0.50%=1.2 0.75%=1.2 0.90%=1.2 0.95%=1.3 0.99%=1.9
Quantiles of raw inter-samples times - Priority 1
0.25%=1.2 0.50%=1.2 0.75%=1.2 0.90%=1.2 0.95%=1.2 0.99%=1.2
Quantiles of inter-samples times between changed samples - Priority 0
0.25%=9.4 0.50%=9.5 0.75%=9.9 0.90%=10.6 0.95%=10.7 0.99%=12.8
Quantiles of inter-samples times between changed samples - Priority 1
0.25%=5046.6 0.50%=5046.6 0.75%=5046.6 0.90%=5046.6 0.95%=5046.6 0.99%=5046.6

Given that what is going on here happens within the guts of Screen and
depends on how Screen interacts with the operating system, I'm not sure if
anything can be done about this issue other than to let others know that
this happens.

The code for my test program is appended below.

Ted Wright

function testMousePriority(whichScreen)

AssertOpenGL;
fprintf('Test mouse sampling function and estimate sampling time\n');
fprintf('To run, hit enter and then begin moving the mouse quickly in a
circular motion\n');
fprintf('The goal is to keep the mouse constantly in motion\n');
pause;

gdp.oldVisualDebugLevel = Screen('Preference', 'VisualDebugLevel', 3);
gdp.oldSupressAllWarnings = Screen('Preference', 'SuppressAllWarnings', 1);

[gdp.wp, gdp.windowRect] = Screen('OpenWindow',whichScreen,0,[],32,2);
fprintf('Flip interval = %.1f\n', Screen('GetFlipInterval', gdp.wp)*1000);


smpls1 = NaN(5000, 6);
smpls2 = smpls1;

tpRect = [100 100 120 120; 400 400 420 420; 800 800 820 820]';
Screen('FrameRect', gdp.wp, [255 255 255], tpRect, 2);
Screen('Flip', gdp.wp);

Priority(0); % make sure prioity is zero
strtTime = GetSecs;
for i = 1:5000
curTime = WaitSecs(0.001);
[x,y,buttons] = GetMouse(whichScreen);
smpls1(i, :) = [x y curTime - strtTime buttons];
end

Priority(1); % Rerun with priority 1
% HideCursor;
strtTime = GetSecs;
for i = 1:5000
curTime = WaitSecs(0.001);
[x,y,buttons] = GetMouse(whichScreen);
smpls2(i, :) = [x y curTime - strtTime buttons];
end
% ShowCursor;
Priority(0);

% Shutdown displays
Screen('Preference', 'VisualDebugLevel', gdp.oldVisualDebugLevel);
Screen('Preference', 'SuppressAllWarnings', gdp.oldSupressAllWarnings);
ShowCursor;
Screen('CloseAll');

stimes1 = diff(smpls1(:, 3));
whch = diff(smpls1(:,1)) | diff(smpls1(:,2)); % Find mouse moved samples
dstimes1 = diff(smpls1(whch, 3));

stimes2 = diff(smpls2(:, 3));
whch = diff(smpls2(:,1)) | diff(smpls2(:,2)); % Find mouse moved samples
dstimes2 = diff(smpls2(whch, 3));

% Plot intersample times of samples with different x/y values
mint = min([dstimes1; dstimes2]); maxt = max([dstimes1; dstimes2]);
subplot(2,1,1)
hist(dstimes1); % Plot intersample times
xlim([mint maxt]);
title('Distribution of mouse intersample times - Priority 0');

subplot(2,1,2)
hist(dstimes2); % Plot intersample times
xlim([mint maxt]);
title('Distribution of mouse intersample times - Priority 1');

% Print selected quantiles for both the raw intersamples times
qpts = [.25 .5 .75 .90 .95 .99];
fprintf('Quantiles of raw inter-samples times - Priority 0\n');
q = quantile(stimes1, qpts);
fprintf('%.2f%%=%.1f ', [qpts; q*1000]);
fprintf('\n');
fprintf('Quantiles of raw inter-samples times - Priority 1\n');
q = quantile(stimes2, qpts);
fprintf('%.2f%%=%.1f ', [qpts; q*1000]);
fprintf('\n');

% and for the changed samples
fprintf('Quantiles of inter-samples times between changed samples - Priority
0\n');
q = quantile(dstimes1, qpts);
fprintf('%.2f%%=%.1f ', [qpts; q*1000]);
fprintf('\n');
fprintf('Quantiles of inter-samples times between changed samples - Priority
1\n');
q = quantile(dstimes2, qpts);
fprintf('%.2f%%=%.1f ', [qpts; q*1000]);
fprintf('\n');
Is that machine where it works a dual-core/multi-core machine, vs. single processor machines where it fails? Matlab/Octave can only hog 1 cpu core from within your Matlab code, so other less loaded cores may be able to handle system tasks like mouse queries.

The main rule is to not overload the system. Never do something like:

while conditionNotSatisfied; end;

Instead add a voluntary wait to your polling loops, so the cpu gets released to other processes for at least a few milliseconds when you can spare them, so crucial system processes like e.g., keyboard and mouse handling, disk i/o etc. can run.

Like this if you don't care if you lose the cpu for 1 msec or maybe a few more msecs (imprecise but especially "friendly" wait):

while conditionNotSatisfied
WaitSecs('YieldSecs', 0.001);
end

Or the classic WaitSecs(0.004); which tries to wait exactly the specified amount of time. On MS-Windows, anything below <= 3 msecs will turn into a busy-wait that hogs the cpu again and can cause priority problems. On some systems larger WaitSecs times can also cause cpu hogging, as the 3 msecs threshold is dynamically adjusted to up to 25 msecs, depending on how bad your specific system handles scheduling. MS-Windows has by far the worst timing behaviour of any os i know, which is why it is normally not used for timing critical applications. Ptb implements a number of hacks and dirty tricks to kind'a make the elephant dance, but many of them can have side effects. Which is why i don't recommend Windows for anything that requires good timing.

If you add such a WaitSecs to your GetMouse loop, it may work as expected. Polling faster than 125 Hz doesn't make sense anyway, as mouse devices don't update faster than that on os/x or windows. On Linux it is configurable down to 1 msec if the mouse hardware supports it. Also some systems only poll/update the mouse cursor position at max. video refresh rate, as that is sufficient for getting smooth mouse cursor movements, i.e., as low as 60 Hz. HIDIntervalTest.m lets you test your USB/HID keyboard/mouse wrt. sampling rate on an os.

-mario

--- In psychtoolbox@yahoogroups.com, "Charles E. (Ted) Wright" <cewright@...> wrote:
>
> The documention for Priority() warns, within the Windows section, that:
> "Currently, no tests had been done to see what tasks are preempted by
> setting the Matlab process to real-time priority." I have found that, on 2
> of 3 Windows XP systems I have tested, all with the latest version of PTB,
> but somewhat different version of Matlab), that setting Priority to 1
> dramatically slows the updating of the mouse positions obtained by GetMouse
> -- i.e., GetMouse runs normally but returns the same value for seconds at a
> time even though the mouse is moving. If the system cursor has not been
> hidden, its position is updated normally by the operating system at Priority
> 1; at Priority 2 the system cursor also stops moving completely.
>
> At the end of this post I have appended a test program that illustrates this
> behavior. It opens the PTB window, puts up a simple display, and then, for 5
> seconds, calls GetMouse at 1 ms intervals recording the time of the call and
> the x/y values returned by the call. This process is repeated twice, once at
> Priority 0 and once at Priority 1. For this script to generate useful
> results, it is important that the mouse be moving constantly during the 10
> seconds of sampling -- it is easier to do this by making a circular motion
> than by simply moving back and fourth. Once the sampling is complete, the
> script prints four sets of quantiles. Two of these look at the time between
> successive samples for Priority 0 and 1. These values are typically slightly
> longer than 1 ms and change little depending on Priority -- as one might
> expect, occasionally there are some longer delays at Priority 0 and these
> show up in the 95th or 99th percentiles. The other two sets of quantiles
> look at the time intervals between mouse samples that differ -- i.e. have
> different x or y values -- again for Priority 0 and 1. For Priority 0 these
> quantiles differ across the 3 systems (although all are running with 60 HZ
> refresh rates). For one of the systems, System 1 below, the quantiles of
> this distribution are essentially the same for Priority 0 and 1. For the
> other two systems, illustrated by the values for System 2 below, they are
> dramatically different -- in essence the mouse values were only updated once
> or twice during the 5 second interval even though the mouse was moving
> constantly and this was reflected in the movement of the system cursor.
> (BTW, hiding the cursor does not change these results.)
>
> System 1 -- Here the sampling works "normally" at Priority 1
> Quantiles of raw inter-samples times - Priority 0
> 0.25%=1.2 0.50%=1.2 0.75%=1.2 0.90%=1.2 0.95%=1.3 0.99%=1.5
> Quantiles of raw inter-samples times - Priority 1
> 0.25%=1.2 0.50%=1.2 0.75%=1.2 0.90%=1.2 0.95%=1.2 0.99%=1.3
> Quantiles of inter-samples times between changed samples - Priority 0
> 0.25%=15.3 0.50%=16.1 0.75%=16.4 0.90%=16.5 0.95%=16.6 0.99%=17.1
> Quantiles of inter-samples times between changed samples - Priority 1
> 0.25%=15.3 0.50%=16.4 0.75%=16.4 0.90%=16.5 0.95%=16.5 0.99%=17.0
>
> System 2 -- Here the samples are collected normally but change VERY slowly
> Quantiles of raw inter-samples times - Priority 0
> 0.25%=1.2 0.50%=1.2 0.75%=1.2 0.90%=1.2 0.95%=1.3 0.99%=1.9
> Quantiles of raw inter-samples times - Priority 1
> 0.25%=1.2 0.50%=1.2 0.75%=1.2 0.90%=1.2 0.95%=1.2 0.99%=1.2
> Quantiles of inter-samples times between changed samples - Priority 0
> 0.25%=9.4 0.50%=9.5 0.75%=9.9 0.90%=10.6 0.95%=10.7 0.99%=12.8
> Quantiles of inter-samples times between changed samples - Priority 1
> 0.25%=5046.6 0.50%=5046.6 0.75%=5046.6 0.90%=5046.6 0.95%=5046.6 0.99%=5046.6
>
> Given that what is going on here happens within the guts of Screen and
> depends on how Screen interacts with the operating system, I'm not sure if
> anything can be done about this issue other than to let others know that
> this happens.
>
> The code for my test program is appended below.
>
> Ted Wright
>
> function testMousePriority(whichScreen)
>
> AssertOpenGL;
> fprintf('Test mouse sampling function and estimate sampling time\n');
> fprintf('To run, hit enter and then begin moving the mouse quickly in a
> circular motion\n');
> fprintf('The goal is to keep the mouse constantly in motion\n');
> pause;
>
> gdp.oldVisualDebugLevel = Screen('Preference', 'VisualDebugLevel', 3);
> gdp.oldSupressAllWarnings = Screen('Preference', 'SuppressAllWarnings', 1);
>
> [gdp.wp, gdp.windowRect] = Screen('OpenWindow',whichScreen,0,[],32,2);
> fprintf('Flip interval = %.1f\n', Screen('GetFlipInterval', gdp.wp)*1000);
>
>
> smpls1 = NaN(5000, 6);
> smpls2 = smpls1;
>
> tpRect = [100 100 120 120; 400 400 420 420; 800 800 820 820]';
> Screen('FrameRect', gdp.wp, [255 255 255], tpRect, 2);
> Screen('Flip', gdp.wp);
>
> Priority(0); % make sure prioity is zero
> strtTime = GetSecs;
> for i = 1:5000
> curTime = WaitSecs(0.001);
> [x,y,buttons] = GetMouse(whichScreen);
> smpls1(i, :) = [x y curTime - strtTime buttons];
> end
>
> Priority(1); % Rerun with priority 1
> % HideCursor;
> strtTime = GetSecs;
> for i = 1:5000
> curTime = WaitSecs(0.001);
> [x,y,buttons] = GetMouse(whichScreen);
> smpls2(i, :) = [x y curTime - strtTime buttons];
> end
> % ShowCursor;
> Priority(0);
>
> % Shutdown displays
> Screen('Preference', 'VisualDebugLevel', gdp.oldVisualDebugLevel);
> Screen('Preference', 'SuppressAllWarnings', gdp.oldSupressAllWarnings);
> ShowCursor;
> Screen('CloseAll');
>
> stimes1 = diff(smpls1(:, 3));
> whch = diff(smpls1(:,1)) | diff(smpls1(:,2)); % Find mouse moved samples
> dstimes1 = diff(smpls1(whch, 3));
>
> stimes2 = diff(smpls2(:, 3));
> whch = diff(smpls2(:,1)) | diff(smpls2(:,2)); % Find mouse moved samples
> dstimes2 = diff(smpls2(whch, 3));
>
> % Plot intersample times of samples with different x/y values
> mint = min([dstimes1; dstimes2]); maxt = max([dstimes1; dstimes2]);
> subplot(2,1,1)
> hist(dstimes1); % Plot intersample times
> xlim([mint maxt]);
> title('Distribution of mouse intersample times - Priority 0');
>
> subplot(2,1,2)
> hist(dstimes2); % Plot intersample times
> xlim([mint maxt]);
> title('Distribution of mouse intersample times - Priority 1');
>
> % Print selected quantiles for both the raw intersamples times
> qpts = [.25 .5 .75 .90 .95 .99];
> fprintf('Quantiles of raw inter-samples times - Priority 0\n');
> q = quantile(stimes1, qpts);
> fprintf('%.2f%%=%.1f ', [qpts; q*1000]);
> fprintf('\n');
> fprintf('Quantiles of raw inter-samples times - Priority 1\n');
> q = quantile(stimes2, qpts);
> fprintf('%.2f%%=%.1f ', [qpts; q*1000]);
> fprintf('\n');
>
> % and for the changed samples
> fprintf('Quantiles of inter-samples times between changed samples - Priority
> 0\n');
> q = quantile(dstimes1, qpts);
> fprintf('%.2f%%=%.1f ', [qpts; q*1000]);
> fprintf('\n');
> fprintf('Quantiles of inter-samples times between changed samples - Priority
> 1\n');
> q = quantile(dstimes2, qpts);
> fprintf('%.2f%%=%.1f ', [qpts; q*1000]);
> fprintf('\n');
>
Mario,

The function, that I attached to the original email, did have a
WaitSecs(.001) in the sampling loops. You are correct that if I increase the
wait time to .005 it works a lot better (but not always perfectly);
increasing this to .002 did not help at all. I had originally set this to
.001 becuase I was trying to determine how often the mouse values were being
updated.

The distinction that determines whether the values get updated is not just
whether the CPU is dual- vs. single-core. Although the machine that runs the
test function without problems at an interval of .001 is dual core, so is
one of the machines where this fails.

Although I understand the basic concept of threads, I have no idea to what
extent Matlab/PTB uses multiple threads or whether they can make use of
separate processors. However, it does seem like the x/y coordinates returned
by GetMouse are only getting updated when there is a fair amount of free
time on the processor running the thread interpreting the primary loop of
the Matlab function.

Ted

On Mon, 28 Jun 2010, Mario wrote:

>
>
>
>
> Is that machine where it works a dual-core/multi-core machine, vs. single
> processor machines where it fails? Matlab/Octave can only hog 1 cpu core from
> within your Matlab code, so other less loaded cores may be able to handle system
> tasks like mouse queries.
>
> The main rule is to not overload the system. Never do something like:
>
> while conditionNotSatisfied; end;
>
> Instead add a voluntary wait to your polling loops, so the cpu gets released to
> other processes for at least a few milliseconds when you can spare them, so
> crucial system processes like e.g., keyboard and mouse handling, disk i/o etc.
> can run.
>
> Like this if you don't care if you lose the cpu for 1 msec or maybe a few more
> msecs (imprecise but especially "friendly" wait):
>
> while conditionNotSatisfied
> WaitSecs('YieldSecs', 0.001);
> end
>
> Or the classic WaitSecs(0.004); which tries to wait exactly the specified amount
> of time. On MS-Windows, anything below <= 3 msecs will turn into a busy-wait that
> hogs the cpu again and can cause priority problems. On some systems larger
> WaitSecs times can also cause cpu hogging, as the 3 msecs threshold is
> dynamically adjusted to up to 25 msecs, depending on how bad your specific system
> handles scheduling. MS-Windows has by far the worst timing behaviour of any os i
> know, which is why it is normally not used for timing critical applications. Ptb
> implements a number of hacks and dirty tricks to kind'a make the elephant dance,
> but many of them can have side effects. Which is why i don't recommend Windows
> for anything that requires good timing.
>
> If you add such a WaitSecs to your GetMouse loop, it may work as expected.
> Polling faster than 125 Hz doesn't make sense anyway, as mouse devices don't
> update faster than that on os/x or windows. On Linux it is configurable down to 1
> msec if the mouse hardware supports it. Also some systems only poll/update the
> mouse cursor position at max. video refresh rate, as that is sufficient for
> getting smooth mouse cursor movements, i.e., as low as 60 Hz. HIDIntervalTest.m
> lets you test your USB/HID keyboard/mouse wrt. sampling rate on an os.
>
> -mario
>