首页 > 其他分享 >常数优化

常数优化

时间:2023-01-15 10:12:32浏览次数:36  
标签:... 12 mat int 数组 常数 优化 dis

数据类型

  • 显而易见地,越小的型的运算越快。

  • 大体来讲 long long 的常数比 int 大一倍,但 __int128 的比 long long 大一倍不止(因为没有 128 位机,故 __int128 的实现是“不自然”的)。

  • shortchar 的表现特别差,不论什么环境。原因?它们在任何算术运算(哪怕是纯 short/char 环境)都会被强转成 int

条件语句

  • if/else,switch,?: 的逻辑各不相同。

  • if/else 语句中,如果 if 的内容一直对,那么就会很快。

  • ?: 中的 : 优先级比 , 高,故如果是 ?...,...:...,... 那么左边没问题,但右边只会执行第一个逗号之前的,换言之,语义为 ?(...,...):(...) , ...,相当于 ?(...,...):(...); ...;

中央寄存器

  • 大体来讲,我们的程序使用的内存都是运行内存,即高速缓存。

  • 但高速缓存是分级的。据 \(\text{P4604 [WC2017]挑战}\),一级 Cache 的大小大约为 \(256\) 个 int

  • 优先度越高的 Cache,空间越来越小,但访问越来越快。具体地,

内联函数

  • C++ 中的 inline 关键字允许我们将函数设置为内联的。这一函数的范畴是广泛的,至少允许对 operator,namespace 甚至于结构体构造函数使用。

  • 内联的本质是在调用函数时不进行实质的函数调用,而是将函数的代码复制到调用处执行其功能,省却了函数初始化(主要是开栈帧之类的)和退出的时间。

  • 对于有复杂行为的函数,将其定义为内联会导致负优化,但实际上编译器会自动忽略 inline 关键字,所以该关键字可以随便用。

  • 特别地,有说法认为递归函数不应定义为内联,但在实际实践中递归函数加 inline 仍然是正优化,虽然与非递归函数相比差了一些。

  • 效果大约是常数 \(/2\)。

循环展开

  • NOI 系列比赛所使用的的评测机芯片是 i7 [email protected],该芯片为 \(12\) 线程。

  • 但在实际的程序运行过程中,单个循环的计算任务只能分配给一个线程。

  • 所以我们可以考虑将最后一层的循环展开,这里举 Floyd 算法为例:

il void floyd(){
void work(){
	For(k,1,n)
		For(i,1,n){
			ri int j=1; 
			for(;j+11<=n;j+=12){
				if(dis[i][j]>dis[i][k]+dis[k][j])
					dis[i][j]=dis[i][k]+dis[k][j];
					
				if(dis[i][j+1]>dis[i][k]+dis[k][j+1])
					dis[i][j+1]=dis[i][k]+dis[k][j+1];
					
				if(dis[i][j+2]>dis[i][k]+dis[k][j+2])
					dis[i][j+2]=dis[i][k]+dis[k][j+2];
					
				if(dis[i][j+3]>dis[i][k]+dis[k][j+3])
					dis[i][j+3]=dis[i][k]+dis[k][j+3];
					
				if(dis[i][j+4]>dis[i][k]+dis[k][j+4])
					dis[i][j+4]=dis[i][k]+dis[k][j+4];
					
				if(dis[i][j+5]>dis[i][k]+dis[k][j+5])
					dis[i][j+5]=dis[i][k]+dis[k][j+5];
					
				if(dis[i][j+6]>dis[i][k]+dis[k][j+6])
					dis[i][j+6]=dis[i][k]+dis[k][j+6];
					
				if(dis[i][j+7]>dis[i][k]+dis[k][j+7])
					dis[i][j+7]=dis[i][k]+dis[k][j+7];
					
				if(dis[i][j+8]>dis[i][k]+dis[k][j+8])
					dis[i][j+8]=dis[i][k]+dis[k][j+8];
					
				if(dis[i][j+9]>dis[i][k]+dis[k][j+9])
					dis[i][j+9]=dis[i][k]+dis[k][j+9];
				
				if(dis[i][j+10]>dis[i][k]+dis[k][j+10])
					dis[i][j+10]=dis[i][k]+dis[k][j+10];
					
				if(dis[i][j+11]>dis[i][k]+dis[k][j+11])
					dis[i][j+11]=dis[i][k]+dis[k][j+11];
			}
			for(;j<=n;++j)
				if(dis[i][j]>dis[i][k]+dis[k][j])
					dis[i][j]=dis[i][k]+dis[k][j];
		}
}
  • 实测可以在 \(2\times 10^3\) 的数据下跑过 \(90\%\) 的点。这就是因为这一操作使得 \(12\) 个线程同时运行,但需要注意的是并没有复杂度 \(\dfrac{O}{12}\) 的神奇效果,在已有的实验中即使展开到 \(12\) 个,也只能使运行时间 \(/2\sim /3\).

  • 类似地,自增(i++,++i)等操作也不利于多线程的充分利用,if 的表现不如没有 defaultswitch(上面的扫尾工作就可以用 switch 解决),以及如求和时开 \(12\) 个 sum 的效果就要比开一个更好,开 sum1,sum2,... 比开 sum[12] 要好。

访问连续性

  • C++ 中的数组本质上是一个数组头地址+平移下标个位置来找对应的元素,显而易见地,这一平移越短(可以从上次的平移结果继续),访问越快。

  • 并且,高速缓存在运行中并不是“用到哪个取哪个”,而是将包含目标下标的一段全部取过来。所以这可能可以省去去内存中找的时间。

  • 从而多维数组的定义顺序是有一定意义的。举一个简单的例子,在倍增求 \(lca\) 算法中我们会用到一个倍增的 fa 数组,符合常人想法的应当是 fa[maxn][log],表示从 \(i\) 出发走 \(2^j\) 步到哪里。

  • 但如果我们仔细考察的话,会发现如果把这个数组倒过来变成 fa[log][maxn] 然后跑一遍 dfs 处理出 fa[0][i] 之后用 For 来形如 dp 地从本层推下一层,会有好得多的下标连续性(因为都是在 fa[step] 这个数组里。多维数组的实际存储方式,以这个为例,是 logmaxn 的数组首尾相接)。

  • 类似地,考察矩阵乘法中的三重循环:

mat operator*(mat A,mat B){
    mat ret=mat(A.n,B.m);
    for(int i=1;i<=ret.n;++i)
        for(int j=1;j<=ret.m;++j)
            for(int k=1;k<=A.m;++k)
                ret.v[i][j]=(ret.v[i][j]+A.v[i][k]*B.v[k][j])%mod;
    return ret;
}
  • 在这种写法中,ret.v[i][j]A.v[i][k] 的下标是连续访问的,但 B.v[k][j] 显然不是,所以我们可以改变循环顺序(容易证明,改变后的枚举总情况是和改变前全同的),得到如下的写法:
mat operator*(mat A,mat B){
    mat ret=mat(A.n,B.m);
    for(int i=1;i<=ret.n;++i)
        for(int k=1;k<=A.m;++k)
            for(int j=1;j<=ret.m;++j)
                ret.v[i][j]=(ret.v[i][j]+A.v[i][k]*B.v[k][j])%mod;
    return ret;
}
  • 本写法具备完美的下标连续性,可以优化大约一半的常数。

  • 另,之所以较小的数据类型运算较快,不仅是运算的原因,也有访问的原因(更紧凑)。

  • 注意,有可能滚维比展开快:乍一看滚维是时间换空间的手法,但事实上在三级乃至更低级的 cache 意义下,可能卡进去了,故反而快了。

STL

  • queuestackvector 快(在功能相同的时候),list 和他们差不多,但实际上哪怕开了 O2 也还是不如手写。不过,在需要循环队列的时候(说实话用到这个的情况很少,除非卡空间,否则用到这个基本就代表着 T 了),手写的没有 STL 快。

  • 不要用 deque!!!也不要用 deque 来实现 priority_queue(指声明里面的第二项,容器)!!!作为对比,上面提到的那几个 STL 基本都是能用的。

  • bitset 在不使用位运算的时候似乎是负优化,尽管因为“窄”比较好卡 cache,但 bitset 的访问是“不自然”的。在使用位运算的时候,不如手动压位(实测 __int128 的表现至少比 bitset 好一倍,即时间少一半)。

标签:...,12,mat,int,数组,常数,优化,dis
From: https://www.cnblogs.com/weixin2024/p/17053120.html

相关文章

  • mysql索引优化-01
    1.1索引是什么?  mysql官方对于索引的定义:可以帮助mysql高效的获取数据的数据结构。  mysql在存储数据之外,数据库系统中还维护着满足特定查找算法的数据结构,这些数据......
  • mysql like性能优化
    网上很多优化like的方法,无非下面几种,抄来抄去的。我用213万条数据,每条数据50个字段左右(用的真实的生产环境的mysql数据库,和真实的生产环境的数据),做了性能测试;时间记录的次数......
  • 线段树优化建图学习笔记
    使用场景:单点向区间,区间向单点或区间向区间建边。实现原理:用线段树的一个节点管辖一段图上区间的顶点。实现步骤:将原图中的顶点拆点(理论上,实际代码中不需要),出点和入点......
  • 【Redis实战专题】「性能监控系列」全方位探索Redis的性能监控以及优化指南
    Redis基本简介Redis是一个开源(BSD许可)、内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合等数据类型。内置复......
  • 如何通过物联网网关感知工业设备故障,优化设备维护体验?
    传统的工业生产中,设备维护一直存在效率低下的问题。传感的设备管理模式无法实时感知设备运行状态,往往在设备故障停机之后才联系工程师进行维护,严重影响生产效益和订单交付;对......
  • 【Elastic Search】优化检索
    查询(query)与过滤(filter)的区别 如下为检索优化方案(部分内容有重复)https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.ht......
  • jQuery事件(事件委托/优化添加删除表格记录/事件委托delegate中的this对应关系)
    视频为什么要用事件委托:新增的dom元素没有对应点击事件。子元素的事件交给父元素来代为处理。父元素要知道是哪个子元素发生的。<!DOCTYPEHTML><html><head><meta......
  • tomcat调优 tomcat配置优化
    1.修改内存/jvm配置调整前JAVA_OPTS="-Xms1024m-Xmx4096m-Xss1024K-XX:PermSize=512m-XX:MaxPermSize=2048m"调整后JAVA_OPTS="-Xms2048m-Xmx2048m-Xss1024K-XX:Perm......
  • 前端页面加载速度优化解决方案
    随着前端各种框架的日益完善,一些基本的性能优化和加载优化都已经很完善了,但是有些必要的优化还是得开发者自己去做。vue.js是一个比较流行的前端框架,与react.js、angular.js......
  • C++ 算法进阶系列之从 Brute Force 到 KMP 字符串匹配算法的优化之路
    1.字符串匹配算法所谓字符串匹配算法,简单地说就是在一个目标字符串中查找是否存在另一个模式字符串。如在字符串ABCDEFG中查找是否存在EF字符串。可以把字符串ABCDE......