前一段时间在博客园里看到这样一篇文章,那位兄弟谈到程序效率的关键是“简短”。他说,“程序越简短,其可执行代码就越少,就越有效率”,而在编写程序的时候,“要尽量改进我们的算法,而改进算法中最重要的一条,就是减少语句”。这句话从表面上似乎正确,但我认为性能这问题不能用“简短”这种方式去思考,否则会进入一些误区。我整理了一下思路,希望可以从几个方面把详细谈一下这个问题。
首先,如果说“简短的代码效率高”,那么什么叫作“简短”呢?姑且理解为“代码少”吧。如果“代码少”能够得出“效率高”,那我认为还需要其他一些条件,例如:
但是,这对于使用高级语言的程序员来说,这两条基本上都无法成立,因为:
- 代码中有条件跳转(如循环),因此一段简短的代码最终执行的“次数”可能比一段复杂的代码要多。这是很常见的情况,并非如那位兄弟所说“两三行代码写出死循环”这样的“特例”。
- 每行代码的效率几乎不可能相同。试想一个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来证明一下,不是吗?当然,我并没有说一些细节上的调整对性能影响不大,我接下来也会讨论直接对汇编进行的优化。但是,即使我们有需要优化汇编的场景……它们基本上也不是靠所谓减少赋值和判断来起到效果的。
原文:http://www.cnblogs.com/JeffreyZhao/archive/2010/01/07/short-code-is-not-always-fast-1-algorithms.html
分享到:
相关推荐
为了有效地实现鲸鱼算法,开发者需要考虑以下几个关键点: - **参数设置**:包括鲸鱼的数量、最大迭代次数、学习率、衰减系数等,这些参数会影响算法的性能和收敛速度。 - **适应度函数**:根据实际问题定义适应度...
算法的关键在于如何设置和声记忆的容量、记忆考虑率、和声变换率等参数,以及如何有效地更新和声,以保证算法的全局探索能力和收敛速度。 在实际应用中,多目标优化问题广泛存在于工程设计、经济管理、生物医学等...
模拟退火算法通过允许一些不太好的状态(即比当前解更差的解)来避免陷入局部最优解,通过设定一个逐渐降低的“温度”参数来控制算法的搜索过程。 在MATLAB中实现模拟退火算法,主要可以分为以下几个步骤: 1. ...
这提供了一个初始的路径,但不一定是最优的,特别是当考虑到动态变化或复杂因素时。 然后,蚁群算法接手进行优化。蚁群算法基于种群智能,模拟了蚂蚁在寻找食物时释放信息素的过程。在这个过程中,虚拟的“蚂蚁”会...
在神经网络的参数优化中,遗传算法可以作为一个有效的替代方案,与传统的梯度下降方法相比,它不依赖于梯度信息,能处理非线性和多模态的优化问题,但可能会面临收敛速度较慢和局部最优的风险。 在实际应用中,...
在算法的初始阶段,系统处于高温状态,此时更容易接受不利于优化的解决方案,随着迭代过程的进行,温度逐渐降低,系统逐渐倾向于接受更优的解决方案。这种设计允许算法在搜索过程中有一定的随机性,防止过早陷入局部...
这个问题属于NP-hard类别,意味着找不到多项式时间的精确解法,因此通常采用近似算法或启发式方法求解,其中蚁群算法就是一种有效的解决方案。 在这个基于Java实现的蚁群算法中,代码可能包含以下关键部分: 1. **...
这种方法简单快速,但不一定总能得到全局最优解。例如,最小生成树问题和背包问题常常使用贪心策略。 **第八课 分治法** 分治法是将复杂问题分解成若干个相对简单的子问题,分别解决后再合并结果。典型的分治算法...
综上所述,VWAP算法作为一种经典的算法交易策略,在金融工程领域有着广泛的应用。它通过模拟交易量分布和优化拆单策略,实现了降低市场冲击成本的目标。然而,随着市场环境的变化和技术的进步,VWAP算法也需要不断...
Matlab自带了多种聚类工具,如`kmeans`、`linkage`等,但Sting算法并不在其中,需要自行编写或寻找已有的实现。此外,`clustergram`和`fitclust`函数可用于可视化和分析聚类结果,帮助你更好地理解和解释数据的聚类...
2. **蚂蚁循环**:每只蚂蚁根据当前路径上的信息素浓度和启发式信息选择下一步,信息素浓度高的路径被选择的概率更大。这一过程反映了蚂蚁在实际路径选择中的“学习”过程。 3. **信息素更新**:完成一次循环后,...
在操作系统中,页面置换算法是一个至关重要的部分,它处理的是虚拟内存管理问题,尤其是在内存有限的情况下如何有效地利用内存资源。在这个“操作系统课程设计代码-页面置换算法模拟程序”中,我们可以深入理解并...
在准备算法考试时,不仅要理解和掌握各种算法的实现,还要能够灵活应用,分析问题的复杂性,并具备一定的编程能力,能够将算法转化为代码。同时,良好的问题分析和解决能力也是考试中不可或缺的部分。通过反复练习和...
3. **算法表示**:使用伪代码表示算法的方法,确保算法易于理解和实现。 4. **算法复杂度初步**:简单介绍时间复杂度和空间复杂度的概念,为后续更深入的学习打下基础。 #### 三、第二章:函数的增长率 **标题**:...
而"蚁群算法ATSP最终"可能是源代码文件,包含了C++实现的蚁群算法解决非对称TSP问题的具体逻辑。通过阅读这些文件,可以深入理解蚁群算法的运作机制以及在实际问题中的应用。 总结来说,蚁群算法是一种有效的解决非...
**混沌蚁群算法** 混沌蚁群算法是一种融合了混沌理论和蚁群优化算法的智能优化技术,它在解决复杂优化问题时展现出了优秀的性能。混沌系统具有高度的非线性、遍历性和对初始条件敏感的特性,而蚁群算法则是通过模拟...
- **局限性**:虽然模拟退火算法能有效避免局部最优,但在大规模问题上可能收敛速度慢,且参数(如初始温度、冷却率)的选择对算法性能有显著影响。 综上所述,模拟退火算法作为一种有效的全局优化技术,在解决TSP...