一些卡常技巧

什么?你说这些东西没用?

  那你就大错特错了。WC考过的东西怎么可能没用

开O2之后FFT会比不开快几倍

  不开O2:NTT比FFT快

  开O2:FFT比NTT快

常数尽量声明成常量

  有一道NTT的题,模数声明成变量跑了\(1166\)ms,模数声明成常量跑了不到\(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\)个时钟周期,乘法运算只要\(3\)个时钟周期,而除法和取模运算要几到几十个时钟周期。

  \(3\times 3\)的矩阵乘法:边加边取模:\(27\)次取模运算;全部算完再取模:\(9\)次取模运算。

优化高位数组的寻址

  用指针保存上一次使用的地址,直接加偏移。

对于一个值的重复运算,存入临时变量中

消除条件跳转

  a:对于适合分治预测的数据,测得平均一次循环需要\(4.0\)个时钟周期;对于随机数据,测得平均一次循环需要\(12.8\)个时钟周期。可见,分支预测错误的惩罚为\(2\times (12.8-4.0)=17.6\)个时钟周期。

  b:用三元运算符重写,让编译器生成一种基于条件传送的汇编代码。测得不论数据如何,平均一次循环只需要\(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]<b[i]?a[i]:b[i];
        int ma=a[i]<b[i]?b[i]:a[i];
        a[i]=mi;
        b[i]=ma;
    }
}

循环展开

  a:平均每个元素需要\(3.65\)个时钟周期。

  b:平均每个元素需要\(1.36\)个时钟周期。

  这样能够刺激CPU并行。

  当展开次数过多时,性能反而会下降,因为寄存器不够用\(\longrightarrow\)寄存器溢出

  注意每部分要独立以及处理非展开次数的倍数的部分

//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\)的个数时,分两段查表有时不如分三段好。

避免使用步长为较大的\(2\)的幂的访问模式

  避免缓存冲突。

  在状压DP、使用高位数组时很重要

  解决方法:把数组稍微开大一些

一些数据

类型 延迟(周期数)
CPU寄存器 \(0\)
TLB \(0\)
L1高速缓存 \(4\)
L2高速缓存 \(10\)
L3高速缓存 \(50\)
虚拟内存 \(200\)

  在某Intel Core i5 CPU上,有这些高速缓存:

高速缓存类型 访问时间(周期) 高速缓存大小 相联度 块大小 组数
L1 I-Cache \(4\) \(32\)KB \(8\) \(64\)B \(64\)
L1 D-Cache \(4\) \(32\)KB \(8\) \(64\)B \(64\)
L2 Cache 约\(12\) \(256\)KB \(4\) \(64\)B \(512\)
L3 Cache 约\(50\) \(6\)MB \(12\) \(64\)B \(8192\)

  对于不同的\(n\)和\(d\),反复调用这个程序,具有不同的时空局部性。

  容易得知,\(n\)越小,时间局部性越好,\(d\)越小,空间局部性越好。

int sum(int *a,int n,int d)
{
    int s=0;
    for(int i=0;i<n;i++)
        s+=a[i*d];
    return s;
}
空间局部性

  \(n\)足够大时结果如下

  与理论相符

\(d\) \(1\) \(2\) \(3\) \(4\) \(8\) \(16\) \(32\) \(64\)
周期数 \(1.50\) \(2.34\) \(3.46\) \(4.73\) \(9.70\) \(15.00\) \(19.76\) \(20.26\)
时间局部性

  \(n=200\)时结果如下

\(d\) \(2^{19}\) \(2^{19}+1\)
周期数 \(159\) \(1.18\)

  这是为什么呢?

  \(200\)个整数,显然能在L1缓存装得下?

  对于\(d=2^{19}\),每次内存访问时,地址的后\(19\)位都是一样的。

  根据CPU高速缓存的原理,这些地址必然会被映射到同一个组

  因此,缓存只有一组,\(159\)周期就是内存访问速度。

  p.s.:后\(19\)位一样的是虚拟地址,在映射成物理地址之后,由于操作系统的特性,也至少有后\(12\)位是一样的。

原文地址:https://www.cnblogs.com/ywwyww/p/8511463.html

时间: 2024-10-03 19:59:01

一些卡常技巧的相关文章

时间卡常技巧

以下内容出自: 时间卡常技巧 先放一句话镇场: 我觉得,卡常数的出题人都是xx,这违背了算法竞赛考察思路的初衷 ——LYD 推荐:论OI中各种玄学卡常 我们一般说的复杂度都是O(n)O(n^2)O(nlogn)是一个级别. 但是我们其实每一个步可能计算很多次,然后会乘上一个2*n,3*n,甚至10*n 我们都叫O(n) 这个乘上的数就是常数.有的时候,你(chu)自(ti)己(ren)的(sang)程(xin)序(bing)可(kuang)能(ka)常(chang)数(shu)太(qwq)大(Q

卡常技巧

一般: 1. 思路清晰,简化流程. 2. 数组访问: 高位数组寻址优化. 访问的内存尽量连续. 3. 少用除法.取模. 4. 重复运算,存入临时变量. 5. 循环展开,刺激 CPU 并行.展开次数过多,性能会下降,因为寄存器不够用. 6. 读入优化,输出优化. 分块: 1. 调整块大小. FFT: 1. 减小循环长度. 2. 两次 FFT ,complex(a[] + b[], a[] - b[]) 自乘. 递归: 1. 改写非递归.例如并查集,例如欧几里得算法,等等. STL: 1. 手写.

CodeForces 327E Axis Walking(状压DP+卡常技巧)

Iahub wants to meet his girlfriend Iahubina. They both live in Ox axis (the horizontal axis). Iahub lives at point 0 and Iahubina at point d. Iahub has n positive integers a1, a2, ..., an. The sum of those numbers is d. Suppose p1, p2, ..., pn is a p

论OI中各种玄学卡常

当你在写程序的时候一般出现过这种无比悲剧的情况: 你讨厌卡常?下面有二则小故事: 作为一个经常出题的人,其实很多时候出题时的画风是这样的:"我有一个绝妙的\(O(nlog^2n)\)的算法,我来出道题吧""咦怎么只能跑 \(5w\) 啊,好咸鱼啊,我要让它能跑 \(10w\),嗯现在 \(10w\) 只要 \(0.3s\) 了,要不努把力跑个 \(20w\) 吧"然后就没有然后了.. 开O2之后FFT会比不开快几倍? 不开\(O2\):\(NTT\)比\(FFT\)

Codeforces 988D Points and Powers of Two 【性质】【卡常】

这道题关键在于想到两个性质,想到就好做了.这还是我做过的第一道卡常题 1.满足题目中条件的子集,其中元素个数不能大于3 2.如果最大子集为3的话,那一定是x-2^i,  k, x+2^i的形式,我们枚举x就好了,然后i的次数是log10^9:如果最大子集是2,那就是x,x+2^i的形式,同样枚举x:如果最大子集是1,输出a[1]就行 整体复杂度是O(n*logn*log10^9) 1 #include<iostream> 2 #include<set> 3 using namesp

bzoj4028 [HEOI2015]公约数数列(分块+卡常?)

被卡常卡到怀疑人生. 思维又难又卡常(可能是我写的太丑了)心态炸了. 最后还是照题解打的.(题解多了一个排序,似乎快了很多) 所以代码就不发了... 原文地址:https://www.cnblogs.com/Xu-daxia/p/9465265.html

卡常模板

卡常模板,必背 1 #pragma GCC optimize(1) 2 #pragma GCC optimize(2) 3 #pragma GCC optimize(3) 4 #include<iostream> 5 #include<cstdio> 6 #include<ctime> 7 #include<cstdlib> 8 #include<cstring> 9 #include<algorithm> 10 #define Ri

[luogu1972][bzoj1878][SDOI2009]HH的项链【莫队+玄学卡常】

题目大意 静态区间查询不同数的个数. 分析 好了,成功被这道题目拉低了AC率... 打了莫队T飞掉了,真的是飞掉了QwQ. 蒟蒻想不出主席树的做法,就换成了莫队... 很多人都不知道莫队是什么... 一句话概括莫队:离线询问分块排序,玄学降低复杂度 那么这道题目就是简单的莫队模板套一下就好了,每一次看看更新的点是不是会对答案造成贡献就可以过掉了. 但是复杂度很明显是\(Q(\sqrt{n}m)\),成功T掉,加上玄学卡常,破罐子破摔了100+终于过掉了. #include <bits/stdc+

[CSP-S模拟测试]:卡常题/b(基环树+DP)

题目描述 $ρ$有一个二分连通无向图,$X$方点.$Y$方点均为$n$个(编号为$1\sim n$).这个二分图比较特殊,每一个$Y$方点的度为$2$,一条黑色边,一条白色边.所有黑色边权值均为$a$,所有白色边权值均为$b$.选择一个$X$方点,代价为连接的所有边的权值之和.激活一个$Y$方点,需要选择至少一个与之相邻的$X$方点.现在,$ρ$想激活每个$Y$方点,他想知道最小的总代价.不过$ρ$很善良,他给你开了$O2$优化.这样你就不会被卡常了.当然,除非你真的连读入优化都不想写,或者常数