大写“O”符号详解

通常您会开发一个测试数据库。它可能只供 10 人访问,所以你的编程和测试工作可以流畅地进行。一旦完成开发和 QA’d (通过质量检验),它将会正式上线(发布), 人们开始访问该网站。过了一年左右,该网站的响应速度越来越慢。在添加更多的数据库服务器没有起到作用后,您的系统管理员在my.cnf’s度过了他的”假期”, 甚至为系统新增了许多内存容量也无济于事。听起来像是在大批量访问网站的情况下程序(代码)结构制约了访问效果。系统对40000人的用户列表信息不能完成排序或处理,或者数组的元素超过了100万项,甚至简单的校验函数减缓了访问速度。为什么?到底发生了什么?

大多数PHP 开发人员从未听说过Big-O ,然而在Web开发中 Big-O 也许比其他任何部分都更加重要(好吧,也许除了系统和操作系统编程以外:) )。代码块一天也许只被 10个用户执行,但是相同的代码很可能每分钟运行了成千上万次。

什么是 Big-O ?

Big-O 或者 landau notation(朗道标记法)是一种 定义一个函数尺度随着参数变化研究函数运行情况 的数学原理。换句话说:Big-O 会告诉你,一个strpos()函数在分别接收 10个和1000000 个 字符长度的字符串参数时运行时间的关系。它不会告诉你明确的运行时间,但是它(Big-O)会告诉你参数和运行时间的趋势关系。

那么,也许你会问:这是怎么回事?

具体来说...

假设您有一个函数,它根据一定的条件将数组中的数据进行排序。例如,按照姓氏的字母顺序排序。 每个人都可以想象出: 对10个用户将会比 对1000万个用户排序快很多。但是(排序)如何更快或更好:后者将会大概花费多长时间?当知道问题的答案后,你会清楚在大量用户访问的环境下你的代码(系统)运行的速度。您将会明白系统的首要瓶颈因素:您的代码 或 硬件?换句话说,你要开始意识到系统的可扩展性。

示例 1 :

假设您有一个函数IsEven() :

function isEven ($iNumber) { // 方法a
    return ( ($iNumber & 1) == 0);
}

此函数将会判断一个数值是否是偶数,如果是则返回值true, 否则返回值false 。

对于此函数,无论传入的参数是 int(1) 还是int(100000005),都没关系;如果函数总是使用同样的时间去判断参数并返回值。

现在,假设我们把函数修改成以下形式:

</pre><pre name="code" class="php">function isEven ($iNumber) { // 方法b
    $bEven = true;
    for ($i=0; $i!=abs($iNumber); $i++) {
    if ($bEven == true) {
        $bEven = false;
    } else {
        $bEven = true;
    }
    return $bEven;
}

当然,(函数)这种写法没有多大意义。但是我偶尔会看到使用低效率算法的代码,所以上面的例子足以证明我的观点。

当函数(方法b)接收的参数是 int(1) ,for循环语句只执行一次。 函数运行的很快,没有问题出现。然而,当我们想判断数值1000000是否是偶数时(接收的参数是 int(1000000) ) ,我们可能会遇到一点麻烦,因为for循环必须执行1000000次函数才能返回判断结果。

如果我们把函数(方法a)放到一个图表中, X 轴表示参数 $iNumber( 取值范围 :1 ~ 1000 ) ,Y 轴表示函数得到判断结果所花费的时间,那么我们将会得到一条水平线的图形。这就表明函数无论接收什么样的 int数值,得到判断结果所花费的时间总是相同的。这可以标记为 O(1),这种函数的性能是最优的。无论您传入的参数是什么,函数得到处理结果所花费的时间总是一样。代码(函数)可扩展性的梦想成真了 :)

现在,我们一起来分析函数(方法b) 。同样 X 轴表示参数 $iNumber , 我们将会看到一条上升的直线。我们用 O(N)标记此函数(N代表参数本身或循环的次数,函数运算规模) 。

参数的数值大小与函数运算时间有直接关系。

示例 2 :

function getModuleInfoByID ($iModuleID) { // 方法 c
    foreach ($this->_aModuleInfo as $aInfo) {
        if ($aInfo['id'] == $iModuleID) return $aInfo;
    }
    return null;
}

当参数id在遍历的对象中能匹配上时,函数(方法c)则返回对应的module信息;否则返回null值。这是一种非常常见的方式通过遍历数组来查找数据。 这类函数可以标记为O(N) , 其中N是模块信息的数量,也是集合 $_aModuleInfo 元素的个数。

我们如何来优化这种类型函数的性能呢?

如果不使用遍历(迭代)而能实现查找数据的功能,将会是很好的选择。通过优化代码结构提高运算效率(降低 O-line 的斜率)总是麻烦的事情 :) 为了做到这一点,我们要对函数(方法c)做一些修改。例如,如果我们把集合元素存放到一个关联数组中,并且把元素的id 作为key(键),如下所示:

function getModuleInfoByID ($iModuleID) { //方法 d
    if(isset($this->_aModuleInfo[$iModuleID]))
        return $this-> _aModuleInfo[$iModuleID];
    return null;
}

在函数(方法d)中,我们不用担心 $_aModuleInfo 数组的长度是多少。此函数运行时不需要执行遍历操作,而且无论是1个还是 1000000个 moduleInfo 元素都没关系。

然而,我们需要注意以下几点:

● 函数isset()起什么作用?它可以用 O(1)还是用O(N)来标记?或者更糟?您必须要知道函数(包括它调用的函数)的运算性能。

● 您的以上对函数运算性能的检查是基于 PHP-level 。基于 CPU-level,结果也许是另一回事儿。例如:CPU处理数值数组要比处理关联数组快很多(前者不涉及哈希查找)。

然而,在PHP中处理数值数组和关联数组的时间没有多大差别。

各种各样的O的区别

O’s的类型有很多并且很复杂 :) 坚持往下看哦,瞧瞧下面哪一种O’s能匹配您编写的函数。您可能从来不需要一个精确的公式,但是只要您知道您的函数运算性能模型类 似 O(log N),而不是 O(N),您已经知道了很多...

下面是O-functions列表,按性能从优到劣依次排序:

● O(1)

常数时间,无论参数是什么,函数运算的时间总是相同的。

● O(log N)

对数时间,函数运算时间在开始时快速增长,但是过了一会儿,函数运算时间增长缓慢。这是个好消息,当您知道有许多对象要处理时(例如用户、文章、评论排序)。

● O(n) Linear(线性)

参数的大小与函数运算时间有直接关系。两倍参数大小意味着两倍的运算时间。

● O(n^2) Quadratic(平方)

大多数时候,这意味着拥有相同数据集的两个迭代器要参与运算(像许多未优化的排序算法)。

● O(n^3) Cubic(立方)

与Quadratic(平方)一样,只是多了一个额外的循环。这对函数的运算性能来说是灾难性的。

● O(n!) Factorial(阶乘)

这些图形几乎是直线上升(例如,10的阶乘是 3628800 ,这意味着大量的迭代操作)。如果您的函数性能模型与此类似,建议重写函数,这种函数的运算性能很糟糕。

说明:图片来源 -- http://nl.wikipedia.org/wiki/Bestand:Exponential.png

其中,红线是 O(n)线性图,蓝线是O(n^3)立方图,绿线是O(2^n)指数图。

注意,尽管在x<=8范围内绿线的y值(时间)是最优的,但是在x>10范围内,绿线的y值(时间)是最差的。

在读取数据时,我们至少统计查询了一半的数组元素。获取一个在 0 到 100范围内的随机数,有50%的机会这个随机数小于 50,同时有50%的机会这个随机数大于等于50 。然而,Big-O 要考虑最坏的情况。所以按顺序搜索长度为N的数组应该标记为O(N), 而不是 O(N/2) 。

Big-O 注意事项

请注意,O(*)标记没有说任何关于运算速度本身的事情。它只是告诉您函数的运算速度和参数是相对应的。例如,在某些情况下,一个 O(n)性能的函数可能比一个O(log n)或O(1) 性能的函数运算的快。然而,会有一个转折点,其它的函数运算的更快。您需要在函数、算法复杂性以及运算速度之间找一个平衡点。反复测试您函数的运算时间(性能)并比较结果。它们的运算性能也许没有您想象的那么糟糕(也许比你想的还要差;) )。测试中代码会告诉你结果,但是不经测试您是不知道的 :)

总结

了解您的函数以及它的运算性能。但是要确保优化函数性能不能过度。让每个函数的运算性能达到 O(1)水平是件完美的事情,但是实际上优化工作是没有尽头的,而且总要在开发时间、预算和速度以及优化工作之间寻找一个平衡点。

1.
本文由mathew翻译,由程序员学架构校审

2. 本文译自https://www.adayinthelifeof.nl/2009/12/21/big-o-notation/

原文作者:Joshua Thijssen December 21, 2009

3.
转载请务必注明本文出自:程序员学架构(微信号:archleaner
)

4. 更多文章请扫码:

时间: 2024-09-30 15:40:34

大写“O”符号详解的相关文章

正则表达式全部符号详解

字符 描述 \ 将下一个字符标记为一个特殊字符.或一个原义字符.或一个 向后引用.或一个八进制转义符.例如,'n' 匹配字符 "n".'\n' 匹配一个换行符.序列 '\\' 匹配 "\" 而 "\(" 则匹配 "(". ^ 匹配输入字符串的开始位置.如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n' 或 '\r' 之后的位置. $ 匹配输入字符串的结束位置.如果设置了RegExp 对象的 Mul

引用&amp;符号详解

变量的引用 PHP 的引用允许你用两个变量来指向同一个内容. 例一: <?php $a="2010"; $b =&$a; echo $a;//这里输出:2010 echo $b;//这里输出:2010 $b="2012"; echo $a;//这里$a的值变为2012 所以输出 echo $b;//这里输出2012 ?> 例二: <?php $a = "date"; $b = &$a; echo $a; // d

Linux命令的特殊符号详解

Linux输入和输出中,会有>>,>和< >>和>都属于输出重定向,<属于输入重定向. >会覆盖目标的原有内容.当文件存在时会先删除原文件,再重新创建文件,然后把内容写入该文件:否则直接创建文件. >>会在目标原有内容后追加内容.当文件存在时直接在文件末尾进行内容追加,不会删除原文件:否则直接创建文件. 原文地址:https://www.cnblogs.com/HuangJiaPing/p/12625414.html

接口测试基础知识详解http请求由三部分组成,分别是:请求行、消息报头、请求正文 1、请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本,格式如下:Method Request-URI HTTP-Version CRLF 其中 Method表示请求方法;Request-URI是一个统一资源标识符;HTTP-Version表示请求的HTTP协议版本;CRLF表示回车和换行(除了

HTTP URL (URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息)的格式如下:http://host[":"port][abs_path]http表示要通过HTTP协议来定位网络资源:host表示合法的Internet主机域名或者IP地址:port指定一个端口号,为空则使用缺省端口80:abs_path指定请求资源的URI:如果URL中没有给出abs_path,那么当它作为请求URI时,必须以"/"的形式给出,通常这个工作浏览器自动帮我们完成.e

shell脚本中常见的一些特殊符号和作用详解

这篇文章主要介绍了shell脚本中常见的一些特殊符号和它的作用详解,总结的很简洁,容易看懂,需要的朋友可以参考下 在编写Shell脚本时,我们需要会用到各种各样的特殊符号,通过这些特殊符号可以使我们编写的代码更加简洁和高效,这里给大家汇总下: 1.{} 大括号: 用法一:通配符扩展eg: ls my_{finger,toe}s这条命令相当于如下命令的组合:ls my_fingers my_toeseg: mkdir {userA,userB,userC}-{home,bin,data}我们将得到

详解MathType中如何插入特殊符号

在论文写作中,经常会用到一些特殊符号,MathType公式编辑器支持插入特殊符号,并且数量繁多,可以满足用户的需求.本教程将详解MathType如何插入特殊符号. MathType中插入特殊符号的操作步骤: 步骤一 点击MathType软件菜单中的编辑->插入符号. 步骤二 在打开的插入符号对话框中,选择需要插入的符号,点击“插入”即可. 如果某个特殊符号经常使用,可对其进行设定快捷键操作,点击符号后,将光标放在“输入新的快捷键”一栏中,按下设定好的快捷键,点击“指定”即可完成设定.对于同一个符

(转)linux 中特殊符号用法详解

linux 中特殊符号用法详解 原文:https://www.cnblogs.com/lidabo/p/4323979.html # 井号 (comments)#管理员  $普通用户 脚本中 #!/bin/bash   #!/bin/sh井号也常出现在一行的开头,或者位于完整指令之后,这类情况表示符号后面的是注解文字,不会被执行. # This line is comments.echo "a = $a" # a = 0由于这个特性,当临时不想执行某行指令时,只需在该行开头加上 # 就

Oracle 11g数据库详解(2015-1-18更新)

Oracle 11g数据库详解 整理者:高压锅 QQ:280604597 Email:[email protected] 大家有什么不明白的地方,或者想要详细了解的地方可以联系我,我会认真回复的 1   简介 数据库操作主要有以下几步: 1.  启动.停止数据库 2.  连接.断开数据库 3.  创建.修改.删除数据库用户 4.  表空间 5.  新建.修改.删除表 6.  查询.插入.修改.删除表数据 7.  新建.修改.删除视图 8.  新建.修改.删除存储过程 9.  新建.修改.删除触发

HTTP协议详解(真的很经典)

引言 HTTP是一个属于应用层的面向对象的协议,由于其简捷.快速的方式,适用于分布式超媒体信息系统.它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展.目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出.HTTP协议的主要特点可概括如下:1.支持客户/服务器模式.2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径.请求方法常用的有GET.HEAD.POS