[黑科技]常数优化的一些技巧

感谢wys和小火车普及这些技巧qwq 这篇文章大概没什么营养

我们来看一道十分简单的题目:

设n=131072,输入两个长度为n的数列,要求输出一个长度为n的数列

其中

首先我们来讲讲这题怎么做。

如果数据是随机的,那么有一种神奇的做法:在a和b中分别挑出最大的p个元素,对于每个i暴力枚举每个p进行更新,这样的复杂度是O(np)的,正确性我不会分析= =

那么数据不是随机的...那么估计没有什么快速的算法,不如暴力!

以下的运行时间均为在我的渣渣笔记本中测试得到,仅供参考。测试环境Ubuntu,编译选项只有-O2。

0.

#define SZ 666666
int a[SZ],b[SZ],c[SZ];
const int n=131072;
int main()
{
    for(int i=0;i<n;i++) scanf("%d",a+i);
    for(int i=0;i<n;i++) scanf("%d",b+i);
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=i;j++) c[i]=max(c[i],a[j]+b[i-j]);
    }
    for(int i=0;i<n;i++) printf("%d ",c[i]);puts("");
    cerr<<clock()<<"ms\n";
}

simple and stupid。我们来测试一下...跑了8s。虽然不是太糟,但是还是很慢...我们来进行一波有理有据的常数优化吧。

1. 加上输入输出优化

const int n=131072;
char ch,B[1<<20],*S=B,*T=B;
#define getc() (S==T&&(T=(S=B)+fread(B,1,1<<20,stdin),S==T)?0:*S++)
#define isd(c) (c>=‘0‘&&c<=‘9‘)
int aa,bb;int F(){
    while(ch=getc(),!isd(ch)&&ch!=‘-‘);ch==‘-‘?aa=bb=0:(aa=ch-‘0‘,bb=1);
    while(ch=getc(),isd(ch))aa=aa*10+ch-‘0‘;return bb?aa:-aa;
}
#define gi F()
#define BUFSIZE 5000000
namespace fob {char b[BUFSIZE]={},*f=b,*g=b+BUFSIZE-2;}
#define pob (fwrite(fob::b,sizeof(char),fob::f-fob::b,stdout),fob::f=fob::b,0)
#define pc(x) (*(fob::f++)=(x),(fob::f==fob::g)?pob:0)
struct foce {~foce() {pob; fflush(stdout);}} _foce;
namespace ib {char b[100];}
inline void pint(int x)
{
    if(x==0) {pc(48); return;}
    //if(x<0) {pc(‘-‘); x=-x;} //如果有负数就加上
    char *s=ib::b;
    while(x) *(++s)=x%10, x/=10;
    while(s!=ib::b) pc((*(s--))+48);
}
int main()
{
    for(int i=0;i<n;i++) a[i]=gi;
    for(int i=0;i<n;i++) b[i]=gi;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=i;j++) c[i]=max(c[i],a[j]+b[i-j]);
    for(int i=0;i<n;i++) pint(c[i]),pc(‘ ‘);pc(10);
    cerr<<clock()<<"ms\n";
}

虽然看起来只有10w多个数,我们还是加一波输入输出优化试试...

居然跑了10s。比原来还慢...这和预期不太相符啊...在windows上加了输入输出确实会变快,但是ubuntu下变慢了...大概输入输出少的时候最好还是不要加优化?

以下的测试全部基于输入输出优化,就假装加了优化跑的更快好了。

2. 手写stl

虽然这段代码非常短,但是我们还是使用了一个stl:max。我们来常数优化一波!

for(int i=0;i<=n;i++)
        for(int j=0;j<=i;j++)
            if(a[j]+b[i-j]>c[i])
                c[i]=a[j]+b[i-j];

测了测,跑了5.3s,比原来快了快一半!可喜可贺。

3. 把if改成三目?

这时候我想起了wys的教导:少用if,多用三目。

for(int i=0;i<=n;i++)
        for(int j=0;j<=i;j++)
            (a[j]+b[i-j]>c[i])
            ?(c[i]=a[j]+b[i-j]):0;

这样写跑了6.1s,居然比if还慢?

有理有据的分析:正常情况下,if改成三目会变快的原因是因为消除了分支预测,分支预测错误跳转的代价很大,而上面那段代码预测错误几率很小,所以if就比较快了。

4. 循环展开

为了写起来方便,首先我们将b数组反序,这样可以减少运算量,接下来把内层j循环展开。

int main()
{
    for(register int i=0;i<n;i++) a[i]=gi;
    for(register int i=0;i<n;i++) b[n-i]=gi;
    for(register int i=0;i<n;i++)
    {
        int*r=b+n-i;
        for(register int j=0;j<=i;j+=8)
        {
        #define chk(a,b,c) if(a+b>c) c=a+b;
        #define par(p) chk(a[p],b[p],c[i])
        par(j) par(j+1) par(j+2) par(j+3)
        par(j+4) par(j+5) par(j+6) par(j+7)
        }
    }
    for(register int i=0;i<n;i++) pint(c[i]),pc(‘ ‘);pc(10);
    cerr<<clock()<<"ms\n";
}

这样理论上cpu可以对中间的代码乱序执行,就是一次执行很多条,从而提高运行速度。

实测优化效果非常好,只跑了2.9s,比原来快了1倍多。

此外我还了解到openmp和cache blocking这两种优化方法,但是对程序提速不明显,这里就不提了,有兴趣的自行度娘。

5. Intrinsic

这是真正的黑科技了= =orz小火车

#include "immintrin.h"
#include "emmintrin.h"
static __m256i a_m[SZ],b_m[8][SZ];
static int a[SZ],b[SZ],c[SZ];
__attribute__((target("avx2")))
inline int gmax(__m256i qwq)
{
    int*g=(int*)&qwq,ans=0;
    (g[0]>ans)?(ans=g[0]):0;
    (g[1]>ans)?(ans=g[1]):0;
    (g[2]>ans)?(ans=g[2]):0;
    (g[3]>ans)?(ans=g[3]):0;
    (g[4]>ans)?(ans=g[4]):0;
    (g[5]>ans)?(ans=g[5]):0;
    (g[6]>ans)?(ans=g[6]):0;
    (g[7]>ans)?(ans=g[7]):0;
    return ans;
}
__attribute__((target("avx2")))
int main()
{
    const int n=131072;
    memset(a,-127/3,sizeof(a));
    memset(b,-127/3,sizeof(b));
    for(register int i=0;i<n;i++) a[i]=gi;
    for(register int i=0;i<n;i++) b[n-i]=gi;
    for(register int i=0;i<=n+5;i+=8)
        a_m[i>>3]=_mm256_set_epi32
        (a[i],a[i+1],a[i+2],a[i+3],
        a[i+4],a[i+5],a[i+6],a[i+7]);
    for(register int r=0;r<8;++r)
    for(register int i=0;i<=n+67;i+=8)
        b_m[r][i>>3]=_mm256_set_epi32
        (b[i+r],b[i+1+r],b[i+2+r],b[i+3+r],
        b[i+4+r],b[i+5+r],b[i+6+r],b[i+7+r]);
    __m256i zero=_mm256_set_epi32(0,0,0,0,0,0,0,0);
    for(register int i=0,lj;i<n;i++)
    {
        __m256i*r=b_m[(n-i)&7]+((n-i)>>3),
        qwq=zero; lj=(i>>3);
        for(register int j=0;j<=lj;j+=8)
        {
#define par(p) qwq=_mm256_max_epi32(\
qwq,_mm256_add_epi32(a_m[p],r[p]));
            par(j) par(j+1) par(j+2) par(j+3)
            par(j+4) par(j+5) par(j+6) par(j+7)
        }
        c[i]=gmax(qwq);
    }
    for(register int i=0;i<n;i++)
        pint(c[i]),pc(‘ ‘);pc(10);
    cerr<<clock()<<"ms\n";
}

这段代码有点长,这里解释一下原理。

大家都知道stl中有一个很好用的库叫bitset,它的原理是将32/64个bit(取决于字长)压成一个数(long),从而使常数/=32或64。

在intel部分指令集中,有类似的数据类型,可以将多个int/float/double等等压在一个128/256/512位的数据类型里,从而一起进行计算。

大致有三种数据类型:

__m128i __m256i __m512i

分别对应压在128/256/512位内。

我们可以对这三种数据类型中压的数进行并行计算!例如一个__m256i里可以包8个int。这些数据类型的方法有点多,intel提供了一个可以查找这些方法的页面:https://software.intel.com/sites/landingpage/IntrinsicsGuide/

实现起来相当于手写bitset,细节详见代码吧。

这段代码只跑了1.4s!又比循环展开快了一倍。

我实测了一下,在uoj上__m256i无法使用,__m128i只能使用部分指令,例如_mm_max_epi32这个指令就不支持......洛谷上可以正常运行。

时间: 2024-08-08 05:19:42

[黑科技]常数优化的一些技巧的相关文章

【黑科技】读写优化

读入优化: inline int read() { char ch; bool flag = false; int a = 0; while(!((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-'))); if(ch != '-') { a *= 10; a += ch - '0'; } else { flag = true; } while(((ch = getchar()) >= '0') &&am

常数优化技巧

今天让我们整理一下一些常数优化技巧: 1. 读入优化: #define sight(c) ('0'<=c&&c<='9') inline void read(int &x){ static char c; for (c=getchar();!sight(c);c=getchar()); for (x=0;sight(c);c=getchar())x=x*10+c-48; }//使用方法 read(x); 这是一直基于getchar的快速读入.相比大家都会,不说了. 2.

做OI题时的一些常用的常数优化小技巧

注意:本文所介绍的优化并不是算法上的优化,那个就非常复杂了,不同题目有不同的优化.笔者要说的只是一些实用的常数优化小技巧,很简单,虽然效果可能不那么明显,但在对时间复杂度要求十分苛刻的时候,这些小的优化对于帮助你成功卡常也是十分重要的.那么我们让切入正题吧. (1)inline放在自定义函数前 不要问为什么,加就行了!额,这个东西好像可以让你的函数有机会被计算机执行得稍微快一点,一般放在使用次数比较多的函数前,像check(),为sort()定制的CMP()等等,当然主函数前就不要放了...比如

【转载】史上最全:TensorFlow 好玩的技术、应用和你不知道的黑科技

[导读]TensorFlow 在 2015 年年底一出现就受到了极大的关注,经过一年多的发展,已经成为了在机器学习.深度学习项目中最受欢迎的框架之一.自发布以来,TensorFlow 不断在完善并增加新功能,直到在这次大会上发布了稳定版本的 TensorFlow V1.0.这次是谷歌第一次举办的TensorFlow开发者和爱好者大会,我们从主题演讲.有趣应用.技术生态.移动端和嵌入式应用多方面总结这次大会上的Submit,希望能对TensorFlow开发者有所帮助. TensorFlow:面向大

2017-12-27 每日黑科技

[碧桂园信管中心] [资讯与趋势] 1.如何评价大数据的未来? 收集.处理.分析数据确实是一件有意义的事,并将产生价值,但问题在于,我们能从大数据里离挖掘到多少价值? 大数据是真的炒作大过实际价值还是一些人目光短浅看不到价值? 大数据产业发展的要面临的挑战: 一是对数据资源及其价值的认识不足. 二是技术创新与支撑能力不够. 三是数据资源建设和应用水平不高. 四是信息安全和数据管理体系尚未建立. 五是人才队伍建设亟需加强. 评论:本人认为大数据概念在现阶段依然有炒作大于实际价值的嫌疑,从2010年

百度再出Lens黑科技!用Paddle Mobile实现类人眼视觉AI能力

你知道吗?人类有 70%的信息获取来自于视觉.但目前存在两个问题: ①人眼本身只能看到物理世界,无法看到其背后复杂的信息世界: ②人类的记忆力有限,视野有限,于是会出现看了就忘.视野窄等各种问题. 科幻片里常见这样一种"人肉外挂":通过一系列的技术改造,主人公(比如史塔克)眼前出现的一切物体都会被自动识别,什么名称.用途.用法用量,全都可以秒速反馈回来,并被即时载入到记忆中,过目不忘. 如今,百度识图在百度 App 和简单搜索 App 上推出的最新版,通过应用Lens技术把这种科幻场景

2017黑科技趋势最具看点的十大新品

腾讯数码讯(Human)作为一年一度的全球消费电子市场风向标,今年同样在拉斯维加斯举办的CES 2017消费电子展,依然吸引了一大批全球各个领域的厂商参展,从科技巨头到初创小团队.从传统汽车厂商再到家电企业,似乎所有能与科技沾边的公司都希望能在CES 2017上好好展示一次自己的风采. 其实每年的CES都有一些明星产品给我们留下深刻的印象,今年的也不例外.而这些明星产品不仅仅只是单单一款产品,更是代表了各自行业在进入到2017年之后的一个发展趋势和方向.而就将这样的变化能否成为未来的主流.或只是

今年黑科技趋势最具的五个看点

CES 2017年人工智能引爆全球最火黑科技盛会 CES 2017 1月5日-8日在美国拉斯维加斯举行,数千家企业.几十万人将参与到这次科技的狂欢秀中.本文为埃森哲技术总监带来的关于本年度CES 的5大看点.他认为:人工智能将统治本年度的CES,变得无处不在.另外,他还分析了智能助理.物联网安全.虚拟现实等多个领域在本届大会上的表现. 2017 年国际消费电子展(CES 2017)将于1月5号拉开帷幕,在这个荒漠之城举办为期5天的展会,保守估计会吸引超过177000名参会者. 这也是一年之中唯一

试读—增长黑客,创业公司必知的“黑科技”

概述 刚一看到书名,最引起注意的是黑客两个字,那个带着神秘色彩,让无数程序员羡慕嫉妒恨的角色.但仔细一看,增长黑客,创业公司必知的"黑科技",是讲公司如何以切实的依据.低廉的成本.可控的风险来达成用户增长.活跃度上升.收入额增加等知识及案例的,这对于初创公司又没有充足的资金去燃烧以改变用户习惯的情况无疑是雪中送炭.指北之针. 什么是增长黑客? 本书适合哪些读者? 增长 靠原始积累实现增长的时代已经过去,也不适合互联网.移动互联网.互联网+的模式.我们经常能看到类似的新闻"某公