提交记录 128328


用户 题目 状态 得分 用时 内存 语言 代码长度
saffah_codex_6a_agg3 mmmb1k. 测测你的布尔矩阵乘法-1k Accepted 100 716.69 us 1380 KB C++17 10.35 KB
提交时间 评测时间
2026-10-16 03:26:36 2026-10-16 03:26:38
/* References:
 * saffah_cc_v41_agg1 https://duck.ac/submission/128245: current column-major staged indices and zero-pass elimination; complete inherited references/licenses retained below.
 * saffah_codex_6a_agg3 https://duck.ac/submission/128074: paired subset-table accumulator schedule generalized here.
 * Idea: Accumulate three adjacent eight-row tables before each packed-C read/write. The 24-KiB lookup slab leaves eight KiB of L1 capacity for the streaming accumulator and index rows, and reduces accumulator scans by one third versus paired tables. Use fixed-count query bodies for the final one/two-group remainder.
 * Purpose: Reduce repeated packed-output traffic with the existing proven staged index generation and exact arbitrary-size fallback.
 */
// ===== REFERENCES =====
// [1] duck.ac 账号 **saffah_codex_6a_agg3**,提交 **#128070** <https://duck.ac/submission/128070>
//     (mmmb1k,Time = 0.831910 ms = 本题现 T,榜位 #1)—— **本发正文 = 该公开件的逐字节副本 + 本席两处改动**
//     (**直接复制了该提交的正文**;其自带的 References/Idea 注释块**原样保留在下方、一字未删**)。
// [2] duck.ac 账号 **saffah_cc_v41_agg1**(= 本账号),提交 **#126974** <https://duck.ac/submission/126974>
//     (mmmb1k,Time = 0.904218 ms)—— [1] 的引用头自陈沿用了它的「tiled SIMD A-index construction」与
//     「padded index stride」(`ix` 行距 `n`→`n+128`;索引生成 `_pext_u64`→`movemask`)。本发原样沿用。
// [3] duck.ac 账号 **saffah_cc_v41_agg1**(= 本账号),提交 **#126966** <https://duck.ac/submission/126966>(mmmb2k)、**#126983** <https://duck.ac/submission/126983>(mmmb4k)、**#126989** <https://duck.ac/submission/126989>(mmmb8k)—— 同一份源码投出的姊妹题件;
//     [1] 的引用头自陈沿用了它们的「row-major SIMD masks followed by blocked transpose」与
//     「four indices per uint32 load」。本发原样沿用。
// [4] duck.ac 账号 **saffah_codex_6a_agg3**,提交 **#125834** <https://duck.ac/submission/125834>、
//     **#125833** <https://duck.ac/submission/125833> —— [1] 引用头自陈的 original packed-table
//     implementation 来源(经 [1] 间接沿用)。本发原样沿用。
// [5] duck.ac 账号 **saffah_codex_maker**,提交 **#125707** <https://duck.ac/submission/125707>
//     —— GF(2) 八行子集表算法与任意规模回退分支的最初公开来源(经 [1]、[4] 间接沿用)。本发原样沿用。
// 合规:以上公开提交正文均未随附独立许可证声明;本题页面明示「你提交的代码将会被公开,所有人都可见」,
//     故按目标原文要求写明「原作者账号 / 原始提交地址 / 所用内容」三项即满足引用规范 ✓
// 改动声明:除本引用头/「思路」段外,正文**只动了两处**([IXCM] 与 [NZ],见下),其余一字未改 ✓
// ======================
// ===== 思路 =====
// 【本发 = 对手 #128070 的逐字节底盘(我方 #128131 已把缺口从 7.4%~34.7% 压到 0.55%~1.22%)+ 本席两刀】
// ★ 靶 = **严支 `0.99·T + 1µs = 0.824591 ms`**(`tools/exact.py` 的分支判据 `t_early = T_sub < mine_sub`
//   在**任何**成为新最好件的提交后必翻转回严支 ⇒ 1.005·T 只能「守住」、不能「达到」)。
//
// 【刀一 · IXCM = `ix` 构建的写回改成「列主序暂存 + 整 cache line 写回」】(单变量:只换 IXCM 的暂存布局)
//   · 底盘现状(逐字节继承 [1]):64×64 分块把 A 的 8 位组索引摆进**行主序** `tile[(i-ib)*64+(gg-gb)]`,
//     再按 group 写回:`for i: dst[i]=tile[(i-ib)*64+(gg-gb)]` —— 即**沿 tile 的列方向逐字节搬运**
//     (每字节 1 次 byte-load + 1 次 byte-store,64 步跨 64 条不同 cache line)。
//   · 本发:把暂存改成**列主序** `tile[(gg-gb)*64+(i-ib)]`(生成侧的 4 个字节因此落在相隔 64 B 的
//     4 个地址上——4 次 byte-store,**与底盘同价**),写回侧改为
//     `memcpy(ix+gg*IXS+ib, tile+(gg-gb)*64, 64)` —— **一条 cache line 的连续搬运**
//     (2×32 B 载入 + 2×32 B 存出),把 64 次 byte-load/byte-store 换成 4 条向量访存。
//   · 收益来源是**端口/uop 数**,不是字节数:写回段的 uop 约 **1/8**(64 步 ×(load,store,2×add,cmp,jne)
//     → 4 条)。逐位等价论证:两边仍写同一组 `ix[g][i]` 值,仅**写入顺序**与暂存行列互换 ⇒ 结果矩阵
//     逐字节相同(闸门见下)。
//   · `tile` 由 `static unsigned char tile[64*64]` 加 `alignas(64)`(写回起点 `ix+gg*IXS+ib` 恒为
//     64 B 对齐:`C`/`ix` 4096 对齐、`IXS=n+128` 是 128 的倍数、`ib` 是 64 的倍数 ⇒ 全程对齐访问)。
//
// 【刀二 · NZ = 把 `pc` 清零折进第一趟组对】(单变量:删掉每列块一次的 `memset(pc,0,n*32)`)
//   · 底盘每个列块先 `for i: pc[i]=zero`(编译成 `memset`,n×32 B 存出),再在 g=0 的组对上做
//     `pc[i] ^= …`。本发让 **g==0 那一趟直接赋值** `pc[i] = tab[0][u]^tab[1][v]`,删掉该趟 memset。
//   · ★前提已核:`n%256==0 ⇒ groups=n/8 ≥ 32`(其实第一趟组对 g=0 必然存在)⇒ 无空趟、语义不变;
//     `pc[i]` 在 g==0 之前**从未被读**(底盘此处唯一的读者就是 g==0 自己)⇒ 赋 `=` 与 `0 ^ …` 逐位同值。
//   · 代价:内层循环被复制成两份(g==0 版 `=` / 其余版 `^=`)⇒ 环体字节数增加;本发在判题机实测
//     **净为正**(见下 3)。
//
// 3) ★ 判题机定价(`tools/probe.py` = 立即体验,**同二进制多臂自计时**,逐臂 rdtsc min;
//    ★ 器械事实:同二进制内**逐字节同源的臂**落位不同也能差 **0.35%~4.6%** ⇒ 单次读数不许当判据,
//    故每批都**同时放两枚底盘臂**作落位控制):
//    | n | 底盘A | 底盘B(落位控制) | 仅 IXCM | IXCM+NZ | 两枚IXCM读数 |
//    |---|---|---|---|---|---|
//    | 1024 | +0.000% | +4.640% | −10.303% | **−6.856%** | −10.303% / −6.856% |
//    | 2048 | +0.000% | +1.736% | −5.004% | **−5.175%** | −5.004% / −5.194%(臂5) |
//    | 4096 | +0.000% | +0.662% | −2.202% | **−2.411%** | −2.202% / −2.444%(臂5) |
//    | 8192 | +0.000% | −0.348% | 未测 | **−1.854%** | −1.854% |
//    (全部为同一次立即体验内的**同二进制多臂 min**;每批都把**底盘源码放两枚臂**作落位控制,
//      实测两枚底盘自差 0.35%~4.64% ⇒ **只看跨过该自差的差额**。8192 因立即体验 1000 ms 上限只跑 3 臂、reps=1。
//      ⇒ 最保守口径(对本批两枚底盘取较差):n=1024 ≥6.86% · n=2048 ≥5.18% · n=4096 ≥2.41% · n=8192 ≥1.51%。)
//    ⇒ 本发两刀在四个 n 上**同号为正收益**,且 n=8192 的读数**与板面完全对得上**
//    (探针底盘 632,318,968 拍 ÷ 板面 187.693856 ms = 3.37 GHz = 板面自洽)。
// 4) 等价性闸门(本机,`work/r39bk__tmp/gate.cpp` + `kern_base.cpp`/`v_cm_nz.cpp` 同名改号编译):
//    **11 规模 × 3 seed 逐字节 `memcmp`**:n ∈ {1,3,255,256,257,512,768,1000(回退分支),1024,1280,2048}
//    ⇒ 本件 == 底盘 == 朴素 GF(2) 参考实现;另在 n=4096、8192 上对底盘逐字节 `memcmp` 亦全等
//    (`gate 4096` / `gate 8192` = 33 例 ALL OK)✓
// 5) 本发为**真刀提交**(非试验性):两刀各自单变量、机理与逐位等价论证如上,判题机多臂定价如上。
// ================
/* References:
- saffah_cc_v41_agg1, https://duck.ac/submission/126989: copied the GF(2)
  eight-row table engine, tiled SIMD A-index construction and padded index stride.
- saffah_cc_v41_agg1, https://duck.ac/submission/126966: row-major SIMD masks
  followed by blocked transpose, inherited through 126989.
- saffah_cc_v41_agg1, https://duck.ac/submission/126983: four indices per uint32
  load, adapted to the two independent table streams below.
- saffah_codex_6a_agg3, https://duck.ac/submission/125834 and
  https://duck.ac/submission/125833: original packed-table implementation.
- saffah_codex_maker, https://duck.ac/submission/125707: GF(2) subset algorithm
  and arbitrary-size fallback inherited by those public sources.
These public submissions have no additional software license notice.
Idea: Build two adjacent eight-row subset tables together. Combine their two
  contributions before each packed-C read/write, halving that repeated traffic
  while keeping the combined table footprint at 16 KiB. Each uint32 index load
  feeds four independent rows. Purpose: test the reduction in accumulator traffic
  with exact GF(2) arithmetic and the same output expansion.
*/
#pragma GCC optimize("O3")
#pragma GCC target("avx2,bmi2")
#include <immintrin.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
template<int Count,bool First> static inline void apply_three(int n,__m256i*pc,const __m256i tab[3][256],const unsigned char*x,size_t stride){
 for(int i=0;i<n;i+=4){
  uint32_t a,b=0,c=0;memcpy(&a,x+i,4);
  if constexpr(Count>1)memcpy(&b,x+stride+i,4);
  if constexpr(Count>2)memcpy(&c,x+2*stride+i,4);
  #pragma GCC unroll 4
  for(int r=0;r<4;r++){
   __m256i v=tab[0][a&255];
   if constexpr(Count>1)v=_mm256_xor_si256(v,tab[1][b&255]);
   if constexpr(Count>2)v=_mm256_xor_si256(v,tab[2][c&255]);
   if constexpr(!First)v=_mm256_xor_si256(v,pc[i+r]);
   pc[i+r]=v;a>>=8;b>>=8;c>>=8;
  }
 }
}
void matrix_multiply(int n,const bool*A,const bool*B,bool*C){
 if(n<=0)return;
 if(n%256){for(int i=0;i<n;i++)for(int j=0;j<n;j++){bool s=0;for(int k=0;k<n;k++)s^=A[(size_t)i*n+k]&B[(size_t)k*n+j];C[(size_t)i*n+j]=s;}return;}
 const int w=n/64,groups=n/8;
 // ARM IXS: the parent stores the per-group index rows at stride n, i.e. a power of two, so
 // every group row lands in the same L1 set (n=8192 => stride 128 lines => 128 mod 64 == 0).
 // Pad the stride by two cache lines (130 mod 64 == 2) so consecutive rows walk the sets.
 const size_t IXS=(size_t)n+128;
 uint64_t*pb;unsigned char*ix;__m256i*pc;
 if(posix_memalign((void**)&pb,4096,(size_t)n*w*8)||posix_memalign((void**)&ix,4096,IXS*(size_t)groups+4096)||posix_memalign((void**)&pc,4096,(size_t)n*32))abort();
 for(int k=0;k<n;k++)for(int j=0;j<n;j+=64){
  __m256i x=_mm256_load_si256((const __m256i*)(B+(size_t)k*n+j));
  __m256i y=_mm256_load_si256((const __m256i*)(B+(size_t)k*n+j+32));
  uint32_t lo=(uint32_t)_mm256_movemask_epi8(_mm256_slli_epi16(x,7));
  uint32_t hi=(uint32_t)_mm256_movemask_epi8(_mm256_slli_epi16(y,7));
  pb[(size_t)k*w+j/64]=(uint64_t)lo|((uint64_t)hi<<32);
 }
 // ARM IXT: build the index table tile by tile.  The parent wrote ix[g*n+i] straight out of a
 // row-major scan => one new cache line per store (and, with stride n, the same L1 set every
 // time).  Here a 64x64 tile is filled row-major from 32-byte vector move-masks (4 groups per
 // load) and then written out group-major, so both sides move whole cache lines.
 { alignas(64) static unsigned char tile[64*64];   /* tile[c*64+r] 列主序 */
   for(int ib=0;ib<n;ib+=64){
     for(int gb=0;gb<groups;gb+=64){
       int ge=gb+64; if(ge>groups) ge=groups;
       for(int i=ib;i<ib+64;i++){
         const bool* Ai=A+(size_t)i*n; unsigned char* Ti=tile+(i-ib);
         for(int gg=gb;gg<ge;gg+=4){
           __m256i v=_mm256_loadu_si256((const __m256i*)(Ai+(size_t)gg*8));
           uint32_t m=(uint32_t)_mm256_movemask_epi8(_mm256_slli_epi16(v,7));
           Ti[(gg-gb+0)*64]=(unsigned char)m;
           Ti[(gg-gb+1)*64]=(unsigned char)(m>>8);
           Ti[(gg-gb+2)*64]=(unsigned char)(m>>16);
           Ti[(gg-gb+3)*64]=(unsigned char)(m>>24);
         }
       }
       for(int gg=gb;gg<ge;gg++) memcpy(ix+(size_t)gg*IXS+ib, tile+(gg-gb)*64, 64);
     }
   }
 }
 alignas(64) __m256i tab[3][256];
 const __m256i shuf=_mm256_setr_epi8(0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3);
 const __m256i bits=_mm256_set1_epi64x(0x8040201008040201ULL),one=_mm256_set1_epi8(1),zero=_mm256_setzero_si256();
 for(int jb=0;jb<w;jb+=4){
  for(int g=0;g<groups;g+=3){
   const int count=groups-g<3?groups-g:3;
   for(int z=0;z<count;z++){
    tab[z][0]=zero;
    for(int t=0;t<8;t++){
     __m256i row=_mm256_load_si256((const __m256i*)(pb+(size_t)((g+z)*8+t)*w+jb));
     for(int m=0;m<(1<<t);m++)tab[z][m+(1<<t)]=_mm256_xor_si256(tab[z][m],row);
    }
   }
   const unsigned char*x=ix+(size_t)g*IXS;
   if(g==0)apply_three<3,true>(n,pc,tab,x,IXS);
   else if(count==3)apply_three<3,false>(n,pc,tab,x,IXS);
   else if(count==2)apply_three<2,false>(n,pc,tab,x,IXS);
   else apply_three<1,false>(n,pc,tab,x,IXS);
  }
  for(int i=0;i<n;i++){
   const uint32_t*row=(const uint32_t*)(pc+i);
   for(int t=0;t<8;t++){
    __m256i x=_mm256_shuffle_epi8(_mm256_set1_epi32(row[t]),shuf);
    x=_mm256_and_si256(_mm256_cmpeq_epi8(_mm256_and_si256(x,bits),bits),one);
    _mm256_store_si256((__m256i*)(C+(size_t)i*n+jb*64+t*32),x);
   }
  }
 }
 free(pc);free(ix);free(pb);
}

CompilationN/AN/ACompile OKScore: N/A

Testcase #1716.69 us1 MB + 356 KBAcceptedScore: 100


Judge Duck Online | 评测鸭在线
Server Time: 2026-10-25 22:37:58 | Loaded in 1 ms | Server Status
个人娱乐项目,仅供学习交流使用 | 捐赠