mcpField
M
matlab-upgrade-mex-ic

matlab-upgrade-mex-ic

github.com/matlab开发1.2k52 次安装1 天前更新
代码仓库

关于 matlab-upgrade-mex-ic

用于将 C、C++ 和 Fortran MEX 源码从分离复数 API 升级到交错复数 API 的 MATLAB 技能。它指导源码修改、可选的 SC/IC 兼容、构建和结果验证。

主要功能

  1. 01识别旧版分离复数 API 调用
  2. 02将旧访问器映射到类型化 IC API
  3. 03支持仅 IC 或 SC/IC 条件编译源码
  4. 04使用显式 MATLAB API 模式构建
  5. 05验证转换后的 MEX 输出

适用场景

  1. 01为 `mex -R2018a` 更新旧 MEX 源码
  2. 02维护同时支持 SC 和 IC 构建的源码
  3. 03检查转换后的复数数组结果
  4. 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.yaml
references/
api-mappings.md
pitfalls.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:升级前验证

在进行任何更改之前,先了解当前状态:

  1. 完整读取源文件
  2. 找出所有 SC API 调用(参见关键 API 映射)
  3. 记录 MEX 处理的数据类型(double、single、int32 等)
  4. 记录其是否处理复数数据(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) + .imagIC 中不存在
mxSetPi(arr, ptr)mxSetComplexDoubles(arr, ptr)IC 中不存在
mxGetImagData(arr)类型专用复数访问器IC 中不存在
mxSetImagData(arr, ptr)类型专用复数设置器IC 中不存在
用于复杂度检查的 mxGetPi(arr) != NULLmxIsComplex(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 函数时:

  1. 调用时: MATLAB 会将输入数据反交错——把一个连续缓冲区拆分为两个独立的实部/虚部数组。这是一次 O(n) 复制。
  2. 返回时: 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 APIIC API类型
mxGetPr + mxGetPimxGetComplexDoubles必需
mxSetPr + mxSetPimxSetComplexDoubles必需
mxGetImagData特定类型的复数访问器必需
mxSetImagData特定类型的复数设置函数必需
mxGetPi(x) != NULLmxIsComplex(x)必需
实数数据访问(在 IC 中不推荐)
SC APIIC API状态
mxGetPrmxGetDoubles不推荐——请替换
mxSetPrmxSetDoubles不推荐——请替换
mxGetDatamxGetDoubles/mxGetInt32s/等不推荐——请替换
mxSetDatamxSetDoubles/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) * 2mxGetElementSize 在 IC 模式下已返回 16使用 numElements * mxGetElementSize(arr),不要乘以 2
使用 mxGetPi(arr) != NULL 检查复数性mxGetPi 在 IC 模式下不存在使用 mxIsComplex(arr)
未询问兼容性需求便进行转换用户可能需要让文件兼容较旧版本的 MATLAB务必询问:仅使用 IC,还是使用 SC/IC 条件编译?
未提供验证步骤在 MEX 中,静默地产生错误结果会造成灾难性后果务必提供测试命令,对比新旧输出

版权所有 2026 The MathWorks, Inc.


评论

支持 Markdown