EP1. 512x512 矩阵乘法 B3615

tag:SIMD,fma,cache

思路主要来源于 algorithmica

首先矩阵乘法这个东西很适合 SIMD。先启动一下指令集:record1,491ms

这里使用了一个 GCC 扩展,即 typedef double vec __attribute__ ((vector_size(32))) 可以生成一个 $4$ 个 double 打包的类型 vec,基本兼容正常数组的语法,底层实现是 __m256d

此外,矩阵乘法的核心操作是 $x\gets x + y\cdot z$,这个可以用 fma 压到一行指令,当然也有对应的指令集操作。

这个东西并不是那么 Cache-friendly。所以我们考虑分块计算,假设我们要求出 C[x,x+h][y,y+w] 这一个较小的子矩阵答案,我们需要 fetch $(h+w)\cdot n$ 个位置,而我们原先的做法相当于算一个位置就 fetch 了 $2\cdot n$ 个位置。

取 $h=6,w=8$,平均一个位置 fetch 了 $\frac{h+w}{hw}$ 行,效率大约提升了 $6.8$ 倍!

当然实现出来没有理论上那么优秀,因为有别的瓶颈。

record2,356ms

这个东西其实对 Cache 还是不太友好,考虑更加激进一点,我们每次只用 A,B 的一小块来更新 C,这样数组中一小部分高频访问,可以减少 Cache-miss。

record3,313ms

当然可以调整参数!

record4,261ms

这个东西差不多到头了,我们发现 lg 的评测机支持 avx512f 也就是同时压 $8$ 个 double,启动一下!

record5,200ms

感觉应该还有优化空间,但卡不动了。

EP2. JKRSJ R2 你的名字。

做法还是比较清晰的:对 $k$ 根号分治:

  • 小于 $\sqrt V$ 的只有根号个 $k$,枚举 $k$ 随便拿个啥 DS 维护一下即可;
  • 大于 $\sqrt V$ 的话,$\lfloor \frac{a_i}{k} \rfloor$ 只有根号种,考虑枚举这个商,把一个询问拆成根号个,假设当前商为 $q$,则询问的答案即为 $q\cdot k$ 在区间 $[l,r]$ 的非严格后继。

区间非严格后继可以扫描线,这样就变成了 $O(n)$ 次修改,$O(q \sqrt V)$ 次询问的 $\text{rmq}$ 问题。后者可以直接上 带修 ST 表好了我们做完了。

下面是常数优化 Hints:

  1. 拆出来的询问不能全部存下来,要不然空间复杂度会多一个根号。所以我们要枚举因数,然后可以提前预处理出因数来卡常。
  2. 善用 vectorreserve。极端情况下手写内存池。
  3. 使用快读快输。
  4. 对于 $k \le \sqrt V$ 的情形,使用 zkw 而非分块;如果对于某一个这样的 $k$,询问次数很少(实测 $\le 4$)左右,就暴力跑。
  5. 由于修改的值单调不增,我们可以将 ST 表修改时(几乎所有的)chkmin 改为赋值。

标签: none

添加新评论