程序调优是每个菜鸡程序员慢慢变成大神的必修课,程序调优关乎到用户体验,服务器的效率等诸多方面,我在此总结部分程序调优经验供大家学习和参考.
2.1 sql调优
在程序的开发过程中免不了使用数据库,并且在逻辑不是很复杂的程序中,程序速率的瓶颈往往是sql语句的执行,因此sql调优是首先要介绍的.
2.1.1 合理创建和利用索引
关系型数据库中索引相当于数据表的目录,数据库有了目录的指示才不用将所有的数据挨个进行排查,理解了这句话,索引的基本创建规则实际上就理解的差不多了,想象一下,数据表是一本书,你需要怎么去建立他的目录才能够让你最快的找出你想要的内容?那当然是根据你的查询或者匹配条件,你的索引与条件越符合,那么查询的速度就会越快(在查询的条件中应该尽量避免使用函数).
当创建了一个索引时,数据库需要在进行修改的时候去维护它,所以索引可以增加查询速度,但是会降低插入速度,并且索引也需要硬盘空间,数据量越多,索引所包含的东西越多,索引所占的空间就会直线上升,因此在建立索引之前需要考量它是否真的有必要.
如果你的查询是完全匹配上的索引并且感觉到查询速度依然不如意的话,那么可能是因为数据库的信息碎片所导致的,简单来说就是数据库的存储数据因为长时间的增删改操作导致数据被分到了不同的区域导致查询时间较长,遇到这种情况可以采用删除并重建索引的方式进行解决(所以当向数据库中导入数据的时候应该先导入数据再建立索引,以防止碎片的产生).
2.1.2 sql语句的自身优化
sql语句的好坏直接决定了查询的时间和硬件的开销,一个好的sql语句的条件应当尽量与索引相符,如果有多条件或者联合查询的时候,尽量先将数据筛选的足够精确,然后再进行其他的操作。。。
优化还有很多方面具体可以参考:https://www.cnblogs.com/kkxwze/articles/10844569.html
2.1.3 尽量减少与数据库的连接和交互次数
在程序与数据库的交互过程是影响程序效率中很重要的一个环节,每次访问数据库都需要大量的开销,因此在访问数据库较为频繁的程序中尽量保持数据库的连接,建立连接池而不是用完就将数据库的连接销毁,这样可以在一定程度上保证程序的效率,避免资源的浪费(c#语言中DbConnection类会自动维护数据库的连接,因此只要不刻意销毁继承DbConnection类所创建出的对象就能够达到数据库的连接重复使用的效果)。
就算程序与数据库始终建立连接,程序访问数据库依然会占用时间和资源,因此需要尽量减少数据库的访问次数,适当利用缓存来增加程序的响应速度,在某些情况也可以将多条执行的语句合并成一句来进行统一执行。
2.1.4 合理分库分表
分表:
众所周知,关系型数据库的数据表中的数据如果变得很大的话就会变得很难维护,但是一般数据的增删改查操作大多数都是对近期的数据进行处理,因此可以根据业务来进行分表处理,将不同时间段的数据放入到不同的表中以减少每张数据表中的数据量,加快语句执行速度,并减少维护成本。
分库:
数据库的的功能是存储数据,数据需要用来做运算和统计分析也需要用来进行展示,因此,我们可以根据数据的不同使用功能来进行数据库的拆分,使得运算和统计互不影响。
具体分库分表可以参照的思路:https://www.cnblogs.com/butterfly100/p/9034281.html
2.2堆,栈,装箱
代码在编写的时候应该尽量考虑哪些代码需要消耗系统资源和时间,并尽量考虑简化这些代码,使得程序的运行效率更上一层楼。
对于c#的代码来说操作可以分为以下几类型:
1.创建对象
2.对象赋值
3.逻辑处理
4.销毁对象
其中逻辑处理和对象的销毁数据代码逻辑优化,在下一步接收,这一步主要介绍对象的创建和赋值的优化。
首先,创建任何一个对象的实体或者对象的指针都是需要消耗时间和系统资源的。
在了解如何优化之前,我们首先需要了解堆栈的概念与特点。
首先我们要了解在创建一个对象时,对象是放在哪里的,对于c#来说,创建的对象有两种,一种是值类型的对象,一种 是引用类型的对象,那么顾名思义,我们访问值类型的对象时访问的是对象的内容,而访问引用类型的对象时访问的是对象的指针 ,然后通过指针去访问内容.
值类型中的数据和引用类型的指针是会被放入栈中,而引用类型的内容会被放入 到堆中,也就是说系统在正常情况下访问的都是栈,如果是值类型那么就会直接得到 数据,如果是引用类型那么就会得到指针,然后再通过指针访问内容,这样说来,栈中的数据读取速度会明显快于堆中数据读取速度的.所以我们得到第一条优化规则
--声明,调用堆中的值类型数据的速度会快于引用类型数据的速度(引用类型的数据总是放在堆中,但是值类型的数据如果是直接调用数据的话就是放在栈中,如果通过引用类型进行调用的话就是放在堆中)
了解了这些之后我们还应该了解到引用类型的数据和值类型的数据在赋值上的区别,
引用类型的数据在赋值时会直接在栈中开辟一份空间用来存储栈中的数据,当修改数据时,会直接修栈中所存储的数据,
而引用类型的赋值分为两种,一种是将指针直接指向已有的堆中,另外一种是在堆中开辟新的空间存放数据,再用指针指向它.
当一个方法正常传参时,值类型的数据会直接拷贝一份到新的方法中,所以在修改方法中值类型的数据时原数据不会发生改变,而当一个引用类型的数据作为方法的参数时拷贝过去的是指针,所以如果修改指针所指的堆中的数据则会影响到原有的数据.(这里可以很明确的知道方法传参会在栈中新开辟空间,而不会影响到堆(不适用ref或者out等关键字,且string除外))
--(string虽然是引用类型的数据,但是它会将值而不是指针作为参数来传递.因此,当有过长的string需要传递时最好先将其进行封装)
,因此
--无论堆中的数据量有多大,引用类型和值类型的传参效率是差不多的.
我们现在再看一下循环创建的效率.我们看一下下面几段代码
代码段1:
int j = 0;
for (int i = 0; i < 10;i++ )
{
int k = 3;
j = j + k;
}
代码段2:
int j = 0;
int k = 3;
for (int i = 0; i < 10;i++ )
{
j = j + k;
}
代码段2的效率是要明显高于代码段1的效率的,因为代码段1在不断重复创建和销毁栈中的k.而代码段2则创建一次,就不需要进行任何的其他操作,这个是正常的逻辑,代码也应该像2这样写,但是作为开发者的我们还应该看到一些其他的东西,
代码段一中的k随着for的执行完毕会被销毁,而代码段2中的k则在方法的生命周期内都会一直存在,虽然加快了运算速度和效率但是依然存在浪费内存的情况,并且存储于栈中的数据无法手动消除,因此在某些极端的情况下可以考虑代码段1的编写方式.
在代码的编写过程中,经常会用到StringBuilder类,很多人只知道这样会增加系统性能,但是不知道为什么,这是因为string字符串是存放在堆中,并且长度是固定的,所以string每做一次拼接都会在堆中新建一个string对象,然后改变指针的指向,那么这个过程是需要消耗系统性能的,所以我们在处理这类拼接的字符串的工作时,用StringBuilder类作为辅助来减少系统开销.
我们再看一下下面的一段代码
,
int i = 5;
object o=i;
int j=(int)o;
这里两个变量,i是值类型,o是引用类型,那么以上的代码发生的实际情况是:运行是程序会先在栈上创建一个i=5的对象,然后在堆上创建一个包含值(5)的对象,o的值是对该对象的一个引用,最后再在栈上创建一个j=5的对象,这个过程就是装箱和拆箱,装箱和拆箱的操作会降低性能,一次装箱拆箱的操作的开销或许微乎其微,但是执行千百次这样的操作不仅会增大程序本身的开销,还会创建众多的对象,增加服务器负担.
2.3 垃圾回收与代码逻辑优化
首先c#与java一样,堆中的垃圾有自动回收机制,但是至于什么时候回收就要看天意了,而正是因为这种不确定性导致程序可能在运行的时候占用的内存无法得到快速的释放,从而影响设备性能,严重的还会导致内存溢出的错误,因此在编写代码的时候发现堆中存放的数据占用空间较多时可以手动清理掉堆中的数据(像datatable用完了之后可以调用clear方法,来清空堆中的数据以保证内存的充足)并且在适当的时候还可以手动调用GC来进行对没有指针指向的堆中的数据进行垃圾的清理工作..
其次,很多我们所用到的高级语法的底层机制其实都是低级语法,只不过在语法上进行了封装,因此虽然lambda表达式或者linq之类能够带给我们很多方便,但是并不能增加系统运行效率,,因此不能迷信这些高级语法,很多时候自己去亲力亲为,掌握自己代码的时间复杂度以及空间复杂度,并在自己的代码基础上进行优化反而是更好的选择!