关于并发你真的了解吗?(三)

本文仅代表带个人观点及理解,本人只是一个编程小菜鸟,如果有不对的地方。请大佬轻喷!

  前言:很多程序员认为像“并发”这么高级的内容离自己很遥远甚至是毫无关系。当我听到这个想法的时候,说实话我很惊讶。实际上并发处理这个词语是与程序员日常工作中所编写的程序息息相关的。试想一下,如果一个程序原本响应速度平均1秒(举例而已,大佬们不要认真)。而由于工作中不注重代码规范,性能等问题。导致响应速度变成了2秒,那么您整体的并发处理还能保持原有的速度吗?要知道每个应用程序池的线程数量是有限的如果单个线程响应速度变慢了一倍,不仅延长了后续其他列队的处理。还有可能会导致列队上限从而引发响应超时,无法访问等问题。(不清楚ISS的可以查看 《关于并发你真的了解吗?(一)》 http://www.cnblogs.com/xinwuhen/p/7049093.html)而这个效率最少降低了一倍以上,一倍是什么概念。这导致公司为了处理高并发问题可能要再投入一倍的财力来搭建机房,这导致原本可以处理的并发数量。“现在”变成了各种访问超时,无法访问!本文将为您介绍日常工作中如何优化性能和日常编程过程中"如何提高并发处理效率"(提高响应速度间接等于提高并发处理能力嘛)。

本文按照两部分来介绍开发过程中需要注意的地方,第一部分总体说明,第二部分抽出其中一项:代码质量来重点说明一下。

总体说明:

1.有一些童鞋喜欢在网上扒一些插件,自定义控件等使用。并且一般都不会查看插件,控件源码。现在的插件,控件很垃圾,不但占服务器资源,而且会使网站运行变慢。去掉垃圾的插件,用标准版!或者自己定义高性能控件。

2.页面嵌套,在一个页面中显示另一个页面的内容。用户的一次访问对服务器来说却是两次或以上。尽量不要采用框架的形式制作页面!

3.页面静态化- 用户可以直接获取页面,不用走那么多流程,比较适用于页面不频繁更新。

4.使用缓存- 第一次获取数据从数据库准提取,然后保存在缓存中,以后就可以直接从缓存提取数据。不过需要有机制维持缓存和数据库的一致性。

5.您的网站图片,文件等资源被人盗链!要防止盗连情况的发生!

6.批量读取 - 高并发情况下,可以把多个请求的查询合并到一次进行,以减少数据库的访问次数

7.延迟修改 - 高并发情况下,可以把多次修改请求,先保存在缓存中,然后定时将缓存中的数据保存到数据库中,风险是可能会断电丢失缓存中的数据

8.使用索引 - 索引可以看作是特殊的缓存,尽量使用索引就要求where字句中精确的给出索引列的值。

9.使用储存过程-那些处理一次请求需要多次访问数据库的操作,可以把操作整合到储存过程,这样只要一次数据库访问就可以了。另外存储过程也节省了预编译的过程,进而打到节省时间的作用。使用存储过程来代替SQL语句查询,还可以在编写存储过程中加入一定的逻辑,从而提高程序的安全性。

10.使用性能较高的SQL语句,很多童鞋在写SQL语句的时候图方便。直接写 Select *或者Count(*)等语句,但是你可知道Select *和Select 列明 在大数据情况下会产生几倍的差距。而Count(1)和Count(*)的差距更是无法想象的。

11.养成检查代码的好习惯,避免因代码质量造成宕机。

12.提高代码质量

提高代码质量

一.++i和i++引申出的效率问题,至于整型变量的前加和后加的区别相信大家也是很清楚的。但却是一般编程过程中最容易忽视的一个问题。

二.循环引发的问题(循环内定义,还是循环外定义对象)

请看下面的两段代码: 

代码1: 
ClassTest CT; 
for(int i = 0; i < 100; ++i) 
    CT = new Test() ;
   //do something 
代码2: 
for(int i = 0; i < 100; ++i)
{
    ClassTest CT =new Test() ; ;
    //do something
你会觉得哪段代码的运行效率较高呢?其实这种情况下,哪段代码的效率更高是不确定的,或者说是由这个类ClassTest本向决定的,分析如下:  
对于代码1:需要调用ClassTest的构造函数100次赋值操作100次;对于代码2:需要声明变量100次,构造函数100次。
如果调用赋值操作函数的开销比声明变量的总开销小,则第一种效率高,否则第二种的效率高。
当然循环内定义,还是循环外定义对象也需要根据自身需求情况来判断,不要发生本末倒置的情况哦。
现在请看下面的两段代码,
代码1:
for(int i = 0; i < n; ++i)
{
    fun1();
    fun2();
}
代码2:
for(int i = 0; i < n; ++i)
{
    fun1();
}
for(int i = 0; i < n; ++i)
{
    fun2();
}
注:这里的fun1()和fun2()是没有关联的,即两段代码所产生的结果是一样的。 
以代码的层面上来看,似乎是代码1的效率更高,因为毕竟代码1少了n次的自加运算和判断,毕竟自加运算和判断也是需要时间的。但是现实真的是这样吗? 
这就要看fun1和fun2这两个函数的规模(或复杂性)了,如果这多个函数的代码语句很少,则代码1的运行效率高一些,但是若fun1和fun2的语句有很多,规模较大,则代码2的运行效率会比代码1显著高得多。可能你不明白这是为什么,要说是为什么这要由计算机的硬件说起。 
由于CPU只能从内存在读取数据,而CPU的运算速度远远大于内存,所以为了提高程序的运行速度有效地利用CPU的能力,在内存与CPU之间有一个叫Cache的存储器,它的速度接近CPU。而Cache中的数据是从内存中加载而来的,这个过程需要访问内存,速度较慢。 
这里先说说Cache的设计原理,就是时间局部性和空间局部性。时间局部性是指如果一个存储单元被访问,则可能该单元会很快被再次访问,这是因为程序存在着循环。空间局部性是指如果一个储存单元被访问,则该单元邻近的单元也可能很快被访问,这是因为程序中大部分指令是顺序存储、顺序执行的,数据也一般也是以向量、数组、树、表等形式簇聚在一起的。 
看到这里你可能已经明白其中的原因了。没错,就是这样!如果fun1和fun2的代码量很大,例如都大于Cache的容量,则在代码1中,就不能充分利用Cache了(由时间局部性和空间局部性可知),因为每循环一次,都要把Cache中的内容踢出,重新从内存中加载另一个函数的代码指令和数据,而代码2则更很好地利用了Cache,利用两个循环语句,每个循环所用到的数据几乎都已加载到Cache中,每次循环都可从Cache中读写数据,访问内存较少,速度较快,理论上来说只需要完全踢出fun1的数据1次即可

三.局部变量VS静态变量

很多人认为局部变量在使用到时才会在内存中分配储存单元,而静态变量在程序的一开始便存在于内存中,所以使用静态变量的效率应该比局部变量高,其实这是一个误区,使用局部变量的效率比使用静态变量要高。 
这是因为局部变量是存在于堆栈中的,对其空间的分配仅仅是修改一次寄存器的内容即可(即使定义一组局部变量也是修改一次)。而局部变量存在于堆栈中最大的好处是,函数能重复使用内存,当一个函数调用完毕时,退出程序堆栈,内存空间被回收,当新的函数被调用时,局部变量又可以重新使用相同的地址。当一块数据被反复读写,其数据会留在CPU的一级缓存(Cache)中,访问速度非常快。而静态变量却不存在于堆栈中。 
可以说静态变量是低效的。

四.减少除法运算的使用

无论是整数还是浮点数运算,除法都是一件运算速度很慢的指令,在计算机中实现除法是比较复杂的。所以要减少除法运算的次数,下面介绍一个简单方法来提高效率:
通过数学的方法,把除法变为乘法运算,如if(a > b/c),如果a、b、c都是正数,则可写成if(a*c > b)

五.尽量避免复制对象

代码1
Test ct1 = new Test ();
代码2
Test ct2 = new Test ();
Test ct3 = ct2; 
先构造一个对象,再把另一个对象值复制给这个对象,如先构造一个对象ct2,再把ct2中的成员变量的值复制给ct3,从这里,可以看出代码1的效率更高一点。

六.数组添加性能>泛型List<T> (由于本文篇幅过长,下文不在一一举例,如果需要了解的童鞋可以自行查阅一下相关文档)

七.List<T>性能   >   Dictionary<T, T>性能

八.for性能>foreach性能

九.Model时,struct性能>class  (不得不说内容)

相信对于一些初入编程的童鞋来说并不清楚struct或者从未使用甚至没有见过。如下代码,可自行拷贝使用一下。

  struct MyStructure
    {
       public string Name;
       public string Surname;
    }
   class MyClass
    {
       public string Name;
       public string Surname;
    }

  class Program
    {
       static void Main(string[] args)
        {           
           MyStructure [] objStruct =new MyStructure[1000];
            MyClass[] objClass = new MyClass[1000]; 
 
           Stopwatch sw = new Stopwatch();
            sw.Start();
           for (int i = 0; i < 1000; ++i)
            {
                objStruct[i] = newMyStructure();
                objStruct[i].Name = "Sourav";
                objStruct[i].Surname = "Kayal";
            }
            sw.Stop();
           Console.WriteLine("For Structure:- "+ sw.ElapsedTicks);
            sw.Restart();
 
           for (int i = 0; i < 1000; ++i)
            {
                objClass[i] = newMyClass();
                objClass[i].Name = "Sourav";
                objClass[i].Surname = "Kayal";
            }
            sw.Stop();
           Console.WriteLine("For Class:- " + sw.ElapsedTicks);           
            Console.ReadLine();
        }

我的运行结果为(可自行尝试体会):

For Structure:- 46

For Class:- 531

相信性能差距已经一目了然了。

十.对于频繁操作的字符串连接 Stringbuilder性能>String性能(StringBuilder采用的是双链结构)

十一.class成员中,String性能>string

十二.循环外使用try catch 性能 > 循环内使用try catch

总结:虽然在尽力控制篇幅,但是还是写了这么多。当然这些也不是全部的内容,只是列举了一些您可能知道但是也并没有重视的日常开发中的“马虎”。关于日常开发过程中损失性能效率的“马虎”实在太多太多,我也仅仅是总结其中一小部分。但是如果您可以做到上述内容,相信您的程序也会有所改善的!

 本文版权归作者 心灬无痕(博文地址:http://www.cnblogs.com/xinwuhen/)所有,欢迎转载和商用,请在文章页面明显位置给出原文链接并保留此段声明,否则保留追究法律责任的权利,其他事项,可留言咨询。

posted @ 2017-06-22 17:21  心灬无痕  阅读(788)  评论(1编辑  收藏  举报