关于 matlab-upgrade-mex-ic
用于将 C、C++ 和 Fortran MEX 源码从分离复数 API 升级到交错复数 API 的 MATLAB 技能。它指导源码修改、可选的 SC/IC 兼容、构建和结果验证。
主要功能
- 01识别旧版分离复数 API 调用
- 02将旧访问器映射到类型化 IC API
- 03支持仅 IC 或 SC/IC 条件编译源码
- 04使用显式 MATLAB API 模式构建
- 05验证转换后的 MEX 输出
适用场景
- 01为 `mex -R2018a` 更新旧 MEX 源码
- 02维护同时支持 SC 和 IC 构建的源码
- 03检查转换后的复数数组结果
- 04审查旧版 C、C++ 或 Fortran MEX 代码
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ matlab/ matlab-agentic-toolkit --skill matlab-upgrade-mex-ic安装后在 Claude Code 中输入/matlab-upgrade-mex-ic确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
manifest.yamlreferences/api-mappings.mdpitfalls.md详细介绍
将 MEX 文件升级为交错复数 API
将 C、C++ 和 Fortran MEX 源文件从分离复数(SC)API 转换为交错复数(IC)API,并遵循 MathWorks 升级工作流,为使用 MX_HAS_INTERLEAVED_COMPLEX 的 SC/IC 条件编译构建提供防护措施,同时处理必需与建议的更改,并进行验证。
适用场景
- 用户的 C、C++ 或 Fortran MEX 文件使用
mxGetPr/mxGetPi、mxGetData/mxGetImagData或mxSetPr/mxSetPi - 用户想使用
mex -R2018a进行构建 - 用户询问交错复数、IC MEX 或 MEX 代码现代化相关问题
- 用户拥有来自 File Exchange、GitHub 或 R2018a 之前的旧版 MEX 代码
- 用户的 C++ MEX 文件(
.cpp、.cxx)使用 C Matrix API(mxGetPr/mxGetPi等) - 用户的 Fortran MEX 文件(
.F、.f90)通过%val()指针使用mxGetPr/mxGetPi - 用户报告 MEX 函数处理大型复数数组时速度较慢(瓶颈在函数调用开销,而非循环)
- 用户询问如何提升 MEX 处理复数数据的性能
不适用场景
- 从头编写新的 MEX 函数——无需转换,但应建议用户从一开始就使用 IC API(
mxGetDoubles、mxGetComplexDoubles等),以提升复数数据的性能,并使用现代、类型安全的数据访问方式,代替旧版mxGetPr/mxGetPi - C++ MEX API(
matlab::mex::Function)——这是完全不同的 API;此技能涵盖使用 C Matrix API(mex.h)的 C++ 文件,不涵盖 MATLAB Data API for C++ 和 C++ Engine - MEX 文件已使用类型化访问器(
mxGetDoubles、mxGetComplexDoubles等)——已兼容 IC,无需转换
工作流
请按顺序执行以下 7 个步骤。不要跳过任何步骤。
步骤 1:升级前验证
在进行任何更改之前,先了解当前状态:
- 完整读取源文件
- 找出所有 SC API 调用(参见关键 API 映射)
- 记录 MEX 处理的数据类型(double、single、int32 等)
- 记录其是否处理复数数据(
mxGetPi、mxIsComplex、mxCOMPLEX)
向用户报告:
- 找到的 SC API 调用数量
- 文件是否处理复数(SC 模式下,复数数组会产生分离与重新交错的额外开销)
- **性能影响:**如果文件处理复数数据,请说明 SC MEX 调用在 MATLAB 边界处会产生 O(n) 的分离与重新交错复制。对于大型数组,转换为 IC 可完全消除此开销(参见 IC 转换的性能优势)。
- 注意到的其他问题(例如,缺少错误检查)
步骤 2:输出文件选择
继续之前询问用户:
"转换后的代码应写入何处?
- 新文件——创建单独的文件(例如
<name>_ic.c或<name>_dual.c),保留原文件不变- 原地修改——直接编辑原文件。如果文件受版本控制且需要时可以还原,选择此项。"
如果用户选择新文件,请使用以下默认命名方式(也可使用用户指定的名称):
- 仅 IC:
<name>_ic.c/<name>_ic.F - SC/IC 条件编译:
<name>_dual.c/<name>_dual.F
如果用户选择原地修改,请确认文件可恢复(例如,“此文件由 git 跟踪;如有需要,可使用 git checkout <file> 还原。”)。然后直接编辑原文件。
告知用户你将写入哪个文件以及原因。
步骤 3:决策点——仅 IC 还是 SC/IC 保护模式?
什么是“SC/IC 保护模式”? 使用
#if MX_HAS_INTERLEAVED_COMPLEX预处理器保护的一份源文件,可在独立复数 API(mex -R2017b)和交错复数 API(mex -R2018a)下编译。编译器在 IC 模式下定义MX_HAS_INTERLEAVED_COMPLEX=1,在 SC 模式下定义MX_HAS_INTERLEAVED_COMPLEX=0,因此这些保护会在构建时选择正确的代码路径。
继续操作前,先询问用户:
“您是否需要此 MEX 文件同时兼容旧版 SC API 和新版 IC API?
- SC/IC 保护模式——使用
#if MX_HAS_INTERLEAVED_COMPLEX/#else保护代码,让同一份源文件可通过mex -R2017b file.c(SC 模式)和mex -R2018a file.c(IC 模式)构建。如果您需要支持多个 MATLAB 版本,请选择此项。- 仅 IC——完全转换为 IC API。构建时需要使用
mex -R2018a。代码更简单,但仅适用于 R2018a 及更高版本。”
如果用户选择 SC/IC 保护模式,使用 #if MX_HAS_INTERLEAVED_COMPLEX 预处理器模式:
C/C++ 文件:
#if MX_HAS_INTERLEAVED_COMPLEX
/* IC code path */
mxComplexDouble *pc = mxGetComplexDoubles(prhs[0]);
#else
/* SC code path */
double *pr = mxGetPr(prhs[0]);
double *pi = mxGetPi(prhs[0]);
#endif
Fortran 文件(.F 预处理源文件):
#if MX_HAS_INTERLEAVED_COMPLEX
mwPointer mxGetComplexDoubles
complex*16, pointer :: pc(:)
call mxGetComplexDoubles(prhs(1), pc)
#else
mwPointer mxGetPr, mxGetPi
mwPointer pr, pi
pr = mxGetPr(prhs(1))
pi = mxGetPi(prhs(1))
#endif
**Fortran 注意事项:**SC/IC 保护模式下的 Fortran 文件必须使用
.F扩展名(大写),以便 MEX 编译器调用 C 预处理器。.f或.f90扩展名会跳过预处理,因此#if保护将不起作用。对于无法使用预处理器保护的.f90文件,请使用仅 IC 转换。
如果用户选择 仅 IC,则直接转换,不使用保护。
步骤 4:迭代式重构
要完整升级到 IC,必须转换所有旧版数据访问 API。请转换此表中的每种模式:
| 旧版模式 | 替换方式 | 原因 |
|---|---|---|
用于数据访问的 mxGetPi(arr) | mxGetComplexDoubles(arr) + .imag | IC 中不存在 |
mxSetPi(arr, ptr) | mxSetComplexDoubles(arr, ptr) | IC 中不存在 |
mxGetImagData(arr) | 类型专用复数访问器 | IC 中不存在 |
mxSetImagData(arr, ptr) | 类型专用复数设置器 | IC 中不存在 |
用于复杂度检查的 mxGetPi(arr) != NULL | mxIsComplex(arr)——在 SC 和 IC 中均可用 | IC 中不存在 |
mxGetPr(arr) | mxGetDoubles(arr) | 在 IC 中用于复数数组会产生错误结果;无类型 |
mxSetPr(arr, ptr) | mxSetDoubles(arr, ptr) | 在 IC 中用于复数数组会产生错误结果;无类型 |
mxGetData(arr) + void* 转型 | 类型专用访问器(mxGetDoubles、mxGetInt32s 等) | 在 IC 中用于复数数组会产生错误结果;无类型 |
mxSetData(arr, ptr) | 类型专用设置器(mxSetDoubles、mxSetInt32s 等) | 在 IC 中用于复数数组会产生错误结果;无类型 |
为什么全部都必须转换? 在 IC 模式下,对复数数组使用
mxGetPr/mxGetData会返回指向交错数据(实部和虚部交错)的指针——如果仍将其视为仅含实部的数据进行迭代,结果会悄无声息地出错。对于仅含实部的数组,它们仍然可用,但没有类型信息(void*或始终为double*)。升级到 IC 时,应一次性转换所有用法,以确保正确性和类型安全。
常见转换错误(复杂度检查、输出分配、缓冲区大小、Fortran 交错布局)请参阅 references/pitfalls.md。
减少 SC/IC 保护模式下的代码重复
在 SC/IC 保护模式下,尽量只对访问器调用添加保护,而不要保护整个逻辑块,以减少代码重复。
C/C++ 文件:
/* Guard only the pointer acquisition — logic stays shared */
double *pr_in, *pr_out;
#if MX_HAS_INTERLEAVED_COMPLEX
pr_in = mxGetDoubles(prhs[0]);
#else
pr_in = mxGetPr(prhs[0]);
#endif
plhs[0] = mxCreateDoubleMatrix(m, n, mxREAL);
#if MX_HAS_INTERLEAVED_COMPLEX
pr_out = mxGetDoubles(plhs[0]);
#else
pr_out = mxGetPr(plhs[0]);
#endif
/* Shared logic — no duplication */
for (i = 0; i < numElements; i++) {
pr_out[i] = pr_in[i] * factor;
}
Fortran 文件(.F):
c Guard only the pointer acquisition
#if MX_HAS_INTERLEAVED_COMPLEX
pr_in = mxGetDoubles(prhs(1))
#else
pr_in = mxGetPr(prhs(1))
#endif
c Shared logic — no duplication
call mxCopyPtrToReal8(pr_in, data, numElements)
do i = 1, numElements
data(i) = data(i) * factor
end do
对于复数数据,两种模式的布局不同(交错结构体与分离数组),因此必须在逻辑代码块外围使用完整的 #if / #else 代码块。
经验法则:
- 实数数据路径——只保护访问器,共用循环
- 复数数据路径——保护整个代码块(不同的数据布局需要不同的循环主体)。C、C++ 和 Fortran 中完整的复数循环示例,请参阅
references/api-mappings.md§“SC/IC Guarded Pattern”。
完整的函数映射表请参阅 references/api-mappings.md。
步骤 5:构建
提供准确的构建命令(根据语言调整扩展名):
% C/C++ files
mex -R2018a <filename>.c % Explicit IC mode
mex -R2017b <filename>.c % Explicit SC mode
mex <filename>.c % Default (version-dependent)
% For C++: same flags with .cpp or .cxx extension
mex -R2018a <filename>.cpp % Explicit IC mode
% Fortran files (.F with preprocessor guards)
mex -R2018a <filename>.F % Explicit IC mode
mex -R2017b <filename>.F % Explicit SC mode
mex <filename>.F % Default (version-dependent)
如果文件使用 SC/IC 条件编译保护,请提醒用户测试全部三种构建模式:-R2017b(SC)、-R2018a(IC)和默认模式(不带标志)。
Fortran: 文件必须使用
.F(大写)扩展名,预处理器保护才能生效。如果源文件扩展名为.f或.f90,添加#if保护时请将其重命名为.F或.F90。
步骤 6:验证
提供 MATLAB 测试命令,用于比较转换后的 MEX 与原始 MEX。
仅限 IC 的转换(单独的源文件):
%% Build original with SC, converted with IC
mex -R2017b originalFile.c -output func_sc % Explicit SC build
mex -R2018a convertedFile_ic.c -output func_ic % Explicit IC build
%% Compare outputs
Z = complex(rand(4,4), rand(4,4));
assert(isequal(func_sc(Z), func_ic(Z)), 'Output mismatch!');
使用 SC/IC 条件编译保护的文件(相同源文件,三种构建模式):
%% Build same source in all three modes
mex -R2017b convertedFile.c -output func_sc % Explicit SC (forces separate complex)
mex -R2018a convertedFile.c -output func_ic % Explicit IC (forces interleaved complex)
mex convertedFile.c -output func_default % Default (no API flag)
%% Compare outputs — all three must match
Z = complex(rand(4,4), rand(4,4));
out_sc = func_sc(Z);
out_ic = func_ic(Z);
out_default = func_default(Z);
assert(isequal(out_sc, out_ic), 'SC vs IC mismatch!');
assert(isequal(out_sc, out_default), 'SC vs default mismatch!');
始终测试以下内容:
- 复数输入(如果 MEX 处理复数数据)
- 实数输入(如果 MEX 处理实数数据)
- 边界情况:空数组、标量、大数组
- 全部三种构建模式:
-R2017b(SC)、-R2018a(IC)和默认模式(不带标志) - 原始源文件仍可通过
mex -R2017b编译(确认其未被修改)
步骤 7:编写文档
在转换后文件的头部注释中添加构建信息:
/*
* Compile (Interleaved Complex API, R2018a+):
* mex -R2018a filename.c
*
* Original (Separate Complex API):
* mex originalFilename.c
*
* API Migration:
* mxGetPr/mxGetPi -> mxGetComplexDoubles
* mxSetPr/mxSetPi -> mxSetComplexDoubles
*/
IC 转换的性能优势
IC 转换不只是 API 现代化——对于处理复数数据的 MEX 函数,它还能带来可测量的性能提升。
IC 为什么更快
自 R2018a 起,MATLAB 在内部以交错格式([re0, im0, re1, im1, ...])存储复数数组。调用使用 SC API 编译的 MEX 函数时:
- 调用时: MATLAB 会将输入数据反交错——把一个连续缓冲区拆分为两个独立的实部/虚部数组。这是一次 O(n) 复制。
- 返回时: MATLAB 会将输出数据重新交错——把两个独立缓冲区合并。又是一次 O(n) 复制。
对于包含 1000 万个元素的复数 double 数组,每个跨越边界的复数数组约需传输 80 MB(N × 16 字节)。包含一个复数输入和一个复数输出的 MEX 总共需要约 160 MB;包含三个复数输入的 MEX 仅输入就需要约 240 MB。
IC MEX 函数以 MATLAB 的原生布局访问数据——跨越边界时零复制。
其他性能优势
- 缓存局部性:
z[i].real和z[i].imag相邻(位于同一缓存行)。SC 需要在相距数百万个元素的两个数组之间跳转。 - 批量复制只需一次 memcpy: 复制复数数组只需一次
memcpy(dst, src, n * sizeof(mxComplexDouble)),而不是分别复制两次。 - FFTW/BLAS/LAPACK 互操作: IC 布局与
fftw_complex、double _Complex(C99)以及 cuBLAS/cuFFT 类型逐字节兼容——无需打包或解包。 - 适合 SIMD: 编译器可以生成打包 double 指令,一次操作即可加载完整的复数。
何时性能提升足以支持迁移到 IC
| 场景 | 加速效果 |
|---|---|
| 大型复数数组(>100K 个元素) | 高——边界复制开销占主导 |
| 在循环中频繁调用 MEX | 高——复制开销会累积 |
| MEX 封装 FFTW/BLAS 调用 | 高——无需打包或解包 |
| 小型数组(<1K 个元素) | 低——复制开销可以忽略 |
仅实数数据(不使用 mxGetPi) | 无——不会发生反交错 |
告诉用户:“对于包含 [N] 个元素的复数数组,SC API 会强制 MATLAB 为每个复数数组参数复制约 [N×16/1e6] MB。有 [K] 个复数数组跨越边界时,每次调用约需复制 [K×N×16/1e6] MB。IC 可完全消除此开销。”
基准测试模板
转换后,提供以下基准测试以量化加速效果:
%% Benchmark SC vs IC MEX performance
N = 1e7; % 10M complex elements
Z = randn(1,N) + 1i*randn(1,N);
numIter = 20;
% Build both versions
mex -R2017b originalFile.c -output func_sc
mex -R2018a convertedFile_ic.c -output func_ic
% Warmup
for k = 1:3, func_sc(Z); func_ic(Z); end
% Time SC
tSC = zeros(numIter,1);
for k = 1:numIter, tic; func_sc(Z); tSC(k) = toc; end
% Time IC
tIC = zeros(numIter,1);
for k = 1:numIter, tic; func_ic(Z); tIC(k) = toc; end
fprintf('SC median: %.2f ms\n', median(tSC)*1000);
fprintf('IC median: %.2f ms\n', median(tIC)*1000);
fprintf('Speedup: %.1fx\n', median(tSC)/median(tIC));
assert(isequal(func_sc(Z), func_ic(Z)), 'Output mismatch!');
优化提示
- 对原地操作类操作使用
mxDuplicateArray:plhs[0] = mxDuplicateArray(prhs[0]); z = mxGetComplexDoubles(plhs[0]);——然后直接修改z[i]。这样可以避免手动分配,并让 MATLAB 优化复制操作。 - 单遍循环: 在 IC 模式下,共轭或缩放操作可在一次循环迭代中同时处理
.real和.imag,实现完美的空间局部性。 - 避免不必要的
mxMalloc: 将输出创建为mxCOMPLEX,并直接写入mxGetComplexDoubles返回的缓冲区——无需中间分配。
关键 API 映射
最常见转换的摘要。完整表格见 references/api-mappings.md。
复杂数据访问
| SC API | IC API | 类型 |
|---|---|---|
mxGetPr + mxGetPi | mxGetComplexDoubles | 必需 |
mxSetPr + mxSetPi | mxSetComplexDoubles | 必需 |
mxGetImagData | 特定类型的复数访问器 | 必需 |
mxSetImagData | 特定类型的复数设置函数 | 必需 |
mxGetPi(x) != NULL | mxIsComplex(x) | 必需 |
实数数据访问(在 IC 中不推荐)
| SC API | IC API | 状态 |
|---|---|---|
mxGetPr | mxGetDoubles | 不推荐——请替换 |
mxSetPr | mxSetDoubles | 不推荐——请替换 |
mxGetData | mxGetDoubles/mxGetInt32s/等 | 不推荐——请替换 |
mxSetData | mxSetDoubles/mxSetInt32s/等 | 不推荐——请替换 |
复数数据类型(IC 模式)
mxComplexDouble /* struct with .real and .imag (both double) */
mxComplexSingle /* struct with .real and .imag (both float) */
mxComplexInt32 /* struct with .real and .imag (both int32_T) */
约定
- 写入前务必询问是创建新文件还是就地修改
- 务必说明每项更改的原因(IC 中不存在,或对复数数组产生错误结果/未指定类型)
- 转换前务必询问是使用 SC/IC 条件编译模式还是仅使用 IC
- 务必提供验证测试命令
- 使用
mxIsComplex()检查复数性,切勿使用mxGetPi() != NULL。mxIsComplex在 SC 和 IC 模式下均可用,因此可置于#if守卫之外。 - IC 模式下的缓冲区大小:
mxGetElementSize()返回完整的交错存储大小(复数 double 为 16 字节)。不要乘以 2。 - **文件命名(创建新文件时):**仅用于 IC 转换的文件使用
_ic.c/_ic.cpp或_ic.F后缀;使用 SC/IC 条件编译的文件使用_dual.c/_dual.cpp/_dual.F;或者按用户选择就地修改 - **C89 兼容性(仅 C 文件):**在每个
#if/#else块顶部或函数顶部声明变量。MEX 编译器可能会强制执行 C89 规则,要求声明必须位于语句之前。C++ 文件没有此限制。 - **Fortran 文件扩展名:**SC/IC 条件编译的 Fortran 文件使用
.F(大写),以便 C 预处理器处理#if MX_HAS_INTERLEAVED_COMPLEX守卫。.f和.f90不会调用预处理器。 - **Fortran 指针处理:**在 Fortran MEX 中,数据指针是通过
%val()传递给复制例程(mxCopyPtrToReal8、mxCopyPtrToComplex16)的mwPointer整数。在 IC 模式下,使用mxGetComplexDoubles,它会返回指向交错缓冲区的mwPointer。
常见错误
| 错误 | 错误原因 | 正确做法 |
|---|---|---|
| 未询问便修改 | 如果没有版本控制,用户可能会丢失可正常工作的 SC 版本 | 务必询问:创建新文件还是就地修改? |
保留原有的 mxGetPr/mxGetData 调用 | MathWorks 不建议在 IC MEX 函数中使用这些调用 | 替换为类型化访问器(mxGetDoubles、mxGetInt32s 等) |
对复数缓冲区使用 numElements * mxGetElementSize(arr) * 2 | mxGetElementSize 在 IC 模式下已返回 16 | 使用 numElements * mxGetElementSize(arr),不要乘以 2 |
使用 mxGetPi(arr) != NULL 检查复数性 | mxGetPi 在 IC 模式下不存在 | 使用 mxIsComplex(arr) |
| 未询问兼容性需求便进行转换 | 用户可能需要让文件兼容较旧版本的 MATLAB | 务必询问:仅使用 IC,还是使用 SC/IC 条件编译? |
| 未提供验证步骤 | 在 MEX 中,静默地产生错误结果会造成灾难性后果 | 务必提供测试命令,对比新旧输出 |
版权所有 2026 The MathWorks, Inc.