注册 登录
编程论坛 ASP.NET技术论坛

怎样对自己写的程序进行优化?

a54400950 发布于 2010-07-02 17:24, 1027 次点击
我是一个 的新手,我想请教一下怎样去优化自己所写的代码,谢谢!
11 回复
#2
yms1232010-07-02 18:23
注意嵌套循环,还有就是注意多表查询。
#3
yms1232010-07-02 18:24
foreach要比for循环消耗内存大一些
#4
a544009502010-07-02 19:11
谢谢,谢谢哦,今天没有白活
#5
saitor2010-07-02 20:06
foreach要比for循环消耗内存大一些
这个结论怎么得出来的?JS?
#6
a544009502010-07-02 20:42
上面的结论是怎么得到的
#7
冰镇柠檬汁儿2010-07-06 09:39
以下是引用yms123在2010-7-2 18:24:44的发言:

foreach要比for循环消耗内存大一些
(引用)

在很多情况下for和foreach具有同样的功能,选择for还是foreach很多人可能都是看自己的喜好,本测试试图通过来真是测试数据来比较他们的执行效率。希望能给大家对他们的时候带来一些帮助。

测试环境:     
        硬件环境:       PIII800   +   CPU456
        软件环境:            +   C#

测试用例:
        利用结果集来存放记录,初始化对结果集添加记录
        分别对记录数为10000,100000,1000000条记录的时候进行采样分析

        关键测试对比代码如下,功能完全一样:
        //foreach开始时间
      datetime3   =   System.DateTime.Now.TimeOfDay.ToString();
      foreach(DataRow   row   in   relationData.Tables[RelationData.RELATIONINFO_TABLE].Rows)
  buffer   =   row[RelationData.PK_TABLE_NAME].ToString();
      datetime4   =   System.DateTime.Now.TimeOfDay.ToString();

      //for开始时间
      datetime5   =   System.DateTime.Now.TimeOfDay.ToString();
      for(int   j=0;j <1000000;j++)
                  buffer   =   relationData.Tables[RelationData.RELATIONINFO_TABLE].Rows[j][RelationData.PK_TABLE_NAME].ToString();
      datetime6   =   System.DateTime.Now.TimeOfDay.ToString();
  
测试结果:
      10000条记录时:
                  foreach读取时间:16:29:34.2577584   
                  foreach结束时间:16:29:34.2677728   
                  for读取开始时间:16:29:34.2677728   
                  for读取结束时间:16:29:34.2878016
      100000条记录时:
                  foreach读取时间:16:31:10.1055808   
                  foreach结束时间:16:31:10.1957104   
                  for读取开始时间:16:31:10.1957104   
                  for读取结束时间:16:31:10.4460704
      1000000条记录时:
                  foreach读取时间:16:33:12.6217504   
                  foreach结束时间:16:33:13.6332048   
                  for读取开始时间:16:33:13.6332048   
                  for读取结束时间:16:33:18.7906208

结果分析:
      1)对于10000条记录可以看出
                  foreach用了   0.0100144     
                  for循环用了0.0300432   
                  foreach所花的时间正好是for循环的   1/3   
      2)对于100000条记录可以看出
                  foreach用了0.0901296      
                  for循环用了0.2503600
                  foreach所花的时间是for循环的   36%
      3)对于1000000条记录结果可以看出
                  foreach用了1.0114544      
                  for循环用了4.1574160
                  foreach所花的时间是for循环的   25%

        通过对这些测试结果的分析,可以看出相对于原来的for语句foreach具有
更好的执行效率,foreach的平均花费时间只有for30%。通过测试结果在for和foreach都可以使用的情况下,我们推荐使用效率更高的foreach.在测试同时
我们附加的发现,用for写入数据时间大约是读取数据时间的10倍左右.:P
#8
冰镇柠檬汁儿2010-07-06 09:42
foreach在提高效率的同时,还是会有些弊端的,因为网上比较公认的说法是foreach封装了for,所以其占用更多的内存是应该的,并且foreach不能对其中的行进行删除,否则会报异常
#9
bygg2010-07-06 09:43
同意7楼,foreach比for好一些.呵呵.
以前我也测试过.
#10
bygg2010-07-06 09:52
以下是引用冰镇柠檬汁儿在2010-7-6 09:42:45的发言:

foreach在提高效率的同时,还是会有些弊端的,因为网上比较公认的说法是foreach封装了for,所以其占用更多的内存是应该的,并且foreach不能对其中的行进行删除,否则会报异常

A.不能修改的是foreach引用变量,因为它是引用的,对它的修改没有意义也没有作用,它就像一个c/c++指针那样对集合中的元素进行指向引用;我们能通过引用看到它,但是没有能力动它。

B.in之前的变量是引用变量,但in之后的集合确是实实在在的,所以可以对它操作,像.ADD();REMOVE()等方法可以使用。
C.foreach引用变量大概是通过地址指向后面的集合(我猜想跟c/c++的指针很像),当修改了集合的元素时,地址可能发生某种改变,所以不跳出循环继续遍历时,就可能出现引用找不到的情况,出现超出集合的异常所以,即使是使用REMOVE() 这样的方法后,跳出循环是不会出错的,但是继续循环,则会出错。切记,切记。
#11
NewDeveloper2010-07-06 10:03
首先,如果说“简短的代码效率高”,那么什么叫作“简短”呢?姑且理解为“代码少”吧。如果“代码少”能够得出“效率高”,那我认为还需要其他一些条件,例如:

代码完全是顺序执行的
每行代码的效率相同
但是,这对于使用高级语言的程序员来说,这两条基本上都无法成立,因为:

代码中有条件跳转(如循环),因此一段简短的代码最终执行的“次数”可能比一段复杂的代码要多。这是很常见的情况,并非如那位兄弟所说“两三行代码写出死循环”这样的“特例”。
每行代码的效率几乎不可能相同。试想一个i++和一个方法调用,他们的性能开销怎么可能相同呢?再者,这个特性并非是“高级语言”特有的,连CPU指令也是一样(以后我们再来详谈这个问题)。
其实这就是目前编程工作中不可能回避的现状,那就是高级语言并不直接反映出机器的执行过程。同时,我们不能从代码表面的简短程度来判断程序效率的高低。正如那位朋友所谈,影响程序的关键因素之一是算法的选择,但我不同意他说算法优化的关键在于“减少语句”。从一定程度上讲,选择高效的算法的确是为了减少指令执行的“总量”,但它并不是如那篇文章所说通过“少一些变量”,“少一些判断”来进行的,而是在于大幅的逻辑改变来减少总体的操作。

事实上,我们很容易便能举出代码简短,但是性能较差的算法。就拿最常见的排序算法来说,冒泡排序不可谓不简单:

点此展开点此折叠

public void BubbleSort(int[] array)
{
    for (int i = array.Length - 1; i >= 0; i--)
    {
        for (int j = 1; j <= i; j++)
        {
            if (array[j - 1] > array[j])
            {
                int temp = array[j - 1];
                array[j - 1] = array[j];
                array[j] = temp;
            }
        }
    }
}而快速排序与之相比实在是复杂太多了:

点此展开点此折叠

public void QuickSort(int[] array)
{
    QuickSortInternal(array, 0, array.Length);
}

private void QuickSortInternal(int[] array, int begin, int end)
{
    if (end == begin)
    {
        return;
    }
    else
    {
        int pivot = FindPivotIndex(array, begin, end);
        if (pivot > begin) QuickSortInternal(array, begin, pivot - 1);
        if (pivot < end) QuickSortInternal(array, pivot + 1, end);
    }
}

private int FindPivotIndex(int[] array, int begin, int end)
{
    int pivot = begin;
    int m = begin + 1;
    int n = end;

    while (m < end && array[pivot] >= array[m])
    {
        m++;
    }

    while (n > begin && array[pivot] <= array[n])
    {
        n--;
    }

    while (m < n)
    {
        int temp = array[m];
        array[m] = array[n];
        array[n] = temp;

        while (m < end && array[pivot] >= array[m])
        {
            m++;
        }

        while (n > begin && array[pivot] <= array[n])
        {
            n--;
        }

    }

    if (pivot != n)
    {
        int temp = array[n];
        array[n] = array[pivot];
        array[pivot] = temp;

    }

    return n;
}OMG,为什么快速排序会那么复杂?其原因在于,快速排序的性能关键之一,在于选择一个好的中轴(pivot)元素,选择一个好的中轴元素可以尽可能减少的交换操作的次数,以此提高算法的效率。而冒泡排序,做法的确“简短”,但是其性能在大部分场合都不如快速排序来的好。而且,同样是快速排序,也可以有多种实现方法,有的语言还可以实现地非常简单,如Haskell:

qsort [] = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)这便是Haskell版的快速排序,够短吧。但是它的效率不如之前那段长长的C#——当然,这么比可能不公平,因为两者使用的数据结构不同,C#基于数组进行排序,而Haskell却使用的是不可变的单向链表。

算法是效率的关键,但选择合适的算法也并非是一件十分容易的事情。我们都知道一个算法它有时间复杂度,空间复杂度等等,但这些是什么呢?是从数学角度得出的“理论值”。一个算法的时间复杂度,考虑的是算法的时间随规模增长的变化规律,它能确保的只是“当规模大到一定程度时”,时间复杂度低的算法效率肯定相对较高。但是在实际开发过程中,我们遇到的数据量往往都是有限的,其形式甚至还有一定规律可循。因此,“理论上”时间复杂度低的算法,并非时时刻刻都有上佳表现。

例如,对于一个已经排序的数组来说,冒泡排序的性能无人能敌;对于一个高效的快速排序算法来说,如果元素数量少于一个阈值时就会使用插入排序(因为快速排序时间复杂度的常数较大);再比如,一个算法使用空间换时间,但是空间一大,物理内存中的数据可能就会被放到磁盘交换区上(也有人把它叫做虚拟内存,但是我不认同这个叫法),此时读取时可能就要从硬盘上重新加载数据页,性能可能就更低一些了。说个更近的,有时候每次都创建对象,性能反而比缓存起来要高,不是吗?

因此我认为,无论怎么样,“简短”不应该是评价一段代码是否高效的因素。您可能会说,算法对性能来说自然很关键,但是这里说的“简短”不是指算法的改变,而是指在算法相同的情况下,少一些赋值,判断操作等等,这样指令少了性能不是更高了吗?不过我的考虑是,既然算法已经确定了,那么究竟有哪些地方值得我们减少一些赋值或判断操作呢?即便这样的确有效果,但我想,如果保留合理的赋值和判断如果可以让代码更清晰,那就不要进行如此“手动”而“原始”更“想当然”的优化吧。清晰、正确比高效更重要。

最重要的是,没有Profiling,一切优化都是徒劳的。如果您认为减少赋值和判断可以有效提高性能,那么也用Profiling来证明一下,不是吗?当然,我并没有说一些细节上的调整对性能影响不大,我接下来也会讨论直接对汇编进行的优化。但是,即使我们有需要优化汇编的场景……它们基本上也不是靠所谓减少赋值和判断来起到效果的。
#12
NewDeveloper2010-07-06 10:04
首先,如果说“简短的代码效率高”,那么什么叫作“简短”呢?姑且理解为“代码少”吧。如果“代码少”能够得出“效率高”,那我认为还需要其他一些条件,例如:

代码完全是顺序执行的
每行代码的效率相同
但是,这对于使用高级语言的程序员来说,这两条基本上都无法成立,因为:

代码中有条件跳转(如循环),因此一段简短的代码最终执行的“次数”可能比一段复杂的代码要多。这是很常见的情况,并非如那位兄弟所说“两三行代码写出死循环”这样的“特例”。
每行代码的效率几乎不可能相同。试想一个i++和一个方法调用,他们的性能开销怎么可能相同呢?再者,这个特性并非是“高级语言”特有的,连CPU指令也是一样(以后我们再来详谈这个问题)。
其实这就是目前编程工作中不可能回避的现状,那就是高级语言并不直接反映出机器的执行过程。同时,我们不能从代码表面的简短程度来判断程序效率的高低。正如那位朋友所谈,影响程序的关键因素之一是算法的选择,但我不同意他说算法优化的关键在于“减少语句”。从一定程度上讲,选择高效的算法的确是为了减少指令执行的“总量”,但它并不是如那篇文章所说通过“少一些变量”,“少一些判断”来进行的,而是在于大幅的逻辑改变来减少总体的操作。

事实上,我们很容易便能举出代码简短,但是性能较差的算法。就拿最常见的排序算法来说,冒泡排序不可谓不简单:

点此展开点此折叠

public void BubbleSort(int[] array)
{
    for (int i = array.Length - 1; i >= 0; i--)
    {
        for (int j = 1; j <= i; j++)
        {
            if (array[j - 1] > array[j])
            {
                int temp = array[j - 1];
                array[j - 1] = array[j];
                array[j] = temp;
            }
        }
    }
}而快速排序与之相比实在是复杂太多了:

点此展开点此折叠

public void QuickSort(int[] array)
{
    QuickSortInternal(array, 0, array.Length);
}

private void QuickSortInternal(int[] array, int begin, int end)
{
    if (end == begin)
    {
        return;
    }
    else
    {
        int pivot = FindPivotIndex(array, begin, end);
        if (pivot > begin) QuickSortInternal(array, begin, pivot - 1);
        if (pivot < end) QuickSortInternal(array, pivot + 1, end);
    }
}

private int FindPivotIndex(int[] array, int begin, int end)
{
    int pivot = begin;
    int m = begin + 1;
    int n = end;

    while (m < end && array[pivot] >= array[m])
    {
        m++;
    }

    while (n > begin && array[pivot] <= array[n])
    {
        n--;
    }

    while (m < n)
    {
        int temp = array[m];
        array[m] = array[n];
        array[n] = temp;

        while (m < end && array[pivot] >= array[m])
        {
            m++;
        }

        while (n > begin && array[pivot] <= array[n])
        {
            n--;
        }

    }

    if (pivot != n)
    {
        int temp = array[n];
        array[n] = array[pivot];
        array[pivot] = temp;

    }

    return n;
}OMG,为什么快速排序会那么复杂?其原因在于,快速排序的性能关键之一,在于选择一个好的中轴(pivot)元素,选择一个好的中轴元素可以尽可能减少的交换操作的次数,以此提高算法的效率。而冒泡排序,做法的确“简短”,但是其性能在大部分场合都不如快速排序来的好。而且,同样是快速排序,也可以有多种实现方法,有的语言还可以实现地非常简单,如Haskell:

qsort [] = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)这便是Haskell版的快速排序,够短吧。但是它的效率不如之前那段长长的C#——当然,这么比可能不公平,因为两者使用的数据结构不同,C#基于数组进行排序,而Haskell却使用的是不可变的单向链表。

算法是效率的关键,但选择合适的算法也并非是一件十分容易的事情。我们都知道一个算法它有时间复杂度,空间复杂度等等,但这些是什么呢?是从数学角度得出的“理论值”。一个算法的时间复杂度,考虑的是算法的时间随规模增长的变化规律,它能确保的只是“当规模大到一定程度时”,时间复杂度低的算法效率肯定相对较高。但是在实际开发过程中,我们遇到的数据量往往都是有限的,其形式甚至还有一定规律可循。因此,“理论上”时间复杂度低的算法,并非时时刻刻都有上佳表现。

例如,对于一个已经排序的数组来说,冒泡排序的性能无人能敌;对于一个高效的快速排序算法来说,如果元素数量少于一个阈值时就会使用插入排序(因为快速排序时间复杂度的常数较大);再比如,一个算法使用空间换时间,但是空间一大,物理内存中的数据可能就会被放到磁盘交换区上(也有人把它叫做虚拟内存,但是我不认同这个叫法),此时读取时可能就要从硬盘上重新加载数据页,性能可能就更低一些了。说个更近的,有时候每次都创建对象,性能反而比缓存起来要高,不是吗?

因此我认为,无论怎么样,“简短”不应该是评价一段代码是否高效的因素。您可能会说,算法对性能来说自然很关键,但是这里说的“简短”不是指算法的改变,而是指在算法相同的情况下,少一些赋值,判断操作等等,这样指令少了性能不是更高了吗?不过我的考虑是,既然算法已经确定了,那么究竟有哪些地方值得我们减少一些赋值或判断操作呢?即便这样的确有效果,但我想,如果保留合理的赋值和判断如果可以让代码更清晰,那就不要进行如此“手动”而“原始”更“想当然”的优化吧。清晰、正确比高效更重要。

最重要的是,没有Profiling,一切优化都是徒劳的。如果您认为减少赋值和判断可以有效提高性能,那么也用Profiling来证明一下,不是吗?当然,我并没有说一些细节上的调整对性能影响不大,我接下来也会讨论直接对汇编进行的优化。但是,即使我们有需要优化汇编的场景……它们基本上也不是靠所谓减少赋值和判断来起到效果的。
1