什么?你说这些东西没用?
那你就大错特错了。WC考过的东西怎么可能没用
不开O2:NTT比FFT快
开O2:FFT比NTT快
有一道NTT的题,模数声明成变量跑了 1166 1166 ms,模数声明成常量跑了不到 300 300 ms
//6s
const int p=10;
int main()
{
open("orzzjt");
int a;
scanf("%d",&a);
int i;
for(i=1;i<=1000000000;i++)
a=(a*a+10)%p;
printf("%d\n",a);
return 0;
}
//10s
int p=10;
int main()
{
open("orzzjt");
int a;
scanf("%d",&a);
int i;
for(i=1;i<=1000000000;i++)
a=(a*a+10)%p;
printf("%d\n",a);
return 0;
}
当然,编译器大多数情况下会帮你优化掉。
加法运算只要 1 1 个时钟周期,乘法运算只要 3 3 个时钟周期,而除法和取模运算要几到几十个时钟周期。
3×3 3 × 3 的矩阵乘法:边加边取模: 27 27 次取模运算;全部算完再取模: 9 9 次取模运算。
用指针保存上一次使用的地址,直接加偏移。
a:对于适合分治预测的数据,测得平均一次循环需要 4.0 4.0 个时钟周期;对于随机数据,测得平均一次循环需要 12.8 12.8 个时钟周期。可见,分支预测错误的惩罚为 2×(12.8−4.0)=17.6 2 × ( 12.8 − 4.0 ) = 17.6 个时钟周期。
b:用三元运算符重写,让编译器生成一种基于条件传送的汇编代码。测得不论数据如何,平均一次循环只需要 4.1 4.1 个时钟周期。
//a.cpp
void minmax1(int *a,int *b,int n)
{
for(int i=1;i<=n;i++)
if(a[i]>b[i])
{
int t=a[i];
a[i]=b[i];
b[i]=t;
}
}
//b.cpp
void minmax2(int *a,int *b,int n)
{
for(int i=1;i<=n;i++)
{
int mi=a[i]int ma=a[i]
a:平均每个元素需要 3.65 3.65 个时钟周期。
b:平均每个元素需要 1.36 1.36 个时钟周期。
这样能够刺激CPU并行。
当展开次数过多时,性能反而会下降,因为寄存器不够用 ⟶ ⟶ 寄存器溢出
注意每部分要独立以及处理非展开次数的倍数的部分
//a.cpp
double sum(double *a,int n)
{
double s=0;
for(int i=1;i<=n;i++)
{
s+=a[i];
}
return s;
}
//b.cpp
double sum(double *a,int n)
{
double s0=0,s1=0,s2=0,s3=0;
for(int i=1;i<=n;i+=4)
{
s0+=a[i];
s1+=a[i+1];
s2+=a[i+2];
s3+=a[i+3];
}
return s0+s1+s2+s3;
}
尽量使用步长为 1 1 的访问模式,即访问的内存是连续的。
在遍历高维数组是很重要
是内存访问的工作集尽量小
在统计整数二进制表示中 1 1 的个数时,分两段查表有时不如分三段好。
避免缓存冲突。
在状压DP、使用高位数组时很重要
解决方法:把数组稍微开大一些
类型 | 延迟(周期数) |
---|---|
CPU寄存器 | 0 0 |
TLB | 0 0 |
L1高速缓存 | 4 4 |
L2高速缓存 | 10 10 |
L3高速缓存 | 50 50 |
虚拟内存 | 200 200 |
在某Intel Core i5 CPU上,有这些高速缓存:
高速缓存类型 | 访问时间(周期) | 高速缓存大小 | 相联度 | 块大小 | 组数 |
---|---|---|---|---|---|
L1 I-Cache | 4 4 | 32 32 KB | 8 8 | 64 64 B | 64 64 |
L1 D-Cache | 4 4 | 32 32 KB | 8 8 | 64 64 B | 64 64 |
L2 Cache | 约 12 12 | 256 256 KB | 4 4 | 64 64 B | 512 512 |
L3 Cache | 约 50 50 | 6 6 MB | 12 12 | 64 64 B | 8192 8192 |
对于不同的 n n 和 d d ,反复调用这个程序,具有不同的时空局部性。
容易得知, n n 越小,时间局部性越好, d d 越小,空间局部性越好。
int sum(int *a,int n,int d)
{
int s=0;
for(int i=0;is+=a[i*d];
return s;
}
n n 足够大时结果如下
与理论相符
d d | 1 1 | 2 2 | 3 3 | 4 4 | 8 8 | 16 16 | 32 32 | 64 64 |
---|---|---|---|---|---|---|---|---|
周期数 | 1.50 1.50 | 2.34 2.34 | 3.46 3.46 | 4.73 4.73 | 9.70 9.70 | 15.00 15.00 | 19.76 19.76 | 20.26 20.26 |
n=200 n = 200 时结果如下
d d | 219 2 19 | 219+1 2 19 + 1 |
---|---|---|
周期数 | 159 159 | 1.18 1.18 |
这是为什么呢?
200 200 个整数,显然能在L1缓存装得下?
对于 d=219 d = 2 19 ,每次内存访问时,地址的后 19 19 位都是一样的。
根据CPU高速缓存的原理,这些地址必然会被映射到同一个组
因此,缓存只有一组, 159 159 周期就是内存访问速度。
p.s.:后 19 19 位一样的是虚拟地址,在映射成物理地址之后,由于操作系统的特性,也至少有后 12 12 位是一样的。