首页/文章/ 详情

FISH 太慢?用 C++ 封装它,35k 接触快 35 倍

1天前浏览3

PFC 的 FISH 很好用。改边界、加监测、算统计,几行就能跑起来。

但模型一大,循环就慢得明显。

我拿一个 3.5 万个接触的 2D 试样做组构张量测试:FISH 跑一趟 146 ms,C++ 只要 4.2 ms。同一套公式、同一批接触,A、φ、N 逐点完全一样,速度差 34.9 倍。

先说结论:PFC 6.0 支持把 C++ 编译成 DLL,注册成 FISH 函数,在 FISH 里直接调用。它不是替代 FISH,而是把“遍历几万个元素”的内循环外包给编译后的 C++。大模型的重统计、重后处理,可以快一个数量级。

1. FISH 慢在哪

FISH 是解释执行的。loop foreach 每转一圈,contact.normal(c)、comp.x(...)、数组下标,都要走一次名字查找和参数转换。

单次开销不大,但乘以几十万次就明显了。

这不是写法问题,是解释器的固有成本。FISH 擅长的是“调度”:读参数、加边界、控制加载、注册监测。它不擅长“对 3 万个接触逐个做十几次浮点运算”。

PFC 6.0 给了一个出口:C++ FISH intrinsic 插件。

做法不复杂:写一个 DLL,实现 IFishLibrary 接口,用 program load function 加载。加载后,DLL 里注册的函数会变成 FISH 里的普通函数,名字类似 myfish.fabric。

alt  

图 1 左边是 FISH 逐条解释的循环;右边是编译进 DLL 的 C++ 循环。FISH 只发起一次调用,整段遍历在 C++ 里完成。

可以把它理解成:FISH 是调度员,C++ 是外包工人。调度员把“这一批接触的组构张量算一下”交出去,工人算完把三个数带回来。

2. 不是只能算数学

很多人以为 C++ 插件只能做纯计算。实际上,拿到 IProgram 之后,可以访问 PFC 的元素接口:

对象      
C++ 能做什么      
球      
读写位置、速度、角速度、位移、朝向、半径、密度、fix、外力、接触力;可新建球      
接触      
遍历全部/活动接触;读法向力、切向力、gap、模型名;可换模型、改属性、设力      
墙      
创建、删除、移动、设速度、设朝向、整套 servo      
clump / pebble      
遍历、读接触与能量、整体缩放/旋转      
分组 / 测量圆      
读写 group、FISH extra;读应力、孔隙率、配位数      

删除球没有直接的 C++ 接口,可以发 ball delete 命令。逐 cycle 自动回调也没有注册入口,仍然用 FISH 的 fish callback 去调 C++。

所以定位很清楚:C++ 负责“对很多元素做同一件事”的内循环;FISH 负责流程和调度。

3. 实测:组构张量演化

组构张量(Oda fabric tensor)是描述接触网络取向的常用量。二维里,对每个接触法向 n=(nx,ny),取走向向量 s=(-ny, nx):

Fij = 平均(s_i * s_j) A   = (λ1 - λ2) / (λ1 + λ2) φ   = 主走向角 

A 越接近 0 越各向同性,越接近 1 越定向;φ 是主方向。

测试模型是一个受控 2D 盒式试样:


  • 18,725 个球,约 35,000 个活动 ball-ball 接触;
  • 顶板分 25 级下压,每级 20 µm,再跑 200 cycle 松弛;
  • 总竖向应变 0.5%,共 26 个采样点;
  • 每个点分别用 FISH 和 C++ 算一遍,两边都只遍历活动 ball-ball 接触。
alt  

图 2 组构各向异性 A 随竖向应变的变化。橙色空心圆是 FISH,蓝色实线是 C++;两条曲线完全重合。

alt  

图 3 主走向角 φ 的演化。两边同样逐点重合。

指标      
FISH      
C++ intrinsic      
单趟耗时(35k 接触)      
146.3 ms      
4.19 ms
26 次采样总耗时      
3804 ms      
109 ms
最大 |A 差异|      
0      
0      
最大 |φ 差异|      
0      
0      
接触数 N      
逐点相同      
逐点相同      
alt  

图 4 26 次采样的总耗时对比:3804 ms 对 109 ms,聚合加速 34.9 倍。

需要说明:这组加载是加速的分步位移,只为了让接触网络在可接受的时间内演化,不是力学强度研究。A 的绝对值很小(0.0024 到 0.0041),φ 对微小各向异性很敏感,不要拿这组曲线下物理结论。

这里比的是“算得快不快”,不是“算得对不对”。 对不对由 A、φ、N 的逐点零差异回答。

4. 过程详解:从零到能跑

4.1 生成工程

官方安装目录里有模板:

pluginfiles/templatefiles/function.zip 

它包含 IFishLibrary 的骨架、四个必须导出的函数,以及 ball/contact 遍历示例。

我把“复 制模板、替换工程名、改类名、改 DLL 命名”这一串做成了脚本:

bashpython gen_fish_plugin.py --name myfish --out D:/dev

生成:

D:/dev/myfish/myfish.h D:/dev/myfish/myfish.cpp D:/dev/myfish/myfish.vcxproj 

模板里已经带了 ball/contact 遍历、组构张量和性能基准示例,不需要的可以删。

alt  

图 5 生成、编译、验证三步;第四步是在真实算例里加载使用。

4.2 插件骨架:注册函数

DLL 必须导出四个函数:getName、getMajorVersion、getMinorVersion、createInstance。真正决定“FISH 里叫什么名字”的,是 createIntrinsics:

cppQStringList createIntrinsics(IFish *fish) {
    fish->registerLibrary(new Fishmyfish(
        FabricFunction,
        "myfish.fabric",
        0, "v3flt", true));
    return {};
}

这里注册了一个叫 myfish.fabric 的函数:无参数,返回三浮点向量,只读。

get() 在函数出现在等号右边时调用;set() 在左边时调用。算法写在 get() 的 switch 里。

4.3 访问接触,计算张量

核心循环只有十几行:

cppIModuleBall *m = prog->findInterface<IModuleBall>();
const IContainerContact *cc = m->getIContainerBallBallContact();

double fxx = 0, fyy = 0, fxy = 0;
quint64 nn = 0;

for (ICIterator<IContact> it(cc->getIContainer());
     !it.atEnd(); ++it) {
    if (!it->getActive()) continue;
    DVect n = it->getNormal();
    double sx = -n.y(), sy = n.x();
    fxx += sx*sx; fyy += sy*sy;
    fxy += sx*sy; ++nn;
}

getActive() 这一行很关键。FISH 的 contact.list('ball-ball') 只返回活动接触;C++ 的容器里还包含非活动接触。不过滤,接触数会多,A 会偏。

最后算 A 和 φ,把 (A, φ, N) 打包成 DVect3 返回:

cpp*p = DVect3(A, phi, double(nn));

FISH 侧这样取:

local v = myfish.fabric local A = comp.x(v) local phi = comp.y(v) local N = comp.z(v) 

FISH 版算法长得几乎一样:

; fxx / fyy / fxy / nn 先置 0 loop foreach local c contact.list('ball-ball')     local n = contact.normal(c)     local sx = -comp.y(n)     local sy =  comp.x(n)     fxx = fxx + sx*sx     fyy = fyy + sy*sy     fxy = fxy + sx*sy     nn = nn + 1 endloop 

区别只在:一个由解释器逐条执行,一个是编译后的机器码。

4.4 编译

bashpython build_fish_plugin.py \
  --proj D:/dev/myfish/myfish.vcxproj \
  --cfg Release2D

产物:

x64/Release2D/functionmyfish2d006.dll   # PFC2D x64/Release/functionmyfish006.dll       # PFC3D(--cfg Release) 

两个容易忽略的点:


  • 源码必须是纯 ASCII,注释也用英文。MSVC 按系统代码页解析中文注释时,可能吞掉下一行;
  • 2D 和 3D 的 DLL 不能混用。

4.5 在 PFC 里加载

model new model configure plugins program load function 'functionmyfish2d006.dll' 

model configure plugins 不能省。少了它,加载看起来不报错,第一次调用才报“Must be configured for C++ plugins”。

如果要在已有 .sav 上继续算,先 load,再 restore。restore 不会自动找回插件;如果 .sav 本身没开 plugins,restore 后还要再执行一次 model configure plugins。

4.6 验证

验证脚本会自动建一个最小模型,调用 freq、ball.loop、contact.loop、fabric,并检查日志:

bashpython verify_fish_plugin.py \
  --name myfish \
  --dll D:/dev/myfish/x64/Release2D/functionmyfish2d006.dll \
  --dim 2d

日志里出现 function plugin myfish loaded 和 CHECK fabric A=,才算真正跑通。

5. 什么时候值得用

适合      
不适合      
几万个接触/颗粒的重复统计      
每接触、每 cycle 的本构计算(用 UDM)      
每次存盘都要算的组构、配位数、损伤      
逐 cycle 自动回调(用 FISH callback 调 C++)      
FISH 没有的算法:KD-tree、并查集、SVD、图算法      
几千个元素的小模型,编译调试不划算      
批量改球/墙/分组、复杂加载控制      
需要跨 PFC 版本分发的工具      

一个简单的判据:如果一段 FISH 循环要跑很多次、每次要遍历上万个元素,就值得搬到 C++。

6. 这套流程已经做成技能

我把生成、编译、验证三步做成了 pfc-fish-cpp 技能:


  • gen_fish_plugin.py:模板 → 可编译工程;
  • build_fish_plugin.py:自动找 MSBuild、SDK、Qt,编 2D/3D;
  • verify_fish_plugin.py:建最小算例 → PFC 无头运行 → 日志验收。

2D 和 3D 都实机跑过。模板里带组构张量和性能基准示例,可以直接改成自己的统计量。

一句话:C++ 不是来替代 FISH 的。FISH 继续负责“怎么算、什么时候算”,C++ 只负责“这一批元素怎么快速算完”。分工清楚,代码就不会失控。

7. 最后

FISH 的解释器让 PFC 很灵活,但也决定了它不适合扛大循环。

C++ FISH intrinsic 的价值,不是把整个脚本改写成 C++,而是只把最热的那一层循环换掉:数据还在 PFC 里,流程还在 FISH 里,结果由编译后的 DLL 返回。

35k 接触的组构张量,146 ms 变成 4 ms。这个差距,在长时间加载和多次采样的算例里,会直接体现在墙钟上。

VibePFC 社区 · 一起把 AI 玩进 PFC
我成立了一个 VibePFC 社群,专门聊 AI 怎么落地到 PFC / 数值模拟——让大模型读懂模型、自己改脚本、自动排查"看不懂"的问题。收一个 50 元的门槛费。
想进群的同学,加 QQ 763388012,备注"vibePFC"即可。

8. 参考资料


  • PFC 6.0 文档:C++ FISH Intrinsic Plug-ins(program load function、IFishLibrary)。
  • 实测工程与基准:_archive/fish-cpp-plugin-test(含 fabric_evolve_box.dat、fabric_benchmark.csv)。
  • 技能:~/.claude/skills/pfc-fish-cpp(生成 / 编译 / 验证三脚本)。


来源:超级大的lobby
ACTUGpythonUMPFC控制
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2026-11-14
最近编辑:1天前
lobby
硕士 | 无 擅长颗粒流PFC
获赞 963粉丝 5964文章 121课程 23
点赞
收藏
作者推荐
未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习计划 福利任务
下载APP
联系我们
帮助与反馈