常见性能优化小技巧原理
一、多用有序数组+折半查找
金山卫士开源后立马招来各种批判,其中有一段批评金山卫士源码说太多if else而不用表驱动使得代码可读性不高,笔者看了下大致如下:
TCHAR szFolderPath[MAX_PATH + 1] = {0};
// MichaelPeng: if else太多,应做成表驱动
if (0 == _tcsicmp(szVariable, _T("%Desktop%"))) {::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_DESKTOP, 0);
} else if (0 == _tcsicmp(szVariable, _T("%Internet%"))) {::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_INTERNET, 0);
} else if (0 == _tcsicmp(szVariable, _T("%Favorites%"))) {::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_FAVORITES, 0);
} else if (0 == _tcsicmp(szVariable, _T("%CommonDocuments%"))) {::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_COMMON_DOCUMENTS, 0);
}
.......
算上中间省去的代码这代码这一共有三十项比较,代码写成这样的确不雅。曾经在和朋友讨论提出使用map,通过以字符串大小做key,这样查找效率会提高很多。不过笔者认为最好的方法还是结构体数组,然后通过字符串大小规则排序,然后使用折半查找法保证效率和map一致(其实略高于红黑树结构的map,严格的说map的查找效率只是接近lgN,具体可以参考红黑树数据结构),这样做能保证查找效率的时候还能够节约内存,虽说这地方因为数据量小而体验不出明显节约内存。
为什么数组比map更节约内存?因为map是采用红黑树数据结构,每个节点都是一个单独的堆节点,考虑到每次申请内存时多余的堆数据结构和指向其他节点的指针,当节点数达到一定时候内存占用量就会表现越加明显。
其实可以做一个简单的实验,分别写两个程序测试;
void main()
{char *p = new char[100000];getchar();
}void main()
{ for (int i = 0;i <100000;i++)char *p = new char;getchar();
}
上面两个程序运行起来你会从任务管理器发现后者占内存明显比前者大,其实是一样的道理。类似这种写法在开源的duilib里面也存在不少,对话框上空间创建时会根据各种名字判断来做相应的操作,很多比较也是不断的if else,但是这种方法并不是什么时候都适用的,考虑到数组增加元素的麻烦性,如果元素需要不断增加删除,那么map就是更好的选择,另外如果能预先知道元素的查找频率,得知极少数元素查找频率极高而大部分元素极少查询,那么用表驱动,将查找频率最高的元素放在首位按照无序遍历也是不错的选择。
笔者曾经开发一款安全软件,由于该软件启动时会搜素本机上可保护的软件列表,同时能监控进程启动时判断该程序是否需要被保护起来(主要是防止进程被打开,注入等操作),但是软件携带的保护列表数以千计,前人在开发过程中或许是当时软件数目较少于是没做排序直接放到vector中,然后每次有进程启动都要遍历数组而效率很低,但是按照自己的规则进行排序后以折半查找方式则很大幅度减少资源以至于启动时搜索可保护软件列表明显提高一个档次。
二、巧用哨兵元素
问题由来:对于一个有N个元素的无序数组a[n],判断其中是否有key这个值,写一个函数。
很多人拿到这个问题直接写出如下代码
BOOL GetIndexBkey(int a[],int nSize,int nKey)
{for (int i = 0; i < nSize;i++){if (nKey == a[i]){return TRUE;}}return FALSE;
}
这么写其实也能达到目的,但是细细看来每一次循环的时候都会比较两次i < nSize和nKey == a[i]。事实上我们可以用一种很巧妙的方法减少一次比较,这样以来在数据量很大的时候效率就会较明显提升,如果这个函数调用相当频繁那么优化效果还是很明显,具体如下:
BOOL GetIndexBkey(int a[],int nSize,int nKey)
{int nlastIndex = nSize-1;//首先判断最后一个元素是不是关键字,如果是直接返回if (a[nlastIndex] == nKey){return TRUE;}//保存数组中最后一个元素,同时将最后一个元素赋值成keyint nSavelastValue = a[nlastIndex];a[nlastIndex] = nKey;int i = 0;while (a[i]!=nKey){i++;}//由于最后一个元素为key,所以上面的循环必定有出口//当循环跳出后如果此时索引指向最后一个元素,说明查找失败//反之成功a[nlastIndex] = nSavelastValue;if (i < nlastIndex){return TRUE;}return FALSE;
}
通过以上写法可以讲比较次数降低到N+2,同时增加4次赋值与一次减法操作,比起之前2N次比较工作量明显小了许多,同时CPU执行的指令也会大幅度减少,效率自然就更高了。
三、多用参数引用化
相信读者一眼就能看出来以下代码效率上的区别,不加引用程序会为参数创造出一个临时对象,并且在函数退出时析构,而换成引用后则省去这一步骤。如果这样的函数调用太过频繁这种写法节省出来的性能开销还是相当可观,但是很多人的代码并没有太过注意这一点。
void SetFileInfo(ThreadSortData fileinfo)
{ThreadSortData fileinfo1 = fileinfo;
}void SetFileInfo1(const ThreadSortData &fileinfo)
{ThreadSortData fileinfo1 = fileinfo;
}
其实对于这种简单的结构体这种引用化参数并没带来太多意义上的性能节省,但是对于下面这个例子相信只要试过的读者肯定记忆极深,看代码
void SetlistInfo(list<int> listTest)
{return;
}int _tmain(int argc, _TCHAR* argv[])
{list<int> listtest;for (int i = 0;i < 1000000;i++){listtest.push_back(i);}SetlistInfo(listtest);return 0;
}
笔者在VS2005 debug版本下测试,当程序执行到SetlistInfo内部时内存占用量立刻翻倍,而且由于大量拷贝操作使得进该函数时耗时间和外面的for循环一样多,但是一旦参数引用化后进入SetlistInfo函数简直毫无压力。也就是说进入该函数的时候程序也重新拷贝一份list,并且复制listtest内部值到这个临时对象,由于listtest元素量特别多以至于这个拷贝耗时间和空间几乎让人无法容忍,到这里相信大家都能明白参数引用化可以带来多大的性能节省。
四、迭代器自加的正确写法
关于STL的教程很多都提到过迭代器自加应该写成++iter而不是iter++,对于类似整形数据来说其实这样写根本没什么区别,但是对于迭代器来说效率上却区别很大。下面通过反汇编代码看看加号写在前面和后面的区别(以Visual C++ 2005 Debug版本为例):
list<ThreadSortData>::iterator iter = listtest.begin();
004136EE lea eax,[ebp-64h]
004136F1 push eax
004136F2 lea ecx,[ebp-44h]
004136F5 call std::list<ThreadSortData,std::allocator<ThreadSortData> >::begin (411276h)
004136FA mov byte ptr [ebp-4],2 iter++;
004136FE push 0
00413700 lea eax,[ebp-144h]
00413706 push eax
00413707 lea ecx,[ebp-64h]
0041370A call std::list<ThreadSortData,std::allocator<ThreadSortData> >::_Iterator<1>::operator++ (41122Bh)
0041370F lea ecx,[ebp-144h]
00413715 call std::list<ThreadSortData,std::allocator<ThreadSortData> >::_Iterator<1>::~_Iterator<1> (41155Fh) ++iter;
0041371A lea ecx,[ebp-64h]
0041371D call std::list<ThreadSortData,std::allocator<ThreadSortData> >::_Iterator<1>::operator++ (411389h)
而在++iter第一个call当中调用的是以下代码(部分简化并没有实际贴全):
_Myt_iter _Tmp = *this;
00413FFC mov eax,dword ptr [ebp-14h]
00413FFF push eax
00414000 lea ecx,[ebp-28h]
00414003 call std::list<ThreadSortData,std::allocator<ThreadSortData> >::_Iterator<1>::_Iterator<1> (41163Bh)
00414008 mov dword ptr [ebp-4],1
++*this;
0041400F mov ecx,dword ptr [ebp-14h]
00414012 call std::list<ThreadSortData,std::allocator<ThreadSortData> >::_Iterator<1>::operator++ (411389h)
相信即使是完全没看过反汇编的朋友此时也能明白上述代码创建了一个临时对象,之后再去下一个值,然后析构之前创建的临时对象。但是对于++iter来说只是在地址0041371D处调用了一个call,这个call跟踪进去发现其实代码如下:
++(*(_Mybase_iter *)this);
00413F73 mov ecx,dword ptr [this]
00413F76 call std::list<ThreadSortData,std::allocator<ThreadSortData> >::_Const_iterator<1>::operator++ (411050h)
return (*this);
00413F7B mov eax,dword ptr [this]
相对++写在后面,此时少了一步构造析构的操作,当然这部分操作带来的性能节省将会随着对象的构造析构的复杂性或者调用频率而产生不同的效果。
未完待续...
转载于:https://www.cnblogs.com/mod109/p/3886589.html
常见性能优化小技巧原理相关推荐
- php get 传循环出来的参数_PHP性能优化小技巧
PHP性能优化小技巧: 1. foreach效率更高,尽量用foreach代替while和for循环. 2. 循环内部不要声明变量,尤其是对象这样的变量. 3. 在多重嵌套循环中,如有可能,应当将最长 ...
- Java中String对象的replaceAll方法调用性能优化小技巧
Java中String对象的replaceAll方法调用性能优化小技巧 0x01 Java中String对象的replaceAll方法调用性能优化小技巧 1.1 What? 1.2 Why? 1.3 ...
- JavaScript 工作原理之十一-渲染引擎及性能优化小技巧
原文请查阅这里,略有删减,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland. 本系列持续更新中,Github 地址请查阅这里. 这是 JavaScript 工作原理的第十一章. 迄 ...
- JavaScript 工作原理之十一-渲染引擎及性能优化小技巧 1
原文请查阅这里,略有删减,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland. 本系列持续更新中,Github 地址请查阅这里. 这是 JavaScript 工作原理的第十一章. 迄 ...
- python读取大文件-python如何读取大文件以及分析时的性能优化小技巧
在二代.三代测序背景下,分析人员难免会遇到解析超过1G.或者10G以上的文件.这里将给大家简单介绍下如何用python读取大文件,并给大家提两个优化代码的小建议. 首先,python 读取GB级大文件 ...
- unity性能优化小技巧【一】
1.了解设备硬件信息 //设备的模型GetMessage("设备模型", SystemInfo.deviceModel);//设备的名称GetMessage("设备名称& ...
- 亿级PV,常见性能优化策略总结与真实案例
作者:晓明 来自:美团技术团队 0 题记 美团网是国内最大的O2O服务平台,虽然经常面临高并发.大流量等问题,但在用户体验优化上美团APP仍被众多IT同行所推崇,他们在性能优化方面积累的宝贵经验尤其值 ...
- OI常用的常数优化小技巧
注意:本文所介绍的优化并不是算法上的优化,那个就非常复杂了,不同题目有不同的优化.笔者要说的只是一些实用的常数优化小技巧,很简单,虽然效果可能不那么明显,但在对时间复杂度要求十分苛刻的时候,这些小的优 ...
- java常见性能优化_十大最常见的Java性能问题
java常见性能优化 Java性能是所有Java应用程序开发人员都关心的问题,因为快速使应用程序与使其正常运行同等重要. 史蒂文·海恩斯(Steven Haines)使用他在Java性能问题上的个人经 ...
最新文章
- GitLab 8.7发布
- Android UI编程之自定义控件初步(上)——ImageButton
- relationship between freedom,potential, risk
- nancy框架安装并使用
- Eclipse正确配置Tomcat之后仍然报错Type Target runtime Apache Tomcat v8.0 is not defined解决方式
- 前端服务器获取js文件偶尔慢_我所认识的前端性能优化
- boke练习: spring boot: security post数据时,要么关闭crst,要么添加隐藏域
- “约见”面试官系列之常见面试题第十五篇之jsonp(建议收藏)
- pytorch 训练过程学习率设置衰减
- 20190508——python基础(if...in...循环语句、while循环、两种循环对比)
- java 3d文字旋转_3d多物体点旋转
- 毕业学生,C语言面试十大常见问题,提升面试分数
- POSIX和CMSIS接口
- ios 图片裁剪框架_iOS开发图片剪切
- 二极管ss14测量_二极管如何测量好坏
- python3d动画效果_使用Matplotlib 3D实现三维波浪动画
- 新PC如何在不激活系统的情况下查看各项参数(Win10)
- Silverlight 2教程(四):Chiron.exe:Silverlight 2打包和动态语言部署工具
- MacBookPro 2015电池召回
- java 图像特效之黑白 浮雕和底片
热门文章
- 贵州大学计算机科学研究所,贵州大学计算机科学与技术学院
- 2020下半年python二级考试时间_2020年下半年计算机等级考试报名通知
- c语言goto语句用法_硬件工程师必知的10个C语言技巧
- currenttimemillis 毫秒还是秒_亿级数据毫秒级查询!ElasticSearch是怎么做到的?
- 帆软报表多行多条数据写入表_在线报表FineReport中多数据集如何实现层式报表...
- python基础应用_【复习】mysql+python基础应用(20190815)
- html不用点击自动执行,页面自动执行(加载)js的几种方法
- 从底部上滑失灵_宝鸡终于也有超火的高空玻璃水滑啦!就在玉池公社!8月10日见~...
- 服务器操作系统字符集,设置服务器字符集
- java encode in ansi_Java应用中的编码问题