Matlab的copy-on-write机制、mex参数传递机制及其他

原文:http://i.mtime.com/1143009/blog/5824796/

Matlab真是让人又爱又恨,诸多强大的工具包以及矩阵(向量)运算的便捷与高效让人欲罢不能(那种一行代码抵C++等效的10+行循环代码的感觉真是爽到不行)。不过最近做实验用Matlab让我吃了不少苦头,主要就是因为对它的copy-on-write机制以及mex文件的参数传递机制不了解。另外感觉这方面内容虽然一般不怎么涉及,但是对于理解Matlab并且充分发挥出它对矩阵运算的高效性而又不陷入无谓且难以察觉的臭虫堆(切身体会啊!)十分重要,又加之中文的关于这方面的材料十分稀缺(我几乎就没见到……),所以在这里稍作总结,便于今后自己查阅也方便有需要的人。

      鉴于Matlab还一直在更新,本文的结论仅针对如下系统而言:

          Matlab: Version 7.7.0.471 (R2008b)

          Windows XP: Home Edition 2002, SP3 (OEM版,不是盗版哦

 

      1. 先解释下2个关键词:

          a. copy-on-write:我们晓得变量在内存里存储都有一个地址,而有的时候我们需要把一个变量A的值拷贝一份产生一个新的变量B。所谓copy-on-write说的就是制造这么一个假象,让你觉得好像已经拷贝过了,但是其实只是让那个新变量也指向原来的内存地址(因为这样对于仅仅读取变量值而言是等效的),只有当那个值必须发生改变的时候(或者说被写入的时候)才赶紧把原先的内容拷贝出来,存到一个新的地方(如果改写的是A,就存给B;如果改写的是B,就存给A)。

          说白了就是偷懒+说谎——直到要被戳穿了不得已为止。不过这样做当然是有好处的,因为拷贝内存是有开销的(即使是线性开销),因此能省则省呗。特别对于Matlab这样的动不动开个庞大矩阵做运算的语言来讲,copy-on-write对于性能提升至关重要。

          b. mex传参:mex是"Matlab Executable"的缩写,是个非常强大的机制。有了它以后,Matlab难以体现其优势的代码(比如多重循环)就可以转而用C(C++貌似也可以)或者FORTRAN来实现,通过预先编译产生动态链接库文件(就是dll)为Matlab进程调用,并且调用接口与直接调用Matlab函数几乎完全一样(好吧,只是"几乎"而已,不然我就不会碰到那些个bug了……)。

          具体的mex介绍不是本文重点,就此略过。这里所谓的"mex传参"就是指在Matlab里调用mex函数的时候,不同的传参方式对于mex函数实际运行的影响。

 

      2. Matlab的变量类型有蛮多种,为了后面方便讨论,我们这里采用以下划分标准:

          a. Scalar类型:就是单个的数值(实数或者虚数都可以,总之是个复数吧),也可以理解成1*1的矩阵;

          b. Array类型:包括最简单的向量(1*n或者n*1),矩阵(m*n)以及多维向量(n1*n2*...*nk),其中的元素是Scalar类型;

          c. Structure类型:由一些Field组成(A.f1, A.f2, ...),其中每个Field值可以是Scalar类型、Array类型或者Structure类型;

          d. 其他类型:所有不符合上述定义的其他合法类型(比如String类型,Cell类型等等)。

      备注:我们这样分类仅仅为了后面讨论方便,因此StructureArray都是狭义的,后文所得结论皆针对此狭义定义而言(倒也不是说对广义的就不成立,是我懒得做进一步验证了……)

 

      3. 有两种不同的情形都可能导致copy-on-write,即赋值函数传参,这里我们再做点小约束(假设A和B是两个Matlab变量):

          a. 赋值:就是单纯的A=B,先不考虑对A或者B做任何附加操作,比如下标索引:A(1:5)=B(1:5)、算术运算:A=B.^2之类的;

          b. 传参:就是单纯的f(A)(当然f(A, B, ...)对于我们讨论A也无妨啦)

      备注:其实对于很多附加操作,结论很容易推出来,不过,呃,为了严谨记么,就先不考虑它们了。

 

      OK了,上结论!!!

 

      4. copy-on-write:

          a. 对于赋值情形:Scalar变量没有copy-on-write;Array变量有;Structure变量的所有Field都是copy-on-write(不管该Field是不是Scalar类型变量),但是各管各的,互不影响。

          举例:A = 1; B = A; (B和A不共享数据,分别存在各自的内存地址里)

 

                  A = [1 2]; B = A; (B和A共享数据,没有发生实际拷贝)

                  B(1) = 0; (copy-on-write!A的内存布局不变,B的数据存到新的地方)

 

                  A.f1 = 1; A.f2 = [1 1]; B = A; (B和A完全共享数据,包括A.f1和B.f1)

                  B.f1 = 2; (copy-on-write!A的内存布局不变,B.f2也不变,B.f1的数据存到新的地方)

          备注:

          1. 对于多维向量,因为实际存储在内存中的时候和列向量并没有区别,都是连续存储的,所以发生copy-on-write的时候是整体拷贝,并不会因为只修改了其中某一维而继续共享其他维的数据。

          2. 这里(点我)举报了Matlab的相关臭虫一枚,感兴趣的可以看下。大意是说,在函数里初始化一个向量,然后修改其中一个元素值,你说会不会copy-on-write咧?(答案是不一定!如果是这么初始化的:data=[0 0 0 0],那么修改data(1)=5之后data的数据整个copy了一份挪了窝;而如果是这样呢:data(1:4)=0或者data=zeros(1,4)?那么修改data(1)=5之后数据还在原地,没有拷贝!

          b. 对于传参情形:比较清楚,统统都是copy-on-write!

          备注:

          1. 网上看到有的材料说Matlab可能在调用函数前进行pre-parse,先判断一下参数在函数中会不会被修改,如果是read-only的函数,就copy-on-write;如果有修改的可能,就直接拷贝一份传进去。但是我实验下来并不是这样!传参的时候全部都是copy-on-write,也就是说一旦参数在函数里被修改,那么马上拷贝一份新的出来,函数外的变量不会受到影响(具体的实验方法后面会提到,也许对于不同版本的Matlab结论不同,列位看官也可以自己试一下)。

          2. 这种机制并不完全尽如人意,比如这里(点我)就给Matlab的开发人员提了一个很好的问题,比如我现在有这么一个函数:

              function data = transform(data)

                  data = data + 1;

              end

          其实我只需要这个函数帮我把data加1就完了,但是实际调用"A = transform(A)"的结果是:会发生2次A的拷贝!第1次是在data+1完了之后赋值更新data的时候(因为copy-on-write,传参的时候其实并没有拷贝),第2次是函数结束的时候把数据返回给A。copy-on-write把拷贝尽可能延迟当然很好,可是在这个情形下,我们其实更加希望——索性不要拷贝!呃,这个说起来其实也不是copy-on-write的错啦……

 

      5. mex传参:这里先稍微介绍一下mex传参的机制。其实Matlab里的所有Scalar变量和Array变量在内存里的存储方式都差不多,就是从某一个首地址P开始连续存储(对于矩阵或是多维数组而言,就是以前面的维数优先展开成向量存储,比如一个2*2的矩阵A存起来就是A(1,1)、A(2,1)、A(1,2)、A(2,2)依次排开)。所以到了mex函数里,其实就只看到数组形式:想索引A(1,2)?就用a(2)吧(假设a是A对应的数组首地址)。再加上Matlab函数传参的copy-on-write机制(注意传参发生时,还在Matlab中),我们其实在mex函数里就可以直接取到参数的实际内存地址(还没有发生任何拷贝哦~),就像是在传引用

      因此针对上面的备注2里提到的问题,我们就可以利用mex巧妙地加以解决。怎么做呢?写一个mex文件,没有返回值(调用的时候直接写transform(A)),实现"data = data + 1"的等效功能,然后利用copy-on-write!它保证把参数数据的真实内存地址信息递交给mex函数(用mxGetPr可以取到首地址,用mxGetDimensions可以取到维数信息,其实也就是数据长度),之后就可以直接对数据进行操作啦,完事之后直接返回,一次拷贝都没有!不必担心copy-on-write在你改写数据的时候出来捣乱,因为你的改写发生在mex函数里,动态加载运行以后Matlab移交控制权,对其无能为力,想要拷贝也无法了!

      很赞是不是?知道了是很赞,可是假设你不晓得这其中有这些小秘密咧(比如昨天的我)……就会莫名奇妙地发现调用函数以后——谁动了我的参数!以后可得注意了。

      硬币的另一面是:有时候我们真的需要在函数里修改参数值,但是又不希望函数外的变量受到影响,怎么办?(如果不使用mex,这个问题根本不存在,因为Matlab的copy-on-write保证了这一点)

      这里提供一个办法,或曰小伎俩,就是在传参的时候不要老老实实传,像这样:f(A),而是加一个全程下标索引,像这样:f(A(1:end))

      为什么这样可行呢?

      因为当Matlab知道你要传的参数仅仅是真实数据的其中一部分的时候(我们只不过用"1:end"就能轻易地骗倒它),原先的"连续存储"就失效了(多维向量的连续下标索引出来的数据在一维展开的情形下一般都是散落在内存各处的),但是mex函数接受的参数必须是数组形式——换言之,在内存里是连续存储的,所以Matlab只好把这些数据拷贝出来重新在内存里连续排好,再传给mex函数——也就是说,我们强制Matlab在mex传参的时候发生一次参数拷贝。然后我们就能在mex函数里放心地对参数为所欲为了~

      不过这样还带来了一个副产品——有时候我们希望在mex传参的时候把实际是整型的变量用int32转换之后再传(Matlab默认的类型都是double)。在这个转换的过程中,因为double和int类型在内存中的表示是截然不同的,因此必然导致原来的参数数据不能直接传给mex,所以这种时候也会发生"拷贝"。

      总结一下就是:Matlab里的mex传参在直接引用参数的情况下(比如f(A))就相当于传引用,mex函数直接对参数进行读写操作,不发生参数拷贝;而在原参数的内存存储形式不能直接以数组形式传递给mex函数的时候,就会发生参数拷贝,从而mex函数对参数所作的任何修改都对外透明

 

      6. 文中关于copy-on-write的结论部分来自于这里这里,且都经过本人实验证实,实验方法为采用"format debug"命令(很遗憾,似乎该命令没有文档说明……)。基本用法如下:

          >> format debug

          >> a = magic(2) % 注意不要打分号";"

          a =

 

          Structure address = 16849840 % a变量的结构体地址(元信息)

          m = 2 % 行数

          n = 2 % 列数

          pr = 1a321130 % 实部内存首地址

          pi = 0 % 虚部内存首地址

               1     3 % 数据内容

               4     2

      通过设断点和观察输出就可以看到各变量的内存地址了,进而可以甄别到底是发生instant-copy还是copy-on-write。

      关于"Structure address"(以下简称"Sa")到底是个什么东西,我曾经以为我知道,但是做了几个实验以后就彻底迷惑了。实验结果是——对Scalar或者Array变量进行赋值,得到新的Sa;对Structure类型变量进行赋值,Sa不变;传参时不管是Scalar/Array/Structure变量,都得到新的Sa……哪位高人给指点指点!

      文中关于mex传参的结论来源于我的切身的惨痛的经历,没有进行进一步查证,不过我觉得我的解释挺靠谱的,列为看官爱信不信~

<think>我们正在解决用户的问题:在MATLAB中执行`mex -clear all`命令时出现错误,提示“mex 参数 'clear ' 未知”。根据用户提供的引用和问题描述,我们需要分析这个错误的原因并提供解决方案。 ### 问题分析 `mex -clear all`命令用于清除所有MEX文件的缓存,但出现参数未知的错误。这通常是因为: 1. **命令语法错误**:MATLAB的`mex`命令在旧版本中可能不支持`-clear`选项,或者选项的书写方式不正确。 2. **版本兼容性**:`-clear`选项可能是较新版本MATLAB才引入的功能,而用户可能在使用旧版本。 3. **命令拼写错误**:用户可能输入了错误的选项。 ### 解决方案 #### 1. 检查MATLAB版本 首先,确认用户的MATLAB版本是否支持`-clear`选项。该选项在R2015b及以后版本中可用。在MATLAB命令窗口输入: ```matlab version ``` 如果版本早于R2015b,则需使用替代方法。 #### 2. 使用正确的命令语法 正确的命令是`mex -clear`(不带`all`),因为`-clear`选项本身就会清除所有MEX文件缓存。所以应尝试: ```matlab mex -clear ``` #### 3. 替代方法(旧版本MATLAB) 如果用户使用的是旧版MATLAB(不支持`-clear`选项),可以使用以下替代方法: - **手动清除MEX文件**:使用`clear mex`命令清除内存中的MEX文件。 - **删除临时文件**:手动删除临时目录中的MEX相关文件(如`.mex`文件、`.pdb`文件等)。临时目录路径可通过`tempdir`命令获取: ```matlab tmpdir = tempdir; % 然后手动删除该目录下与MEX相关的文件 ``` #### 4. 验证解决方案 执行以下步骤验证: ```matlab % 清除MEX缓存 mex -clear % 或使用 clear mex % 重新编译示例MEX文件测试 cd(fullfile(matlabroot, 'extern', 'examples', 'mex')) mex yprime.c yprime(1, 1:4) % 测试运行 clear yprime % 清除函数 ``` 如果编译和运行成功,则问题解决。 ### 错误原因详解 - **引用分析**:用户提供的引用[1]提到`mexErrMsgTxt`和`mexWarnMsgTxt`,这与MEX编程中的错误处理有关,但与本问题关联不大。引用[2][3]涉及MEX内存错误和调试,也与本问题无关。 - **核心问题**:`mex -clear all`中的`all`是多余的,因为`-clear`选项本身不需要参数。正确的命令是`mex -clear`。 ### 预防措施 1. **查阅文档**:使用`doc mex`查看当前版本的`mex`命令选项。 2. **版本适配**: | MATLAB 版本 | 清除MEX缓存的命令 | |-------------|--------------------------| | R2015b+ | `mex -clear` | | R2015a及之前 | `clear mex` + 手动删除临时文件 | ### 相关问题 §§相关问题§§ 1. 如何解决MATLAB MEX编译中的“未定义函数或变量”错误? 2. MATLAB中如何更新MEX编译器配置? 3. 如何解决MATLAB调用MEX文件时的内存泄漏问题? 4. MATLAB不同版本对MEX的支持有哪些差异? 5. 如何在旧版MATLAB中手动清除MEX缓存?</think>### 解决 MATLAB 运行 `mex -clear all` 报错"参数 'clear' 未知"问题 #### 错误原因分析 1. **命令语法错误** `mex` 命令不支持 `-clear all` 参数组合: - `-clear` 是独立参数,不需要 `all` 后缀 - 正确命令是 `mex -clear`(不带 `all`) 2. **版本兼容性问题** `-clear` 选项在较新 MATLAB 版本中引入(R2015b+),旧版本需使用替代方法 3. **缓存清理机制差异** MEX 缓存清理与普通变量清理使用不同机制[^1] --- ### 解决方案 #### 方法 1:使用正确语法(推荐) ```matlab % 清除所有 MEX 文件缓存 mex -clear ``` #### 方法 2:替代命令(兼容所有版本) ```matlab % 清除内存中的 MEX 文件 clear mex % 强制刷新函数缓存 rehash toolboxcache ``` #### 方法 3:手动清理缓存文件 1. 定位临时目录: ```matlab tmpdir = tempdir % 显示系统临时目录路径 ``` 2. 手动删除文件: - 删除所有 `.mex*` 文件(如 `.mexw64`, `.mexa64`) - 删除所有 `.pdb` (程序数据库) 文件 - 删除 `mex_*` 临时文件夹 --- ### 验证步骤 1. 执行清理命令: ```matlab mex -clear % 或 clear mex ``` 2. 检查缓存状态: ```matlab inmem % 显示内存中所有函数(应无 MEX 文件) ``` 3. 测试重新编译: ```matlab mex -v your_file.cpp % 验证能否正常编译 ``` --- ### 预防措施 1. **定期维护命令**: ```matlab % 每周执行一次 clear mex rehash toolboxcache delete(fullfile(tempdir, '*.pdb')) ``` 2. **版本适配方案**: | MATLAB 版本 | 推荐命令 | |-------------------|----------------------------| | R2015b (2015) 及更新 | `mex -clear` | | R2015a (2015) 及更早 | `clear mex` + `rehash toolboxcache` | 3. **调试建议**: ```matlab % 启用详细输出模式 mex -v -g your_file.cpp ``` 当出现内存错误时,参考内存调试方法[^2][^3] --- ### 典型错误场景对比 | 错误类型 | 解决方案 | 相关引用 | |--------------------------|----------------------------|---------| | `参数 'clear' 未知` | 使用 `mex -clear` | [^1] | | MEX 堆内存损坏 | 使用 Valgrind 调试工具 | [^3] | | 二次调用闪退 | 检查数组越界/内存泄漏 | [^2] | | 未定义符号错误 | 检查库链接顺序 | [^1] | > **提示**:若需深度调试 MEX 内存问题,可使用 MATLAB Engine API 配合 Valgrind[^3]: > ```c > // test.c 调试框架示例 > #include "engine.h" > int main() { > Engine *ep = engOpen(""); > engEvalString(ep, "load('input.mat');"); > // 调用 MEX 函数 > engEvalString(ep, "mexFunction(inputs);"); > } > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值