Mex inplace change problems R2015b and later

조회 수: 8 (최근 30일)
James Tursa
James Tursa 2018년 2월 17일
댓글: Walter Roberson 2020년 12월 8일
This post is to alert mex programmers of a change in R2015b and later that makes inplace changes more hazardous than they used to be (as if this wasn't already tricky enough). The MATLAB changes are of course internal and unpublished, so what follows is just one brief example of a problem you might run into. Changing an input variable inplace of course violates the const rules for the input prhs[ ] array, but is often done by careful mex programmers to avoid the deep data copies that would otherwise be required, particularly when very large variables are involved.
The change: MATLAB R2015b and later now saves reference copies of workspace variables for simple assignments, rather than generating a shared data copy as it used to. The implications of this will be shown below.
The mex routine that changes the first element of the first input argument inplace:
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
if( nrhs && mxIsDouble(prhs[0]) && mxGetNumberOfElements(prhs[0]) > 0 && !mxIsSparse(prhs[0]) ) {
*mxGetPr(prhs[0]) = 99.0;
}
}
R2015a:
>> x = 1:5
x =
1 2 3 4 5
>> inplace_change(x)
>> x
x =
99 2 3 4 5
>> x = 1:5
x =
1 2 3 4 5
R2015b:
>> x = 1:5
x =
1 2 3 4 5
>> inplace_change(x)
>> x
x =
99 2 3 4 5
>> x = 1:5
x =
99 2 3 4 5
WHAT THE ...??? WHAT HAPPENED???
As it turns out, in R2015a and earlier MATLAB apparently generates the 1:5 array on the fly as a new variable and assigns it as expected. The inplace x(1) change works as expected, and because we didn't do anything with x that would cause any data sharing to take place, there are no nasty side effects. When the 1:5 assignment happens again, everything works again as expected.
But in R2015b and later, the assignment of 1:5 causes MATLAB to save a reference copy of the 1:5 variable in the background. Unknown to you, x is in fact sharing with another variable (a background variable that you can't see directly, but you can see it if you hack into it with a mex routine and examine the reference count field). So when you do the inplace change of the x(1), you also inadvertently changed the 1st element of that background reference copy as well. Now for the really nasty part. When you subsequently do another 1:5 assignment, MATLAB apparently remembers that pattern and remembers that it already has a copy of that stored in memory in the background, so it simply uses that for the assignment. Well, now you are screwed because that background reference copy of 1:5 was inadvertently changed inplace so that the 1st element is 99 instead of 1. This really sucks, I know! But that is what you will have to deal with in R2015b and later.
The fix? Well, I know you inplace mex programmers are careful to begin with. But now you are going to have to be really careful how you create & use those variables that are going to be changed inplace. You might have to avoid using any expression (e.g., involving literal values etc) that might allow the parser to outsmart you and save a reference copy. The only advice I can give you is inside your mex routine, use an mxArray header hack to examine the RefCount field to see if you are in trouble before you make the inplace change. Maybe throw an error if the RefCount is anything other than 0.
As stated before, I don't know the rules for this, and actually just discovered this effect recently. Whether this only affects literal assignments as above or other types of expressions I don't know. I haven't really investigated this much yet, but thought I would put this info out there right away.
The reference copy thing is not just for literal assignments btw. For example:
R2015a:
x = 1:5;
y = x; % <-- y will be a shared data copy of x
R2015b:
x = 1:5;
y = x; % <-- y will be a reference copy of x
  댓글 수: 5
James Tursa
James Tursa 2020년 12월 8일
편집: James Tursa 2020년 12월 8일
This:
% file inplace_change_test.m
x = 0 + 1:5
inplace_change(x)
x
x = 0 + 1:5
y = 0 + 1:5
produces this output:
>> mex inplace_change.c -R2018a
Building with 'Microsoft Visual C++ 2015 (C)'.
MEX completed successfully.
>> version
ans =
'9.8.0.1323502 (R2020a)'
>> inplace_change_test
x =
1 2 3 4 5
x =
99 2 3 4 5
x =
99 2 3 4 5
y =
1 2 3 4 5
So the parser did not use the same caching for y as it did for x in this case, but since parser rules are subject to change without notice I wouldn't necessarily count on this.
Walter Roberson
Walter Roberson 2020년 12월 8일
I edited my post between, as perhaps it matters that a different variable was being assigned to.

댓글을 달려면 로그인하십시오.

답변 (3개)

RAB
RAB 2018년 9월 3일
To add to the confusion, in R2015b try running:
>> x = 1:5
x =
1 2 3 4 5
>> inplace_change(x)
>> x
x =
99 2 3 4 5
>> x = 1:5
x =
99 2 3 4 5
>> x = 1: 5 % Note the extra space to change the command
x =
1 2 3 4 5
The caching mechanism in the MATLAB execution engine apparently considers exact syntax and not the "meaning" of the command.
  댓글 수: 1
Walter Roberson
Walter Roberson 2020년 12월 8일
If you have multiple commands on one line, then is it exact match for the entire line that is important, or is it exact match to the point that the statement ends?
e.g. if you had put a semicolon after the assignment would parsing for match stop there?

댓글을 달려면 로그인하십시오.


RAB
RAB 2018년 9월 4일
The command itself does not even need to be changed, just add a comment or just the "%" ...
>> x = 1:5
x =
1 2 3 4 5
>> inplace_change(x)
>> x
x =
99 2 3 4 5
>> x = 1:5
x =
99 2 3 4 5
>> x = 1:5%
x =
1 2 3 4 5
  댓글 수: 2
James Tursa
James Tursa 2018년 9월 4일
But again, using the same syntax again has the same problem. E.g.,
>> x = 1:5
x =
1 2 3 4 5
>> inplace_change(x)
>> x
x =
99 2 3 4 5
>> x = 1:5
x =
99 2 3 4 5 <-- WRONG!!!
>> x = 1:5%
x =
1 2 3 4 5
>> inplace_change(x)
>> x
x =
99 2 3 4 5
>> x = 1:5%
x =
99 2 3 4 5 <-- WRONG AGAIN!!!
RAB
RAB 2018년 9월 5일
Indeed, my example served to illustrate that the caching works on the exact syntax of the command.

댓글을 달려면 로그인하십시오.


James Tursa
James Tursa 2018년 9월 4일
편집: James Tursa 2018년 9월 4일
One potential workaround is to change an element of the variable after the assignment to force a deep copy. E.g.,
>> x = 1:5
x =
1 2 3 4 5
>> x(1) = x(1); % <-- Force a deep copy, x is no longer a reference copy of the background variable
>> inplace_change(x) % <-- Doesn't alter that background copy of 1:5
>> x
x =
99 2 3 4 5
>> x = 1:5
x =
1 2 3 4 5 <-- Now we get the correct result
Since the background reference copy behavior apparently only occurs for "small" variables this deep copy will not be too costly. E.g., for double class variables background reference copies will be created for variables of size 256 or smaller, but will not be created for variables of size 257 or greater. E.g.,
>> x = 1:256;
>> inplace_change(x)
>> x = 1:256;
>> x(1)
ans =
99 <-- WRONG!!!
>> clear all
>> x = 1:257;
>> inplace_change(x)
>> x = 1:257;
>> x(1)
ans =
1 <-- CORRECT!
  댓글 수: 1
RAB
RAB 2018년 9월 5일
Ok, interesting and makes sense that background copies are not kept for large variables.
I did not look into the caching/background copy behavior for other data types etc. Obviously the goal is to come up with consistent behavior no matter what the size of the variable is.

댓글을 달려면 로그인하십시오.

카테고리

Help CenterFile Exchange에서 Logical에 대해 자세히 알아보기

태그

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by