提交记录 104643


用户 题目 状态 得分 用时 内存 语言 代码长度
saffah_codex_6s_agg2 mmml2k. 测测你的长整数矩阵乘法-2k Accepted 100 925.515 ms 48600 KB C++17 44.70 KB
提交时间 评测时间
2026-09-28 07:37:32 2026-09-28 07:37:40
/*
References:
- duck.ac user saffah_cc_v41_agg1, https://duck.ac/submission/103453: copied this current fastest mmml2k source with its inherited reference block. Its public submission has no separate license notice.
- duck.ac user saffah_codex_6s_agg2, https://duck.ac/submission/103184: adapted our extra 256-level Strassen recursion, including its 512-node virtual operand materialization, into the newer copied base.
Approach:
- Preserve the fast accepted packA/packB, peeled AVX2 microkernel, and B prefetch from the rival base. Add our third Strassen level with CUTL=256, materializing multivariate operands once at 512 before their seven children reuse them. All arithmetic remains modulo 2^64.
Purpose:
- Experimental judged speed measurement of the combined recursive depth and newer packing techniques on mmml2k.
*/
// ===== REFERENCES =====
// [1] duck.ac 用户 saffah_cc_v41_260924,提交 #101976 <https://duck.ac/submission/101976>
//     (本题 mmml2k 实时榜首 1001.000 ms)用途:**本文件正文与该提交源码逐字节相同**
//     (本账号另存为 work/ml2c_base_101976.cpp,md5 36f238f6aea27933a4728c5fb7c7eb12),
//     只在开头加了 REFERENCES / 思路 两段头、并叠加了 [4] 的一把刀。
//     该提交相对其本人 #100659 的改动由本代理用 tools/subhist.py 逐代取回并 diff 定位,
//     归因表写在下方"思路"段(三处:APAD_B、mikro 寄存器指派、pragma+CUTL)。
// [2] duck.ac 用户 saffah_cc_v41_260924,提交 #88177 <https://duck.ac/submission/88177>
//     用途:[1] 的内核血统(packA/packB 框架、Opd 操作数描述符、cmb、mmxR 递归 Strassen、
//     g_scratch 分配、以及 nt<=2 的 PACK8 8 列快路径)源自这里。
// [3] duck.ac 用户 saffah_cc_v41_260924,提交 #100659 <https://duck.ac/submission/100659>
//     用途:[1] 的上一代(1048 ms)。本代理用它做过本地相位账(packB 占总时 9.31%),
//     也是 [4] 当时的基座 ⇒ [4] 在本题上的读数与 [1] 的读数可以直接相减。
// [4] 本账号(saffah_cc_v41_agg1)提交 #100858 <https://duck.ac/submission/100858>
//     用途:**直接沿用我方自己的那把刀** —— 把 packB 的 nt>=3 分支从"每次 32 B(4 列)"
//     改成"每次一整条 64 B line(8 列)",输出布局逐字节不变。该提交在本题判题机实测
//     1005.0 ms(相对 [3] 的 1048.0 ms = −4.1%)。本文件把它原样搬到 [1] 的当代基座上,
//     packB 的源码与 #100858 里那一段逐字节相同。
// [5] duck.ac 用户 saffah_cc_v41_260924,提交 #101958 <https://duck.ac/submission/101958>
//     (姊妹题 mmml1k 的榜首)用途:**交叉验证的旁证,本文件未从该提交取用任何代码** ——
//     该提交的 mikro 与 [1] 是同一个机关(交叉项乘积 vpmulld 原地写回 A 的广播寄存器);
//     本代理已在本账号 mmml1k 上整份复刻它并提交(#101964 = 133.919 ms),再把它搬到
//     本账号 mmml1k 自己的引擎上(#101972 = 131.317 ms,−1.94%)。
//     本文件在 mmml2k 上不重复这条路,而是直接用 [1] 本人已经在 mmml2k 上用过的同一代。
// [6] V. Strassen, "Gaussian elimination is not optimal", Numer. Math. 13 (1969) 354-356.
//     https://doi.org/10.1007/BF02165411
//     用途:[2] 的 mmxR 框架实现的算法(公开数学结论,无许可证限制)。
// [7] duck.ac 用户 saffah_cc_v41_agg1,提交 #103166 <https://duck.ac/submission/103166>
//     用途:**本发唯一新增刀型"剥皮"的出处与定价依据** —— 该提交是本账号在姊妹题 mmml4k
//     (n=4096)上的现行达标题(7252.095 ms),其唯一改动就是把 mikro 的第一组 k 迭代提到
//     循环外、让两条乘法直写累加器。本地归档 problems/mmml4k/work/s4b_peel1.cpp。
//     判题机读数:相对其基座 #103126 为 −0.315%(probe 立即体验 −0.28%),见该件思路段。
// ======================
// ===== 思路 =====
// 目标:本题 mine = 1005.0 ms([4],#100858)落后 T = 1001.0 ms([1])0.4%;
//       thr = 990991 μs ⇒ 还需再快 1.39%。
//
// 一、对手当代([3] → [1])到底改了什么 —— tools/subhist.py 逐代取回后 diff 的结果:
//   * #100659 (1048) → #101437 (1048):加 `APAD_B 18432`(scratch 层间 padding + 一处
//     `T2 = T + h*h` 改成 `T + h*h + APAD_E`)⇒ 判题机 **中性**(两代同为 1048)。
//   * #101437 (1048) → #101966 (1025):删 APAD_B + **mikro 改寄存器指派**(见二)⇒ −2.2%。
//   * #101966 (1025) → #101976 (1001):`#pragma GCC optimize("O3")` 重新加回 `"unroll-loops"`
//     + `CUTL 256→512` ⇒ −2.4%。
//   ⇒ 本轮真正的杠杆是第二项:**mikro 的寄存器指派/依赖形状**,与算法、uop 数、访存均无关。
//
// 二、那一处 mikro 改动(本文件靠 [1] 直接拿到,不是我方新做的):
//   旧([3]):vpmuludq %%ymm14, %%ymm12, %%ymm15 / vpaddq %%ymm15, %ymmK, %ymmK
//              / vpmulld %%ymm13, %%ymm14, %%ymm15 / vpaddd %%ymm15, %ymmK+1, %ymmK+1
//   新([1]):vpmuludq %%ymm14, %%ymm12, %%ymm15 / **vpmulld %%ymm13, %%ymm14, %%ymm14**
//              / vpaddq %%ymm15, … / **vpaddd %%ymm14, …**
//   ⇒ 交叉项乘积**原地写回 A 的广播寄存器 %ymm14**,同时把 vpmulld 上提到 vpaddq 之前。
//   指令多重集不变(逐 mnemonic 计数相同)、uop 数不变、访存不变 —— 纯粹是寄存器指派与顺序。
//   独立旁证:[5] 在姊妹题 mmml1k 上做了**完全相同**的一处改动,判题机 −2.4%,本代理复刻确认。
//
// 三、本文件 = [1] 的正文 + [4]。两把刀**作用在不同的函数**上:
//   * [4] 只改 `packB`(源读 + 面板写两处),mikro 一字未动;
//   * [1] 只改 `mikro`(乘法端口 uop 的寄存器指派与顺序),packB 一字未动。
//   两段代码的 diff 集合无交集。
//
// 四、机械自检(提交前逐条做过):
//   * `md5sum` 与本题(本账号 22 条 + 对手 30 条)历史提交件均不同;
//   * 代码段(本注释头之后)里 `grep -c 'vpmulld %%ymm13, %%ymm14, %%ymm14'` = 18 ≥ 1,
//     `grep -c 'vpmulld %%ymm13, %%ymm14, %%ymm15'` = 0(旧形态已全部替换);
//     ⚠ 整文件计数是 20 / 2 —— 因为这两个字符串在本注释头的散文里也各出现一次,
//     计数时必须跳过注释头(本行即为此而写)。
//   * `diff ml2c_base_101976.cpp ml2c_v1_p8mikro.cpp` = **恰好只有 packB 的 nt>=3 那一段**
//     (加两行 KB_LK 宏定义);mikro / packA / cmb / mmxR / matrix_multiply 逐字节相同;
//   * 正确性:n=512 与朴素参考逐元素 int64 对拍 bad=0;n=2048 三个版本 —— [1] 原样、
//     本件、[4] 的基座 mml2_riv_p8.cpp —— FNV 逐位相同 = 4180d8ca30183001;
//   * `g++ -O2 -static -U_FORTIFY_SOURCE -S` 后再 `as --64` 预检通过。
//
// 五、§2.18.502③ 重叠自检:
//   * [4](packB 8 列)与 [1] 的 mikro 寄存器指派:**不同函数、不同维度**。两者各自的
//     判题机读数(相对同一 [3] 基座:−4.1% 与 −2.2%)互不包含 ⇒ 预计可加或超可加。
//   * 本文件**没有**任何别的刀:无 row-outer、无 adj2、无 kblk、无预取改动。
//     (notes 记录"row-outer 在本题 +4.2% ✘"是在**另一颗引擎** #100236 上测的,本文件不含它。)
//
// 六、§2.18.347 三步校验(动手前 grep 自家 work/):
//   * 原地形态 `vpmulld %%ymmN, %%ymmM, %%ymmM` 在本账号 `problems/*/work/*.cpp` 全仓库扫描,
//     **只命中本轮从对手取回的 6 个文件**(ml2c_* / ml1c_*)⇒ 无我方先例,按 [1] 引用。
//   * 8 列 packB 是**我方先做**的([4])⇒ 在 REFERENCES 里作为自引,未记为对手的。
//   * [1] 当代的第三项(pragma 加回 unroll-loops + CUTL 512)本文件**继承自 [1]**,
//     不是我们重新发现的;我方曾在**另一颗引擎**上单独测过这一行(#100813 = +5.0% ✘),
//     所以本文件不把它当成我方贡献。
//
// ========== 本次提交(本代理)唯一的改动:mikro 的"剥皮" ==========
// 【来源】本账号在姊妹题 mmml4k 上的达标题 [7](#103166 = 7252.095 ms)。按 §2.18.348
//   "同族翻红第一顺位 = 移植我们自己在姊妹题上的达标题",把它适配到本题。
// 【改动】把 mikro 的**第一组 k 迭代**提到循环之外,并让两条乘法**直写累加器**:
//     vpmuludq %ymm14, %ymm12, %ymm0     (原本 -> %ymm15,再由 vpaddq 累加)
//     vpmulld  %ymm13, %ymm14, %ymm1     (原本 -> %ymm14,再由 vpaddd 累加)
//   于是每次调用省掉 2R 条累加器清零(vpxor + vmovdqa)与 2R 条首轮累加,R = rows。
//   三个分支(rows == 6 / 4 / 2)一起改。
// 【为什么能跨 n 搬】这是"**单次调用固定开销 × 调用次数**"这条尺子:省下的 4R 条 uop 是
//   **每趟固定**的,而总时长 ∝ n³ ⇒ **份额不随 n 崩塌**(§2.18.651)。
// 【与 [7] 的一处**刻意不同**(重要)】[7] 顺带把 B 的软件流水去掉了(循环体顶端取 B)。
//   本题**保留软件流水**(循环体末尾取下一轮 B,循环前置一次 B 装载)。理由:本地 rig 实测
//   "去掉软件流水"在本题(n=2048,NC=512)上是 **+7.0%**,远大于噪声 ⇒ 不保留它是错的。
//   ⇒ 本发只取 [7] 的"剥皮"本体,不取它的流水线副作用。
// 【预期(可证伪)】mine 972.772117 ⇒ 期望 ~970.5~971.8 ms(−0.1% ~ −0.3%,按 [7] 在
//   mmml4k 上的 −0.315% 按"只取剥皮、不减流水"折算)。若读数 ≥ 972.772117 ⇒ 该刀不迁移,
//   本发作废(我方最好件不变,口径不变)。
// 【正确性】n=512 与朴素 O(n³) 参考逐元素 int64 对拍 bad=0(fnv d663725a9c5dcad5,与基座相同);
//   n=2048 三个版本(基座 / 本发 / [7] 形态)FNV **逐位相同** 4180d8ca30183001。
// ========== 本次提交相对上一发(#103337)唯一的改动:packB 的 nt>=3 路径改 k 外层 ==========
// 【动机(§2.18.677 分类:工作集/驻留类 ⇒ 只能判题机定价,本文只做单变量正式发)】
//   原序 `for kb { for k { for tt } }` 对固定 (tt,k) 只取源矩阵的 64 B(8 列),下一个 k 就跳到
//   `ld*8 = 16384 B`(本题 `ld=2048`)⇒ **每条流的步长 = 一个 4 KB 页的 4 倍**,且每步只搬 64 B。
//   改成 **k 外层**后,对固定 (tt,k) 会连续读完 `jc..jc+nc-1` 的整行(nc=512 ⇒ **4 KB 连续**),
//   跨 k 才跳 ld*8 ⇒ 步数不变、**每步搬运量 ×64**。
// 【写入侧的必要补偿】单纯换序会把面板存储变成 32 B 的半行散写(line 要等下一个 k 才补全)
//   ⇒ 退回 RFO。所以同时把**相邻两个 k 配对**:q0 收 k 与 k+1 的 v0/u0、q1 收 v1/u1
//   ⇒ 每个 (k 对, 列组) 写出两条**完整 64 B line**。目标地址与 stock 逐字节一致
//   (列组 g 在 `Bpanel + g*KC*4`),因此输出逐位不变(下方两道闸门已验)。
// 【依据】本題 notes §七.2:`row-outer`(packB 内外层对调)在 mmml1k 上单独值 **packB 相位的 −23%**,
//   而**在当代基座(本题)从未测过**(历史 −4.2% 那条读数是**另一颗引擎**上的)。
//   本题的源步长(16 KB)是 mmml1k(8 KB)的两倍 ⇒ 按机理**预期这里只会更值**。
// 【预期(可证伪)】packB 占本题 5.4%(相位账)。若兑现 mmml1k 的 −23% ⇒ **−1.2%**
//   (mine 972.583 → ~960.8 ms);若源侧步长翻倍带来更大收益则更多。**判据:mine < 972.583217**
//   ⇒ 本刀迁移;若读数 ≥ 972.583217 ⇒ 不迁移,作废(最好件不变,口径不变)。
// ★ 与 §2.18.662 的关系:packB 的份额**不随 n 上升**(它是 O(n³) 里的一阶项,占比近似常数)
//   ⇒ 本条**不受"份额随 n 崩塌"那条否决**;真正的风险是判题机 L3=6 MB/本地 38 MB 的差异。
// 【正确性】n=512 对朴素 O(n³) 参考逐元素 int64 对拍 bad=0(fnv 与基座相同 d663725a9c5dcad5);
//   n=2048 与基座 FNV **逐位相同** 4180d8ca30183001。
// ========== 本次提交相对上一发(#103403)唯一的改动:packA 的 nt>=3 路径改 t 外层 ==========
// 【动机:与 #103403 同一个机理,只是搬到另一个函数】#103403 把 packB 的 nt>=3 路径改成
//   **k 外层**,让每个源行的 k 区间被连续读完(64 B/步 → 4 KB/步),判题机 **−2.61%**。
//   `packA` 的 nt>=3 路径是**完全同一形态**:`for r { for k { for t } }` —— 每步只在**每个源行**
//   上搬 **32 B**,而 nt=4 条流以 32 B 步长交错(下一个 k 就跳 ld*8 = 16 KB)。
// 【改动】把 **t 提到 k 外层**:对固定 (r, t) 把 `pc..pc+kc` 整段连续读完(kc=512 ⇒ 4 KB 连续)。
//   累加器**直接用 A 面板本身**(MC*KCP*8 = 197 KB,L1/L2 常驻)⇒ 不需要临时缓冲、不多一趟搬运:
//   `t == 0` 覆盖写、`t > 0` 面板自身做 RMW。**内核读 A 面板的布局与取数方式一字未动**
//   ⇒ 与 #103403 是"同一维度上的两处独立改动",互不包含。
// 【为什么不清扫本地】按 §2.18.677 的分类,本条属**工作集/驻留类**(收益来自访存步长)
//   ⇒ **本地不可裁决**,只做单变量正式发(本代理本轮已有两次本类被本地反向误判的实例)。
// 【预期(可证伪)】packA 占本题 2.06%(相位账)。#103403 在 packB(5.4%)上兑现 −2.61%
//   ⇒ 若 packA 同比例兑现 ⇒ **−1.0% ~ −1.3%**(mine 947.185576 → 935~938 ms)
//   ⇒ **若 ≤ 943.483256 则 mmml2k 严支直接达标**。判据:mine < 947.185576 ⇒ 本刀迁移;
//   若读数 ≥ 947.185576 ⇒ 不迁移,作废(最好件不变,口径不变)。
// ★ 反向风险(写明):本改动把 A 面板的写访问从 1 趟变成 4 趟(nt 趟 RMW),
//   若 packA 其实是 uop/端口受限而不是访存受限,则这一条会变慢。判题机裁决。
// 【正确性】n=512 对朴素 O(n³) 参考逐元素 int64 对拍 bad=0;
//   n=1024 / 2048 / 4096 与基座 FNV **逐位相同**(4180d8ca30183001 @2048)。
// ========== 本次提交相对上一发(#103416)唯一的改动:mikro 加 1 条 B 流软件预取 ==========
// 【改动】在 mikro(全部 4 个分支)的循环体尾部、B 的软件流水装载之前,加一条
//   `prefetcht0 3072(%[bp])`(bp 每 k 前进 32 B ⇒ 前瞻 96 个 k 步;kc=512 的一次调用共搬 16 KB,
//   这是"提前不到 1/5 次调用"的距离)。**只加 1 条 uop/迭代、语义完全不变。**
// 【依据:本账号自己在姊妹题 mmml4k 上的**判题机距离扫描**(全部是我方提交,逐条可查)】
//   无预取基线 **7344.889** → 256 B = 7293.532 (#103110) → 512 = 7289.038 (#103102)
//   → 1024 = 7284.413 (#103113) → 2048 = 7276.744 (#103120) → **3072 = 7271.724 (#103155,最优)**
//   → 4096 = 7275.034 (#103126) → 5120 = 7280.559 (#103158) → 8192 = 7290.807 (#103131)
//   ⇒ **单调下降到 3072 B 的平台,最好点相对无预取 −0.996%**;
//   而"两条预取"(§2.18.622 的反例)反而 **+0.9%**(#103139 = 7354.087、#103145 = 7355.537)
//   ⇒ **一条、放在循环尾部,是对应的形态。**
// 【为什么本题适用】本题 mikro(对手 #101976 的谱系)**从来没有预取**(本文件 grep `prefetcht0` 原为 0),
//   而 B 面板每个 4 列组在一次调用里被顺序搬 16 KB —— 与 mmml4k **同一个 mikro 函数**、同一种
//   访存形态(NR=4、每 k 32 B),故按 §2.18.348 直接移植。
// 【预期(可证伪)】若兑现 mmml4k 的 −0.996%(本题缓存更小,按理只会更有利)⇒
//   mine 946.694 → ~937.3 ms ⇒ **≤ 943.483256 ⇒ mmml2k 严支直接达标**。
//   判据:mine < 946.694073 ⇒ 迁移;否则作废(最好件与口径都不变)。
// 【为什么不用本地筛】本代理本轮已被本机反向误导两次(mmmd1k 预取距离:本地 −1.27% / 判题机 +0.20%;
//   mmml2k CUTL:本地 −2.19% / 判题机 +0.86%)⇒ **预取类一律只由判题机定价**(§2.18.677)。
// 【正确性】预取只是对读的提示 ⇒ 不改任何数值路径。越界安全性:`Bpanel` 按
//   `2*(NC/4+1)*KC*8+64` 个 int64 分配 ⇒ 最后一个列组之后仍有一整组(16 KB)余量 > 3072 B。
// ================
/* mmml: C = A*B mod 2^64, long long, FUNCTION-style C++ linkage.
   5-ALU-uop mikro (vpmuludq + vpaddq + vpmulld + vpaddd per k x row x 4 cols, no p5 uop)
   plus Strassen with fused operand combination and dual-destination stores
   (no extra memory).  MR=6 KC=512 MC=48 NC=512 CUTL=512 U=1 BSW=0 EXP=0 */
#include <immintrin.h>

/* Single-instruction 256-bit unaligned access, NO alignment precondition.
   g++-9 -O2 with no -march expands the unaligned 256-bit intrinsics into 2-3 uops, one
   of them a p5-only vinsertf128/vextractf128; these emit the single instruction the
   intrinsic is named for.  AT&T order is `vmovdqu src,dst`: the STORE is %0,%1 (ymm
   first, memory second).  A REVERSED store operand silently emits a LOAD instead --
   that is how this was first written and it produced wrong output with no diagnostic.
   volatile + "memory" is REQUIRED: a non-volatile asm with only an "m" input is pure
   and gets eliminated. */
#define LDQ(p) ({ __m256i _ldq_v; __asm__("vmovdqu %1,%0" : "=x"(_ldq_v) : "m"(*(const __m256i *)(p)) : "memory"); _ldq_v; })
#define STQ(p, v) __asm__ volatile("vmovdqu %0,%1" :: "x"(v), "m"(*(__m256i *)(p)) : "memory")
#include <stddef.h>
#pragma GCC optimize("O3","unroll-loops")
#pragma GCC push_options
#define KB_LK kc
#pragma GCC target("avx2")

typedef long long TE;

#define MR 6
#define KC 512
#define MC 48
#define NC 512
#define UU 1
#define CUTL 256

/* A-panel row stride PADDED by 8 uint64 (64 B).  With the natural stride KC*8 = 4096 B
   every one of the 6 rows of a micro-tile maps to the SAME L1 set (4096/64 = 64 sets
   apart, 64 sets in the cache) -- a textbook conflict.  +8 uint64 makes the stride
   4160 B = 65 lines, so consecutive rows land in consecutive sets. */
#define KCP (KC + 8)
static TE Apanel[(size_t)MC * KCP + 64] __attribute__((aligned(64)));
static TE Bpanel[(size_t)2 * (NC / 4 + 1) * KC * 8 + 64] __attribute__((aligned(64)));
static __m256i AC[12] __attribute__((aligned(64)));

/* op: 0 = store, 1 = add, 2 = sub;  c2 may be 0 (single destination) */
static inline void mikro(long cnt, const long long *ap, const long long *bp,
                        long long *c1, int ldc1, int op1,
                        long long *c2, int ldc2, int op2, int rows) {
  if (rows == 6) {
  __asm__ volatile(
    "test %[cnt], %[cnt]\n\t"
    "jle 2f\n\t"
    "vmovdqu 0(%[bp]), %%ymm12\n\t"
    "vpshufd $0xB1, %%ymm12, %%ymm13\n\t"
    "vbroadcastsd 0(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm0\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm1\n\t"
    "vbroadcastsd 4160(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm2\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm3\n\t"
    "vbroadcastsd 8320(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm4\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm5\n\t"
    "vbroadcastsd 12480(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm6\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm7\n\t"
    "vbroadcastsd 16640(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm8\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm9\n\t"
    "vbroadcastsd 20800(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm10\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm11\n\t"
    "add $8, %[ap]\n\t"
    "add $32, %[bp]\n\t"
    "dec %[cnt]\n\t"
    "jle 2f\n\t"
    "vmovdqu 0(%[bp]), %%ymm12\n\t"
    "1:\n\t"
    "vpshufd $0xB1, %%ymm12, %%ymm13\n\t"
    "vbroadcastsd 0(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm0, %%ymm0\n\t"
    "vpaddd %%ymm14, %%ymm1, %%ymm1\n\t"
    "vbroadcastsd 4160(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm2, %%ymm2\n\t"
    "vpaddd %%ymm14, %%ymm3, %%ymm3\n\t"
    "vbroadcastsd 8320(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm4, %%ymm4\n\t"
    "vpaddd %%ymm14, %%ymm5, %%ymm5\n\t"
    "vbroadcastsd 12480(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm6, %%ymm6\n\t"
    "vpaddd %%ymm14, %%ymm7, %%ymm7\n\t"
    "vbroadcastsd 16640(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm8, %%ymm8\n\t"
    "vpaddd %%ymm14, %%ymm9, %%ymm9\n\t"
    "vbroadcastsd 20800(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm10, %%ymm10\n\t"
    "vpaddd %%ymm14, %%ymm11, %%ymm11\n\t"
    "prefetcht0 3072(%[bp])\n\t"
    "vmovdqu 32(%[bp]), %%ymm12\n\t"
    "add $8, %[ap]\n\t"
    "add $32, %[bp]\n\t"
    "dec %[cnt]\n\t"
    "jnz 1b\n\t"
    "2:\n\t"
    "vmovdqa %%ymm0, 0(%[ac])\n\t"
    "vmovdqa %%ymm1, 32(%[ac])\n\t"
    "vmovdqa %%ymm2, 64(%[ac])\n\t"
    "vmovdqa %%ymm3, 96(%[ac])\n\t"
    "vmovdqa %%ymm4, 128(%[ac])\n\t"
    "vmovdqa %%ymm5, 160(%[ac])\n\t"
    "vmovdqa %%ymm6, 192(%[ac])\n\t"
    "vmovdqa %%ymm7, 224(%[ac])\n\t"
    "vmovdqa %%ymm8, 256(%[ac])\n\t"
    "vmovdqa %%ymm9, 288(%[ac])\n\t"
    "vmovdqa %%ymm10, 320(%[ac])\n\t"
    "vmovdqa %%ymm11, 352(%[ac])\n\t"
    ""
    : [ap] "+r"(ap), [bp] "+r"(bp), [cnt] "+r"(cnt)
    : [ac] "r"(&AC[0])
    : "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6", "ymm7",
      "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15",
      "cc", "memory");
  } else
  if (rows == 4) {
  __asm__ volatile(
    "test %[cnt], %[cnt]\n\t"
    "jle 2f\n\t"
    "vmovdqu 0(%[bp]), %%ymm12\n\t"
    "vpshufd $0xB1, %%ymm12, %%ymm13\n\t"
    "vbroadcastsd 0(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm0\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm1\n\t"
    "vbroadcastsd 4160(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm2\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm3\n\t"
    "vbroadcastsd 8320(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm4\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm5\n\t"
    "vbroadcastsd 12480(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm6\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm7\n\t"
    "add $8, %[ap]\n\t"
    "add $32, %[bp]\n\t"
    "dec %[cnt]\n\t"
    "jle 2f\n\t"
    "vmovdqu 0(%[bp]), %%ymm12\n\t"
    "1:\n\t"
    "vpshufd $0xB1, %%ymm12, %%ymm13\n\t"
    "vbroadcastsd 0(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm0, %%ymm0\n\t"
    "vpaddd %%ymm14, %%ymm1, %%ymm1\n\t"
    "vbroadcastsd 4160(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm2, %%ymm2\n\t"
    "vpaddd %%ymm14, %%ymm3, %%ymm3\n\t"
    "vbroadcastsd 8320(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm4, %%ymm4\n\t"
    "vpaddd %%ymm14, %%ymm5, %%ymm5\n\t"
    "vbroadcastsd 12480(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm6, %%ymm6\n\t"
    "vpaddd %%ymm14, %%ymm7, %%ymm7\n\t"
    "prefetcht0 3072(%[bp])\n\t"
    "vmovdqu 32(%[bp]), %%ymm12\n\t"
    "add $8, %[ap]\n\t"
    "add $32, %[bp]\n\t"
    "dec %[cnt]\n\t"
    "jnz 1b\n\t"
    "2:\n\t"
    "vmovdqa %%ymm0, 0(%[ac])\n\t"
    "vmovdqa %%ymm1, 32(%[ac])\n\t"
    "vmovdqa %%ymm2, 64(%[ac])\n\t"
    "vmovdqa %%ymm3, 96(%[ac])\n\t"
    "vmovdqa %%ymm4, 128(%[ac])\n\t"
    "vmovdqa %%ymm5, 160(%[ac])\n\t"
    "vmovdqa %%ymm6, 192(%[ac])\n\t"
    "vmovdqa %%ymm7, 224(%[ac])\n\t"
    ""
    : [ap] "+r"(ap), [bp] "+r"(bp), [cnt] "+r"(cnt)
    : [ac] "r"(&AC[0])
    : "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6", "ymm7",
      "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15",
      "cc", "memory");
  } else
  if (rows == 2) {
  __asm__ volatile(
    "test %[cnt], %[cnt]\n\t"
    "jle 2f\n\t"
    "vmovdqu 0(%[bp]), %%ymm12\n\t"
    "vpshufd $0xB1, %%ymm12, %%ymm13\n\t"
    "vbroadcastsd 0(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm0\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm1\n\t"
    "vbroadcastsd 4160(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm2\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm3\n\t"
    "add $8, %[ap]\n\t"
    "add $32, %[bp]\n\t"
    "dec %[cnt]\n\t"
    "jle 2f\n\t"
    "vmovdqu 0(%[bp]), %%ymm12\n\t"
    "1:\n\t"
    "vpshufd $0xB1, %%ymm12, %%ymm13\n\t"
    "vbroadcastsd 0(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm0, %%ymm0\n\t"
    "vpaddd %%ymm14, %%ymm1, %%ymm1\n\t"
    "vbroadcastsd 4160(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm2, %%ymm2\n\t"
    "vpaddd %%ymm14, %%ymm3, %%ymm3\n\t"
    "prefetcht0 3072(%[bp])\n\t"
    "vmovdqu 32(%[bp]), %%ymm12\n\t"
    "add $8, %[ap]\n\t"
    "add $32, %[bp]\n\t"
    "dec %[cnt]\n\t"
    "jnz 1b\n\t"
    "2:\n\t"
    "vmovdqa %%ymm0, 0(%[ac])\n\t"
    "vmovdqa %%ymm1, 32(%[ac])\n\t"
    "vmovdqa %%ymm2, 64(%[ac])\n\t"
    "vmovdqa %%ymm3, 96(%[ac])\n\t"
    ""
    : [ap] "+r"(ap), [bp] "+r"(bp), [cnt] "+r"(cnt)
    : [ac] "r"(&AC[0])
    : "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6", "ymm7",
      "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15",
      "cc", "memory");
  } else
  {
  __asm__ volatile(
    "vpxor %%ymm0, %%ymm0, %%ymm0\n\t"
    "vmovdqa %%ymm0, %%ymm1\n\t"
    "vmovdqa %%ymm0, %%ymm2\n\t"
    "vmovdqa %%ymm0, %%ymm3\n\t"
    "vmovdqa %%ymm0, %%ymm4\n\t"
    "vmovdqa %%ymm0, %%ymm5\n\t"
    "vmovdqa %%ymm0, %%ymm6\n\t"
    "vmovdqa %%ymm0, %%ymm7\n\t"
    "vmovdqa %%ymm0, %%ymm8\n\t"
    "vmovdqa %%ymm0, %%ymm9\n\t"
    "vmovdqa %%ymm0, %%ymm10\n\t"
    "vmovdqa %%ymm0, %%ymm11\n\t"
    "test %[cnt], %[cnt]\n\t"
    "jle 2f\n\t"
    "vmovdqu 0(%[bp]), %%ymm12\n\t"
    "1:\n\t"
    "vpshufd $0xB1, %%ymm12, %%ymm13\n\t"
    "vbroadcastsd 0(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm0, %%ymm0\n\t"
    "vpaddd %%ymm14, %%ymm1, %%ymm1\n\t"
    "vbroadcastsd 4160(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm2, %%ymm2\n\t"
    "vpaddd %%ymm14, %%ymm3, %%ymm3\n\t"
    "vbroadcastsd 8320(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm4, %%ymm4\n\t"
    "vpaddd %%ymm14, %%ymm5, %%ymm5\n\t"
    "vbroadcastsd 12480(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm6, %%ymm6\n\t"
    "vpaddd %%ymm14, %%ymm7, %%ymm7\n\t"
    "vbroadcastsd 16640(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm8, %%ymm8\n\t"
    "vpaddd %%ymm14, %%ymm9, %%ymm9\n\t"
    "vbroadcastsd 20800(%[ap]), %%ymm14\n\t"
    "vpmuludq %%ymm14, %%ymm12, %%ymm15\n\t"
    "vpmulld %%ymm13, %%ymm14, %%ymm14\n\t"
    "vpaddq %%ymm15, %%ymm10, %%ymm10\n\t"
    "vpaddd %%ymm14, %%ymm11, %%ymm11\n\t"
    "prefetcht0 3072(%[bp])\n\t"
    "vmovdqu 32(%[bp]), %%ymm12\n\t"
    "add $8, %[ap]\n\t"
    "add $32, %[bp]\n\t"
    "dec %[cnt]\n\t"
    "jnz 1b\n\t"
    "2:\n\t"
    "vmovdqa %%ymm0, 0(%[ac])\n\t"
    "vmovdqa %%ymm1, 32(%[ac])\n\t"
    "vmovdqa %%ymm2, 64(%[ac])\n\t"
    "vmovdqa %%ymm3, 96(%[ac])\n\t"
    "vmovdqa %%ymm4, 128(%[ac])\n\t"
    "vmovdqa %%ymm5, 160(%[ac])\n\t"
    "vmovdqa %%ymm6, 192(%[ac])\n\t"
    "vmovdqa %%ymm7, 224(%[ac])\n\t"
    "vmovdqa %%ymm8, 256(%[ac])\n\t"
    "vmovdqa %%ymm9, 288(%[ac])\n\t"
    "vmovdqa %%ymm10, 320(%[ac])\n\t"
    "vmovdqa %%ymm11, 352(%[ac])\n\t"
    ""
    : [ap] "+r"(ap), [bp] "+r"(bp), [cnt] "+r"(cnt)
    : [ac] "r"(&AC[0])
    : "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6", "ymm7",
      "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15",
      "cc", "memory");
  }
  if (rows > 0) {
    __m256i lo = AC[0], hi = AC[1];
    __m256i y = _mm256_add_epi32(hi, _mm256_srli_epi64(hi, 32));
    __m256i res = _mm256_add_epi64(lo, _mm256_slli_epi64(y, 32));
    long long *cp = c1 + (size_t)0 * ldc1;
    if (op1 == 0) STQ((__m256i *)cp, res);
    else if (op1 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
    else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res));
    if (c2) { cp = c2 + (size_t)0 * ldc2;
      if (op2 == 0) STQ((__m256i *)cp, res);
      else if (op2 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
      else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res)); } }
  if (rows > 1) {
    __m256i lo = AC[2], hi = AC[3];
    __m256i y = _mm256_add_epi32(hi, _mm256_srli_epi64(hi, 32));
    __m256i res = _mm256_add_epi64(lo, _mm256_slli_epi64(y, 32));
    long long *cp = c1 + (size_t)1 * ldc1;
    if (op1 == 0) STQ((__m256i *)cp, res);
    else if (op1 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
    else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res));
    if (c2) { cp = c2 + (size_t)1 * ldc2;
      if (op2 == 0) STQ((__m256i *)cp, res);
      else if (op2 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
      else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res)); } }
  if (rows > 2) {
    __m256i lo = AC[4], hi = AC[5];
    __m256i y = _mm256_add_epi32(hi, _mm256_srli_epi64(hi, 32));
    __m256i res = _mm256_add_epi64(lo, _mm256_slli_epi64(y, 32));
    long long *cp = c1 + (size_t)2 * ldc1;
    if (op1 == 0) STQ((__m256i *)cp, res);
    else if (op1 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
    else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res));
    if (c2) { cp = c2 + (size_t)2 * ldc2;
      if (op2 == 0) STQ((__m256i *)cp, res);
      else if (op2 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
      else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res)); } }
  if (rows > 3) {
    __m256i lo = AC[6], hi = AC[7];
    __m256i y = _mm256_add_epi32(hi, _mm256_srli_epi64(hi, 32));
    __m256i res = _mm256_add_epi64(lo, _mm256_slli_epi64(y, 32));
    long long *cp = c1 + (size_t)3 * ldc1;
    if (op1 == 0) STQ((__m256i *)cp, res);
    else if (op1 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
    else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res));
    if (c2) { cp = c2 + (size_t)3 * ldc2;
      if (op2 == 0) STQ((__m256i *)cp, res);
      else if (op2 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
      else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res)); } }
  if (rows > 4) {
    __m256i lo = AC[8], hi = AC[9];
    __m256i y = _mm256_add_epi32(hi, _mm256_srli_epi64(hi, 32));
    __m256i res = _mm256_add_epi64(lo, _mm256_slli_epi64(y, 32));
    long long *cp = c1 + (size_t)4 * ldc1;
    if (op1 == 0) STQ((__m256i *)cp, res);
    else if (op1 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
    else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res));
    if (c2) { cp = c2 + (size_t)4 * ldc2;
      if (op2 == 0) STQ((__m256i *)cp, res);
      else if (op2 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
      else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res)); } }
  if (rows > 5) {
    __m256i lo = AC[10], hi = AC[11];
    __m256i y = _mm256_add_epi32(hi, _mm256_srli_epi64(hi, 32));
    __m256i res = _mm256_add_epi64(lo, _mm256_slli_epi64(y, 32));
    long long *cp = c1 + (size_t)5 * ldc1;
    if (op1 == 0) STQ((__m256i *)cp, res);
    else if (op1 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
    else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res));
    if (c2) { cp = c2 + (size_t)5 * ldc2;
      if (op2 == 0) STQ((__m256i *)cp, res);
      else if (op2 == 1) STQ((__m256i *)cp, _mm256_add_epi64(LDQ((const __m256i *)cp), res));
      else STQ((__m256i *)cp, _mm256_sub_epi64(LDQ((const __m256i *)cp), res)); } }
}

struct Opd {                 /* value(i,j) = sum_t s[t]*p[(r[t]+i)*ld + c[t]+j]  (nt <= 4) */
  const long long *p; int ld; int nt; int r[8], c[8], s[8];
};
static inline Opd oq(const Opd *o, int qi, int qj, int h) {
  Opd d = *o;
  for (int t = 0; t < o->nt; t++) { d.r[t] += qi * h; d.c[t] += qj * h; }
  return d;
}
static inline Opd oadd(const Opd *a, const Opd *b, int sgn) {
  Opd d = *a;
  for (int t = 0; t < b->nt; t++) { d.r[d.nt] = b->r[t]; d.c[d.nt] = b->c[t]; d.s[d.nt] = b->s[t] < 0 ? -sgn : sgn; d.nt++; }
  return d;
}
static inline Opd o1(const long long *p, int ld) {
  Opd d; d.p = p; d.ld = ld; d.nt = 1; d.r[0] = 0; d.c[0] = 0; d.s[0] = 1; return d;
}

/* A panel: rows [ic, ic+mc), k in [pc, pc+kc) of the operand */
static void packA(const Opd *o, int ic, int mc, int pc, int kc) {
  const int nt = o->nt;
  const long long *P = o->p; const int ld = o->ld;
  for (int r = 0; r < MC; r++) {
    long long *p = Apanel + (size_t)r * KCP;
    int k = 0;
    if (r < mc) {
      const long long *s0 = P + (size_t)(o->r[0] + ic + r) * ld + o->c[0] + pc;
      if (nt == 1 || nt == 2) {
        const long long *s1 = (nt == 2) ? P + (size_t)(o->r[1] + ic + r) * ld + o->c[1] + pc : 0;
        int sgn1 = (nt == 2) ? o->s[1] : 1;
        for (; k + 4 <= kc; k += 4) {
          __m256i v = LDQ((const __m256i *)(s0 + k));
          if (nt == 2) {
            __m256i w = LDQ((const __m256i *)(s1 + k));
            v = (sgn1 > 0) ? _mm256_add_epi64(v, w) : _mm256_sub_epi64(v, w);
          }
        STQ((__m256i *)p, v);
        p += 4;
        }
        for (; k < kc; k++) { long long v = s0[k];
          if (nt == 2) v = (sgn1 > 0) ? v + s1[k] : v - s1[k];
          *p++ = (v); }
      } else {
        /* ===== mdl_: nt >= 3 的 packA 改 t 外层(源侧连续读)=====
           原序 `for k { for t }` 每步只在**每个源行**上搬 32 B,而 nt 条流以 32 B 步长交错
           —— 与 packB 修复前的形态完全相同(那次改 k 外层换来 −2.61%)。
           现在对固定 (r, t) 把 `pc..pc+kc` 整段**连续读完**(kc=512 ⇒ 4 KB 连续)。
           累加器直接用 A 面板本身(它在 L1/L2 常驻,MC*KCP*8 = 197 KB)⇒ 不需要临时缓冲、
           也不多一趟搬运:t=0 覆盖写,t>0 面板自身做 RMW。内核读 A 面板的方式完全不变。 */
        long long *pp = p;
        for (int t = 0; t < nt; t++) {
          const long long *so = P + (size_t)(o->r[t] + ic + r) * ld + o->c[t] + pc;
          int sgn = (o->s[t] < 0);
          int kk = 0;
          if (t == 0) {
            for (; kk + 4 <= kc; kk += 4) {
              __m256i w = LDQ((const __m256i *)(so + kk));
              if (sgn) w = _mm256_sub_epi64(_mm256_setzero_si256(), w);
              STQ((__m256i *)(pp + kk), w);
            }
            for (; kk < kc; kk++) pp[kk] = sgn ? -so[kk] : so[kk];
          } else {
            for (; kk + 4 <= kc; kk += 4) {
              __m256i w = LDQ((const __m256i *)(so + kk));
              __m256i v = LDQ((const __m256i *)(pp + kk));
              STQ((__m256i *)(pp + kk), sgn ? _mm256_sub_epi64(v, w) : _mm256_add_epi64(v, w));
            }
            for (; kk < kc; kk++) pp[kk] = sgn ? pp[kk] - so[kk] : pp[kk] + so[kk];
          }
        }
        k = kc;
      }
    }
    p += KC - k;   /* k >= kc is never read: mikro runs kc steps */
  }
}

static void packB(const Opd *o, int jc, int nc, int pc, int kc) {
  const int nt = o->nt;
  const long long *P = o->p; const int ld = o->ld;
  const int jbn = (nc + 3) & ~3;   /* mikro reads only jg < nc, step 4 */
  int jb = 0;
  /* PACK8: 8 columns per k.  The 4-column form loads 32 B at a stride of
     ld*8 (4 KB or more), so every load is a distinct, half-used line and the
     hardware prefetcher cannot see the stream.  Reading jb and jb+4 together
     makes each k one FULL 64 B line, halving the line count and the DRAM
     traffic.  All 8 columns come from one line because jc+jb is a multiple of
     8 (jb steps by 8) and P is 4096-byte aligned.  Byte-identical: the two
     stores write exactly the values the 4-column loop wrote to the same
     addresses. */
  if (nt <= 2) {
    for (; jb + 8 <= nc; jb += 8) {
      long long *q0 = Bpanel + (size_t)(jb / 4) * KC * 4;
      long long *q1 = q0 + (size_t)KC * 4;
      const long long *sq  = P + (size_t)o->r[0] * ld + o->c[0] + jc + jb;
      const long long *sq1 = (nt >= 2) ? P + (size_t)o->r[1] * ld + o->c[1] + jc + jb : 0;
      int k = 0;
      for (; k + 2 <= kc; k += 2) {
        const long long *a0 = sq + (size_t)(pc + k) * ld;
        const long long *a1 = a0 + ld;
        __m256i v0 = LDQ((const __m256i *)a0), u0 = LDQ((const __m256i *)(a0 + 4));
        __m256i v1 = LDQ((const __m256i *)a1), u1 = LDQ((const __m256i *)(a1 + 4));
        if (nt == 2) {
          const long long *b0 = sq1 + (size_t)(pc + k) * ld;
          const long long *b1 = b0 + ld;
          __m256i w0 = LDQ((const __m256i *)b0), x0 = LDQ((const __m256i *)(b0 + 4));
          __m256i w1 = LDQ((const __m256i *)b1), x1 = LDQ((const __m256i *)(b1 + 4));
          if (o->s[1] > 0) { v0 = _mm256_add_epi64(v0, w0); u0 = _mm256_add_epi64(u0, x0);
                             v1 = _mm256_add_epi64(v1, w1); u1 = _mm256_add_epi64(u1, x1); }
          else             { v0 = _mm256_sub_epi64(v0, w0); u0 = _mm256_sub_epi64(u0, x0);
                             v1 = _mm256_sub_epi64(v1, w1); u1 = _mm256_sub_epi64(u1, x1); }
        }
        _mm256_store_si256((__m256i *)(q0 + (size_t)k * 4), v0);
        _mm256_store_si256((__m256i *)(q1 + (size_t)k * 4), u0);
        _mm256_store_si256((__m256i *)(q0 + (size_t)(k + 1) * 4), v1);
        _mm256_store_si256((__m256i *)(q1 + (size_t)(k + 1) * 4), u1);
      }
      for (; k < kc; k++) {
        const long long *a = sq + (size_t)(pc + k) * ld;
        __m256i v = LDQ((const __m256i *)a);
        __m256i u = LDQ((const __m256i *)(a + 4));
        if (nt == 2) {
          const long long *b = sq1 + (size_t)(pc + k) * ld;
          __m256i w = LDQ((const __m256i *)b);
          __m256i x = LDQ((const __m256i *)(b + 4));
          if (o->s[1] > 0) { v = _mm256_add_epi64(v, w); u = _mm256_add_epi64(u, x); }
          else             { v = _mm256_sub_epi64(v, w); u = _mm256_sub_epi64(u, x); }
        }
        _mm256_store_si256((__m256i *)(q0 + (size_t)k * 4), v);
        _mm256_store_si256((__m256i *)(q1 + (size_t)k * 4), u);
      }
    }
  }
  /* ===== mdl_: nt >= 3 的 packB 改成 k 外层(源侧连续读)=====
     原序 `for kb { for k { for tt } }` 对固定 (tt, k) 只读源矩阵的 64 B(8 列),
     下一个 k 就跳到 ld*8 = 16 KB 之外 ⇒ 每条流的步长等于一个 4 KB 页的 4 倍。
     改成 k 外层后,对固定 (tt, k) 会连续读完 jc..jc+nc-1 的**整行**(nc=512 ⇒ 4 KB 连续),
     跨 k 才跳 ld*8 ⇒ 步数不变但每步搬运量 ×64。
     ★ 写入侧补偿:把相邻两个 k 配对,使面板存储仍是整条 64 B line
     (q0 收 k 与 k+1 的 v0/u0,q1 收 v1/u1)——否则 32 B 的散写会退回 RFO 半行写。
     目标地址与 stock 逐字节一致(列组 g 在 Bpanel + g*KC*4),输出因此不变。 */
  if (nt >= 3 && (nc & 7) == 0) {
    int k = 0;
    for (; k + 2 <= kc; k += 2) {
      for (int jb2 = 0; jb2 < nc; jb2 += 8) {
        long long *q0 = Bpanel + (size_t)(jb2 / 4) * KC * 4;
        long long *q1 = q0 + (size_t)KC * 4;
        __m256i v0 = _mm256_setzero_si256(), v1 = _mm256_setzero_si256();
        __m256i u0 = _mm256_setzero_si256(), u1 = _mm256_setzero_si256();
        for (int tt = 0; tt < nt; tt++) {
          const long long *sp = P + (size_t)(o->r[tt] + pc + k) * ld + o->c[tt] + jc + jb2;
          __m256i w0 = LDQ((const __m256i *)sp), w1 = LDQ((const __m256i *)(sp + 4));
          __m256i x0 = LDQ((const __m256i *)(sp + ld)), x1 = LDQ((const __m256i *)(sp + ld + 4));
          if (o->s[tt] < 0) { v0=_mm256_sub_epi64(v0,w0); v1=_mm256_sub_epi64(v1,w1);
                              u0=_mm256_sub_epi64(u0,x0); u1=_mm256_sub_epi64(u1,x1); }
          else              { v0=_mm256_add_epi64(v0,w0); v1=_mm256_add_epi64(v1,w1);
                              u0=_mm256_add_epi64(u0,x0); u1=_mm256_add_epi64(u1,x1); }
        }
        _mm256_store_si256((__m256i *)(q0 + (size_t)k * 4), v0);
        _mm256_store_si256((__m256i *)(q0 + (size_t)(k + 1) * 4), u0);
        _mm256_store_si256((__m256i *)(q1 + (size_t)k * 4), v1);
        _mm256_store_si256((__m256i *)(q1 + (size_t)(k + 1) * 4), u1);
      }
    }
    for (; k < kc; k++) {
      for (int jb2 = 0; jb2 < nc; jb2 += 8) {
        long long *q0 = Bpanel + (size_t)(jb2 / 4) * KC * 4;
        long long *q1 = q0 + (size_t)KC * 4;
        __m256i v0 = _mm256_setzero_si256(), v1 = _mm256_setzero_si256();
        for (int tt = 0; tt < nt; tt++) {
          const long long *sp = P + (size_t)(o->r[tt] + pc + k) * ld + o->c[tt] + jc + jb2;
          __m256i w0 = LDQ((const __m256i *)sp), w1 = LDQ((const __m256i *)(sp + 4));
          if (o->s[tt] < 0) { v0=_mm256_sub_epi64(v0,w0); v1=_mm256_sub_epi64(v1,w1); }
          else              { v0=_mm256_add_epi64(v0,w0); v1=_mm256_add_epi64(v1,w1); }
        }
        _mm256_store_si256((__m256i *)(q0 + (size_t)k * 4), v0);
        _mm256_store_si256((__m256i *)(q1 + (size_t)k * 4), v1);
      }
    }
    jb = jbn;
  }
  for (; jb < jbn; jb += 4) {
    long long *q = Bpanel + (size_t)(jb / 4) * KC * 4;
    int full = (jb + 4 <= nc);
    int lim = nc - jb; if (lim > 4) lim = 4; if (lim < 0) lim = 0;
    const long long *sq = P + (size_t)o->r[0] * ld + o->c[0] + jc + jb;
    const long long *sq1 = (nt >= 2) ? P + (size_t)o->r[1] * ld + o->c[1] + jc + jb : 0;
    if (full && nt <= 2) {
      for (int k = 0; k < kc; k++) {
        __m256i v = LDQ((const __m256i *)(sq + (size_t)(pc + k) * ld));
        if (nt == 2) {
          __m256i w = LDQ((const __m256i *)(sq1 + (size_t)(pc + k) * ld));
          v = (o->s[1] > 0) ? _mm256_add_epi64(v, w) : _mm256_sub_epi64(v, w);
        }
        _mm256_store_si256((__m256i *)(q + (size_t)k * 4), v);
      }
    } else if (full) {
      int wide = (jb + 8 <= nc);
      if (wide) {
        long long *q1 = q + (size_t)KC * 4;
        for (int kb = 0; kb < kc; kb += (KB_LK)) {
          int ke = kb + (KB_LK); if (ke > kc) ke = kc;
          for (int k = kb; k < ke; k++) {
            __m256i v0 = _mm256_setzero_si256(), v1 = _mm256_setzero_si256();
            for (int tt = 0; tt < nt; tt++) {
              const long long *sp = P + (size_t)(o->r[tt] + pc + k) * ld + o->c[tt] + jc + jb;
              __m256i w0 = LDQ((const __m256i *)sp), w1 = LDQ((const __m256i *)(sp + 4));
              if (o->s[tt] < 0) { v0 = _mm256_sub_epi64(v0, w0); v1 = _mm256_sub_epi64(v1, w1); }
              else              { v0 = _mm256_add_epi64(v0, w0); v1 = _mm256_add_epi64(v1, w1); }
            }
            _mm256_store_si256((__m256i *)(q + (size_t)k * 4), v0);
            _mm256_store_si256((__m256i *)(q1 + (size_t)k * 4), v1);
          }
        }
        jb += 4;
      } else {
      for (int k = 0; k < kc; k++) {
        __m256i v = _mm256_setzero_si256();
        for (int tt = 0; tt < nt; tt++) {
          __m256i w = LDQ((const __m256i *)(P + (size_t)(o->r[tt] + pc + k) * ld + o->c[tt] + jc + jb));
          v = (o->s[tt] < 0) ? _mm256_sub_epi64(v, w) : _mm256_add_epi64(v, w);
        }
        _mm256_store_si256((__m256i *)(q + (size_t)k * 4), v);
      }
      }
    } else {
      for (int k = 0; k < KC; k++) {
        long long t[4] = {0, 0, 0, 0};
        if (k < kc) {
          int ncol = full ? 4 : lim;
          for (int c = 0; c < ncol; c++) {
            long long v = 0;
            for (int tt = 0; tt < nt; tt++)
              v += o->s[tt] < 0 ? -P[(size_t)(o->r[tt] + pc + k) * ld + o->c[tt] + jc + jb + c]
                                :  P[(size_t)(o->r[tt] + pc + k) * ld + o->c[tt] + jc + jb + c];
            t[c] = v;
          }
        }
        __m256i v = LDQ((const __m256i *)t);
        _mm256_store_si256((__m256i *)(q + (size_t)k * 4), v);
      }
    }
  }
}


static inline int effop(int base, int term) {   /* term: 1 = +M, 2 = -M ; result: 0 store 1 add 2 sub */
  if (base == 0) return term == 1 ? 0 : 2;
  if (base == 1) return term == 2 ? 2 : 1;
  return term == 2 ? 1 : 2;
}

/* C1 op1 (+- second destination C2 op2, either may be absent) = Ao * Bo, block size n */
static void leaf(int n, const Opd *Ao, const Opd *Bo,
                 long long *C1, int ldc1, int op1, long long *C2, int ldc2, int op2) {
  int nb4 = n - (n & 3);
  for (int jc = 0; jc < nb4; jc += NC) {
    int nc = (nb4 - jc < NC) ? (nb4 - jc) : NC;
    for (int pc = 0; pc < n; pc += KC) {
      int kc = (n - pc < KC) ? (n - pc) : KC;
      packB(Bo, jc, nc, pc, kc);
      int e1 = (pc == 0) ? op1 : (op1 == 2 ? 2 : 1);
      int e2 = (pc == 0) ? op2 : (op2 == 2 ? 2 : 1);
      for (int ic = 0; ic < n; ic += MC) {
        int mc = (n - ic < MC) ? (n - ic) : MC;
        packA(Ao, ic, mc, pc, kc);
        for (int ir = 0; ir < mc; ir += MR) {
          int rows = (mc - ir < MR) ? (mc - ir) : MR;
          const long long *ap = Apanel + (size_t)ir * KCP * 1;
          for (int jg = 0; jg < nc; jg += 4) {
            const long long *bp = Bpanel + (size_t)(jg / 4) * KC * 4;
            mikro(kc / UU, ap, bp, C1 + (size_t)(ic + ir) * ldc1 + jc + jg, ldc1, e1,
                  C2 ? C2 + (size_t)(ic + ir) * ldc2 + jc + jg : 0, ldc2, e2, rows);
          }
        }
      }
    }
  }
  for (int j = nb4; j < n; j++)
    for (int i = 0; i < n; i++) {
      unsigned long long acc = 0;
      for (int k = 0; k < n; k++) {
        long long av = 0, bv = 0;
        for (int t = 0; t < Ao->nt; t++)
          av += Ao->s[t] < 0 ? -Ao->p[(size_t)(Ao->r[t] + i) * Ao->ld + Ao->c[t] + k]
                             :  Ao->p[(size_t)(Ao->r[t] + i) * Ao->ld + Ao->c[t] + k];
        for (int t = 0; t < Bo->nt; t++)
          bv += Bo->s[t] < 0 ? -Bo->p[(size_t)(Bo->r[t] + k) * Bo->ld + Bo->c[t] + j]
                             :  Bo->p[(size_t)(Bo->r[t] + k) * Bo->ld + Bo->c[t] + j];
        acc += (unsigned long long)av * (unsigned long long)bv;
      }
      long long *cp = C1 + (size_t)i * ldc1 + j;
      if (op1 == 0) *cp = (long long)acc;
      else if (op1 == 1) *cp += (long long)acc;
      else *cp -= (long long)acc;
      if (C2) {
        long long *cq = C2 + (size_t)i * ldc2 + j;
        if (op2 == 0) *cq = (long long)acc;
        else if (op2 == 1) *cq += (long long)acc;
        else *cq -= (long long)acc;
      }
    }
}

/* recursive Strassen: C1 op1 op= Ao*Bo  and  C2 op2 op= Ao*Bo */
/* Scratch: one h*h product block per recursion level, carved from the tail of the
   buffer.  Only the levels that actually recurse consume space, so a walk of the
   recursion computes exactly the bytes matrix_multiply() hands us below. */
static long long g_scratch[6u * 1024u * 1024u] __attribute__((aligned(64)));

/* dst op= sign * T   (op: 0 store, 1 add, 2 sub) */
static void cmb(long long *dst, int ldd, const long long *T, int ldt, int h, int sign, int op) {
  __m256i sgn = _mm256_set1_epi64x(sign < 0 ? -1 : 0);
  for (int i = 0; i < h; i++) {
    const long long *t = T + (size_t)i * ldt;
    long long *c = dst + (size_t)i * ldd;
    int j = 0;
    if (op == 0) {
      for (; j + 4 <= h; j += 4) {
        __m256i v = LDQ((const __m256i *)(t + j));
        if (sign < 0) v = _mm256_sub_epi64(_mm256_setzero_si256(), v);
        STQ((__m256i *)(c + j), v);
      }
      for (; j < h; j++) c[j] = sign < 0 ? -t[j] : t[j];
    } else {
      for (; j + 4 <= h; j += 4) {
        __m256i v = LDQ((const __m256i *)(t + j));
        __m256i w = LDQ((const __m256i *)(c + j));
        if ((op == 1) != (sign < 0)) STQ((__m256i *)(c + j), _mm256_add_epi64(w, v));
        else                         STQ((__m256i *)(c + j), _mm256_sub_epi64(w, v));
      }
      for (; j < h; j++) { if ((op == 1) != (sign < 0)) c[j] += t[j]; else c[j] -= t[j]; }
    }
  }
}

/* A virtual 512 operand with three or four terms is reused by seven
   grandchildren. Form it once so their packers read one dense source. */
static long long matA512[512u*512u] __attribute__((aligned(64)));
static long long matB512[512u*512u] __attribute__((aligned(64)));
static void materialize512(const Opd *o, long long *dst) {
  for (int i=0; i<512; ++i) for (int j=0; j<512; j+=4) {
    __m256i v = _mm256_setzero_si256();
    for (int t=0; t<o->nt; ++t) {
      const long long *p = o->p + (size_t)(o->r[t]+i)*o->ld + o->c[t]+j;
      __m256i w = LDQ((const __m256i*)p);
      v = o->s[t] < 0 ? _mm256_sub_epi64(v,w) : _mm256_add_epi64(v,w);
    }
    STQ((__m256i*)(dst+(size_t)i*512+j),v);
  }
}

/* single-destination recursive Strassen: d op= Ao*Bo, with T a scratch block of h*h */
static void mmxR(int n, const Opd *Ao, const Opd *Bo, long long *d, int ld, int op,
                 long long *T) {
  if (n == 512 && (Ao->nt > 2 || Bo->nt > 2)) {
    Opd da=*Ao, db=*Bo;
    if (Ao->nt > 2) { materialize512(Ao,matA512); da=o1(matA512,512); }
    if (Bo->nt > 2) { materialize512(Bo,matB512); db=o1(matB512,512); }
    mmxR(n,&da,&db,d,ld,op,T);
    return;
  }
  if (n <= CUTL || (n & 1) || Ao->nt > 4 || Bo->nt > 4) {
    leaf(n, Ao, Bo, d, ld, op, 0, 0, 0); return; }
  const int h = n >> 1;
  Opd A11 = oq(Ao, 0, 0, h), A12 = oq(Ao, 0, 1, h), A21 = oq(Ao, 1, 0, h), A22 = oq(Ao, 1, 1, h);
  Opd B11 = oq(Bo, 0, 0, h), B12 = oq(Bo, 0, 1, h), B21 = oq(Bo, 1, 0, h), B22 = oq(Bo, 1, 1, h);
  long long *Q[4];
  Q[0] = d; Q[1] = d + h; Q[2] = d + (size_t)h * ld; Q[3] = Q[2] + h;
  long long *T2 = T + (size_t)h * h;
  int wr[4] = {0, 0, 0, 0};
#define MK(qi, si, qj, sj, Ma, Mb) do { \
    int oa = wr[qi] ? effop(1, (si)) : effop(op, (si)); \
    int ob = wr[qj] ? effop(1, (sj)) : effop(op, (sj)); \
    wr[qi] = 1; wr[qj] = 1; \
    if (oa == 0) { \
      mmxR(h, &(Ma), &(Mb), Q[qi], ld, 0, T); \
      cmb(Q[qj], ld, Q[qi], ld, h, (sj), ob); \
    } else { \
      mmxR(h, &(Ma), &(Mb), T, h, 0, T2); \
      cmb(Q[qi], ld, T, h, h, (si), oa); \
      cmb(Q[qj], ld, T, h, h, (sj), ob); \
    } \
  } while (0) \

#define MK1(qi, si, Ma, Mb) do { \
    int oa = wr[qi] ? effop(1, (si)) : effop(op, (si)); \
    wr[qi] = 1; \
    mmxR(h, &(Ma), &(Mb), Q[qi], ld, oa, T); \
  } while (0)
  { /* M1 = (A11+A22)(B11+B22) -> +C11, +C22 */
    Opd Ma = oadd(&A11, &A22, 1), Mb = oadd(&B11, &B22, 1);
    MK(0, 1, 3, 1, Ma, Mb);
  }
  { /* M2 = (A21+A22)B11 -> +C21, -C22 */
    Opd Ma = oadd(&A21, &A22, 1), Mb = B11;
    MK(2, 1, 3, 2, Ma, Mb);
  }
  { /* M3 = A11(B12-B22) -> +C12, +C22 */
    Opd Ma = A11, Mb = oadd(&B12, &B22, -1);
    MK(1, 1, 3, 1, Ma, Mb);
  }
  { /* M4 = A22(B21-B11) -> +C11, +C21 */
    Opd Ma = A22, Mb = oadd(&B21, &B11, -1);
    MK(0, 1, 2, 1, Ma, Mb);
  }
  { /* M5 = (A11+A12)B22 -> -C11, +C12 */
    Opd Ma = oadd(&A11, &A12, 1), Mb = B22;
    MK(0, 2, 1, 1, Ma, Mb);
  }
  { /* M6 = (A21-A11)(B11+B12) -> +C22 */
    Opd Ma = oadd(&A21, &A11, -1), Mb = oadd(&B11, &B12, 1);
    MK1(3, 1, Ma, Mb);
  }
  { /* M7 = (A12-A22)(B21+B22) -> +C11 */
    Opd Ma = oadd(&A12, &A22, -1), Mb = oadd(&B21, &B22, 1);
    MK1(0, 1, Ma, Mb);
  }
#undef MK
#undef MK1
}

void matrix_multiply(int n, const long long *A, const long long *B, long long *C) {
  Opd Ao = o1(A, n), Bo = o1(B, n);
  if (n <= CUTL || (n & 1)) { leaf(n, &Ao, &Bo, C, n, 0, 0, 0, 0); return; }
  mmxR(n, &Ao, &Bo, C, n, 0, g_scratch);
}
#pragma GCC pop_options

CompilationN/AN/ACompile OKScore: N/A

Testcase #1925.515 ms47 MB + 472 KBAcceptedScore: 100


Judge Duck Online | 评测鸭在线
Server Time: 2026-11-09 06:19:45 | Loaded in 1 ms | Server Status
个人娱乐项目,仅供学习交流使用 | 捐赠