友好连接

2008年12月25日星期四

压力测试的几种常见性解决方案

并发性(压力测试)指的是多个用户试图同时访问相同数据的处理,问题的关键在于如何设计应用程序对并发性问题的处理方式,特别是当前很多系统都存在多用户对共享资源的访问,常见的解决方案如下:1:保守方法:这种并发性模型在数据上加了锁,如一个用户在操作数据库的一条记录时,在允许编辑的环境中,系统就会拒绝来自其它用户读取数据的请求。对于很可能出现一个以上用户同时编辑相同数据的情况时,最适合采用这种方式,虽然这种方式在实现上有一定的复杂度。在此模式下测试并发性主要关心的是验证能否正确地取得、释放加在记录上的锁,并且正确处理应用程序中所有可能更新这条记录的部分。a :锁的获得:因为同一时刻只有一个用户能够进入一条数据记录或数据项的更新状态,所以关键是系统必须把锁正确地分配给第一个请求的用户。获得锁的操作应该是可操作的,具体的做法是:让两个用户试图同时进入编辑状态或者也可以使用大量的请求,对于后者我们可以使用一个脚本来产生多个同时的编辑数据请求,以此来验证只有一个请求获得成功。b :锁的效用:验证锁的有效性必须确保其它任何用户不能用任何方式修改这个数据(如修改和删除),具体的验证方法是:让一个用户打开一条记录(进入编辑模式并且保持这个状态),同时其它用户在应用程序的所有地方试图编辑、删除等一切方法更新数据,系统应该拒绝所有其它用户更新数据的企图。c :锁的释放:必须验证:当编辑数据的用户释放了该条记录后,系统能够让其它用户编辑该条记录,另一个注意的方面是错误处理,也就是持有锁的用户用到错误的情况下(如客户端崩溃),系统应该完成什么样的操作,系统从释放锁的故障中重新恢复的能力要重点考虑。2:开放方式:在此模式中,总是允许用户读取数据,甚至还可能允许更新数据,但当用户试图保存数据时,系统会自动检查自从这个用户检索数据以后是否有其它人更新过数据,如果数据发生了变化,那么更新就失败。这种方法比保守模型允许更多的用户查看数据,所以它适用于不太可能出现多人同时修改同一数据的情况。在此模式下,更新是唯一需要关注的要点,最佳的测试方法是综合手动和自动测试技术,在手动测试时,两个测试人员编辑数据,然后试图同时保存数据,一个用户更新的操作成功后,另一个用户得到的消息是内容是其它用户已经更新了数据,此时他只有重新装载数据并且重新完成修改操作。在使用自动海量的测试方法时,同理,只有一个用户能更新记录,而其它用户都收到提示,因为其它用户已经更新了数据,所以他的操作无效。3:无并发保护,是所有模式中最简单的一种,通俗的说即胜利属于最后一个用户,但当两个用户同时修改一条记录时,可能导致数据损坏。在此模式下,无论更新请求的顺序如何,所有用户都该成功完成更新操作,特别需要关注的是数据的完整性和更新错误,如:当一个用户更新某记录的同时,它确被删除了。处理并发测试时还要注意,当相同的数据可以通过不同的界面或者功能更新时,应该测试所有可能访问这条记录的功能。 转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1238691.html

性能测试基础知识-性能的规划与实现

性能的规划与实现 一个不能按意愿执行的程序是没有用处的。每个程序都必须满足某组用户(有时会是一组很大且需求各不相同的用户)的需求。如果程序的性能确实不能满足那些用户中很大一部分用户的需求,则不会使用这个程序。一个不被使用的程序是不能实现预期功能的。 这种情况对经许可的软件包和用户编写的应用程序是确实存在的,尽管大多数软件包开发者意识到低性能的影响,并尽力提高程序的运行速度。不幸的是,他们不能预测程序要经历的所有环境和用途。让程序具有可接受性能的最终职责就落在了那些选择或编写、规划以及安装软件包的人身上。 本章描述程序员或系统管理员可以确保新编写或购买的程序具有可接受性能的步骤。(当程序员这个词单独出现时,它包含系统管理员和任何对程序的最终成功负责的人。) 为使程序达到可接受的性能,在工程开始时就要确定和量化可接受性,并且决不能忽视达到目标所需的方法和资源。尽管听起来这是基本方法,但一些编程工程却有意抵制它。他们采用一种清楚地描述为设计、编码、调试、可能是编写文档,有时间的话再确定其性能的策略。 为使程序运行时不仅在逻辑上,而且在时间上都是可预知的,唯一办法就是在软件规划和开发过程中对性能注意事项进行整体考虑。由于安装者较之开发者有较少的自由,所以在现有软件安装时提前规划也许就更关键了。 尽管对一个小程序来说,这个过程的细节可能看起来很繁重,但不要忘了我们还有第二个“记事本”。我们不仅必须保证新程序具有令人满意的性能,还须确保该程序对现有系统的补充部分不会降低运行于该系统的其它程序的性能。 确定工作负载的组成部分 无论程序是新编写的还是购买的、大程序还是小程序,开发者、安装者和预期用户都对程序的使用有所假设,比如: 谁使用该程序 程序在何种环境下运行 这些环境出现的频度,以及在某年某月某日某时会出现多少次 在这些环境下是否还需使用其它现有程序 程序运行于何种系统 有多少数据将要从何处进行处理 由程序或为程序创建的数据是否会在其它方面用到 除非这些想法是作为设计过程的一部分提出的,否则很可能模糊不清,并且程序员将几乎无疑会有与预期用户不同的假设。甚至在程序员同时也是用户这样明显很普通的情况中,让假设无关会使以任何严格方式进行设计与假设的比较成为不可能。更糟的是,在对正进行的工作没有完全理解的情况下是不可能确定性能需求的。 编写性能需求文档 在确定和量化性能需求时,确定某一特殊要求背后的推理是很重要的。这是规划过程总能力的一部分。用户可能会将其需求声明基于与程序员的假设不匹配的程序逻辑的假设。性能需求集至少应记录下面几点: 各种特定类型的用户 — 计算机交互作用在大部分时间会经历的最佳响应时间,以及对大部分时间的定义。响应时间从用户执行“运行”这个操作的时间直到用户从计算机接收到足够反馈以继续执行任务来衡量。这是用户的主观等待时间。它不是从一个子例程的入口到第一个写语句的时间。 如果用户对响应时间不感兴趣,而仅仅对结果感兴趣,您可以询问“当前独立执行时间估计值的十倍”是否可以接受。如果回答“是”,您就可以继续讨论吞吐量。否则,您可以在用户十分注意的情况下继续讨论响应时间。 最低程度可接受剩余时间的响应时间。较长的响应时间会使用户认为系统当机。您还需要指定剩余时间,例如,一天的高峰时刻,百分之一的交互作用。在一天的某特定时间减少响应时间很难办到,或者代价更高。 需要的典型吞吐量和将发生的次数。这并不是临时注意事项。例如,对一个程序的需求可能是每天运行两次:上午 10:00 和下午 3:15。如果这是一个运行 15 分钟,并且计划运行于多用户系统的有 CPU 限制的程序,则需要某种协商以便依次运行。 最大吞吐量周期的大小和计时。 综合预期请求及其如何随时间变化。 多用户应用程序中每台机器的用户数及总用户数。此描述应包括这些用户登录和注销的次数,以及假设的击键速率、完成的请求和思考次数。您可能想弄清楚思考次数是否随前后请求而系统地变化。 用户所做的关于工作负载要在其上运行的机器的任何假设。如果用户头脑中存在一台具体的机器,那么确保您早就了解它。同样,如果用户所采用的是特殊类型、大小、成本、位置、互联或任何其它变量,而这些变量将限制您满足前述需求的能力,那么假设也变为需求的一部分。满意程度可能不会在程序开发、测试或首次安装的系统上进行评估。 估计工作负载的资源需求 除非您正在购买配有详细资源需求文档的软件包,否则资源估计可能是性能规划过程中最困难的任务。造成困难有如下几个原因: 执行任何任务都有几种方法。您可以编写 C(或其它高级语言)程序、shell 脚本、perl 脚本、awk 脚本、sed 脚本、AIXwindows 对话等等。从性能观点看,一些看来特别适合算法和程序员生产力的技术非常昂贵。 有一条准则很有用,即,抽象级别越高,就越要谨慎,以确保某个系统不会承受令人惊讶的性能。请仔细考虑由一些明显无害的构造所暗示的数据量大小和迭代数量。 单个过程的精确成本是很难确定的。困难之处不仅仅是技术上的;还有哲学上的。如果多用户运行的给定程序的多个实例正在共享程序文本页面,则哪一个进程应该负责那些内存页面呢?操作系统将最近用过的文件页面保留在内存中,以便为重新访问该数据的程序提供高速缓存的效果。重新访问数据的程序应该对用来保留数据的空间负责吗?某些评估的粒度,比如系统时钟,可以在用于同一程序连续实例的 CPU 时间上产生变化。 有两种方法来处理资源报告的模糊性和可变性。第一种是忽略模糊性,持续消除可变性的来源,直到评估变得可一致性接受。第二种方法是尝试让评估尽可能真实,并从统计上描述结果。注意后者产生与生产环境有某种相关性的结果。 系统很少专门运行单个程序的单个实例。存在几乎一直处于运行的守护程序、频繁的通信活动和通常来自多个用户的工作负载。这些活动很少线性增加。例如,增加给定程序的实例个数几乎没有增加使用的新程序文本页面数,因为大部分程序已存在于内存中。但是,附加的进程可能导致对处理器高速缓存的额外争用,所以,不仅其它进程不得不和新进程共享处理器时间,而且所有进程都会经历执行每条指令需要更多周期的情况。这实际上使得处理器速度减慢,结果导致更频繁的高速缓存未命中。 为使您的估计与具体情况所允许的一样真实,请使用以下准则: 如果程序存在,对最类似您自己需求的现有安装进行评估。最好的方法是使用容量规划工具,如 BEST/1。 如果没有合适的安装可用,则进行试安装并对综合工作负载进行评估。 如果生成与需求相匹配的综合工作负载是不实际的,则评估个体的交互作用并将结果用作仿真输入。 如果程序还不存在,查找使用同种语言和通用结构的同等程序并对其进行评估。再次强调,语言越抽象,在确定可比性时就越需谨慎。 如果同等程序不存在,则用规划的语言开发一个主要算法的原型,对这个原型进行评估并对工作负载建模。 只有当任何类型的评估都是不可能或不可行的,您才应作一个有根据的猜测。如果在规划阶段有必要对资源需求进行猜测,则在其开发阶段尽早对实际程序进行评估是很关键的。 牢记独立软件供应商(ISV)对他们的应用程序常常有可缩放的准则。 在估计资源时,我们主要对四个方面感兴趣(无特殊顺序): CPU 时间 工作负载的处理器成本 磁盘访问 工作负载产生的磁盘读写速率 LAN 流量 工作负载生成的信息包数目和交换的数据字节数 实内存 工作负载所需 RAM 的大小 以下各节讨论了在各种情况下如何确定这些值。 评估工作负载资源 如果实际程序、可比程序或原型对评估都是可用的,则技术方法的选择依赖以下几点: 除了我们要评估的工作负载以外,系统是否还在处理其它工作。 我们是否有权使用会降低性能的工具(例如,系统是否处于生产中或在评估持续时间中是否为我们所专用?). 我们能够模拟或观察真实工作负载的程度。 估计新程序需要的资源 对未编写的程序进行精确估计是不可能的。编码阶段发生的创作和重新设计是难以预见的,但下面的准则可以帮助您对需求有一个全面了解。作为一个起点,最小程序需要以下条件: 大约 50 毫秒的 CPU 时间,大部分是系统时间。 实内存 一个程序文本页面 大约 15 个页面(其中 2 个是暂存页面)用于工作(数据)段 对 libc.a 进行访问。通常这和所有其它程序共享,并当作操作系统基本成本的一部分。 大约 12 个页面调进的磁盘 I/O 操作(如果程序最近尚未编译、复制或使用)。否则什么都不需要。 除了上述一些方面,还有由设计所隐含要求的基本成本容差(给出的单元仅作示例用): CPU 时间 不包含高级迭代或昂贵子例程调用的普通程序的 CPU 消耗小得几乎不可测量。 如果提到的程序包含计算复杂的算法,则开发一个原型,对算法进行评估。 如果提到的程序使用计算复杂的库子例程,如 X 或 Motif 构造或 printf() 子例程,则用其它小程序对它们的 CPU 消耗进行评估。 实内存 每个程序文本页面允许大约 350 行代码,其中每行大约 12 字节。不要忘了编码风格与编译器选项可在任一方面产生一两种因素的差异。该容差是针对与您典型情况相关的页面的。如果您的设计在可执行程序的结束处安插有执行次数很少的子例程,则那些页面通常不消耗实内存。 引用共享库而不是 libc.a 会增加内存需求,仅从这个意义上来说,那些库并不与其它程序或正在估计程序的实例共享。为量度这些库的大小,请编写一个长期运行的引用那些库的小程序,并对进程使用 svmon -P 命令。 估计在设计中所确定的数据结构所需存储量大小。集中到最靠近的页面。 在短时间的运行中,每一个磁盘 I/O 操作使用一个内存页面。假设页面必须已是可用的。不要假设程序会等待另一个程序的页面释放。 磁盘 I/O 对于顺序 I/O,每读或写 4096 字节导致一个 I/O 操作,除非文件最近刚被访问过并且一些页面仍留在内存中。 对于随机 I/O,每一次对不同的 4096 字节页面的访问,无论大小,都会导致一个 I/O 操作,除非文件最近刚被访问过并且一些页面仍留在内存中。 每一次对大文件进行 4 KB 页面的顺序读写会占用大约 100 个单元。每一次进行 4 KB 页面的随机读写会占用大约 300 个单元。记住实际文件不一定顺序存储在磁盘上,尽管程序对它们进行顺序写和读。因此,与顺序存取成本相比,实际磁盘存取的典型 CPU 成本与随机存取成本更接近。 通信 I/O 如果磁盘 I/O 实际上是对网络文件系统(NFS)远程安装的文件系统的,则磁盘 I/O 在服务器上执行,但客户机会承担更高的 CPU 和内存要求。 任何一种 RPC 对 CPU 负载都有非常大的贡献。设计中提出的 RPC 应该预先进行最小化、批处理、原型化和评估。 每一次进行 4 KB 页面的顺序 NFS 读或写会占用客户机大约 600 个单元。每一次进行 4 KB 页面的随机 NFS 读或写会占用客户机大约 1000 个单元。 Web 浏览和 Web 服务暗示有大量的网络 I/O,同时 TCP 连接的打开和关闭非常频繁。 变换程序级别估计为工作负载估计 估计高峰和典型资源需求的最好方法是使用排队模型,如 BEST/1。您可以使用静态模型,但有冒高估或低估高峰资源的危险。在任一情况下,从资源需求的观点出发,您都需要理解工作负载中的多个程序是如何交互的。 如果您正在构建一个静态模型,请使用时间间隔,这是对大多数频繁运行或苛求的程序(通常两者是相同的)而言可接受性最差的响应时间。决定在每个时间间隔中通常运行哪些程序,这要基于您所规划的用户数、他们的思考次数、击键输入速率以及预期的混合操作。 使用以下准则: CPU 时间 在时间间隔中运行的所用程序的 CPU 需求总和。包括程序正要执行的磁盘和通信 I/O 的 CPU 需求。 如果在时间间隔中这个数字大于可用 CPU 时间的 75%,则应考虑减少用户数或增加 CPU。 实内存 操作系统内存需求随物理内存大小而变化。操作系统本身使用 6 到 8 MB。单机系统中该数字更小。后一个数字是对 LAN 连接以及使用 TCP/IP 和 NFS 的系统而言的。 在时间间隔中运行的程序所有实例的工作段需求总和,包括为程序数据结构所估计的空间。 即将运行的每个不同程序文本段的内存需求(一个程序文本副本为该程序所有实例服务)的总和。记住来自非共享库的任何(且仅仅)子例程将成为可执行程序的一部分,但这些库本身并不在内存中。 每一个由工作负载中任何程序使用的共享库所消耗的空间大小总和。再次强调,一个副本可供所有实例使用。 为了提供足够的空间用作某种文件高速缓存和自由列表,您的内存规划总和不应超过要使用的机器大小的 80%。 磁盘 I/O 每个程序的每个实例所暗示的 I/O 总数。分别计算小文件(或随机读写的大文件)与完全顺序读或写的大文件(大于 32 KB)的 I/O 总数。 除去那些您认为可以从内存中获得的 I/O。前一个时间间隔的任何读或写记录在当前时间间隔中很可能仍然可用。此外,检查提出的机器的大小并与机器工作负载的总 RAM 需求对比。操作系统需求与工作负载需求之外的所有剩余空间可能包含最近读或写的文件页面。如果您的应用程序设计如上面所述,那么很有可能您会重新使用最近访问过的数据,您可以针对高速缓存的效果计算容差。记住重新使用是在页面级别上,而不是记录级别上。如果重新使用一条给定记录的可能性很低,但每个页面又有大量记录,则在任何给定时间间隔中需要的一些记录可能会像最近使用过的其它记录一样落在同一页面中。 把净 I/O 需求(每张磁盘每秒钟的磁盘 I/O)与当前磁盘驱动器的近似容量相比较。如果随机或顺序需求超过要保存应用程序数据的相应的磁盘总容量的 75%,那么就有必要在应用程序运行时进行调谐(并且可能是扩展)。 通信 I/O 计算工作负载的带宽消耗。如果 LAN 上所有节点的总带宽消耗大于额定带宽的 70%(以太网中的 50%),您可能想使用带宽更高的网络。 对要加在服务器上的额外负载的 CPU、内存和 I/O 需求进行类似分析。 注: 记住只有当不可能进行综合评估时,这些准则才有用。任何可用来代替某个准则的应用程序特定的评估都会显著提高估计的精确性。 转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1238695.html

性能测试基础知识-处理器调度程序性能

处理器调度程序性能概述 线程支持 线程可看作开销低的进程。它是一个可分派实体,创建它需要的资源比创建一个进程需要的资源少。 进程由一个或多个线程组成。事实上,操作系统的早期发行版中负载的直接迁移就是继续创建和管理进程。每个新进程在创建时只带有单一的线程,该线程具有其父进程的优先级并与其它进程中的线程争用处理器。进程在执行时拥有它所使用的资源,而线程仅仅拥有它的当前状态。 当新的或修改的应用程序利用操作系统的线程支持创建额外的线程时,那些线程在该进程的上下文中创建。它们共享进程的私有段和其它资源。 进程中的一个用户线程有一个特定的争用作用域。如果争用作用域是全局的,则该线程与系统中所有其它线程一起争用处理器时间。在进程创建时产生的线程具有全局争用作用域。如果争用作用域本地的,则该线程与进程中的其它线程竞争以成为进程共享的处理器时间的接收方。 决定接下来应该运行哪个线程的算法叫调度策略。 进程和线程 进程是系统中的一个活动,它由某个命令、shell 程序或另一进程启动。 进程的属性如下: pid pgid uid gid 环境 cwd 文件描述符 信号操作 进程统计信息 nice 线程的属性如下: 堆栈 调度策略 调度优先级 暂挂信号 阻塞信号 线程特定的数据 每个进程由一个或多个线程组成。线程是一个单独的控制序列流。多个控制线程允许应用程序进行重叠操作,例如读取终端和写文件。 多个控制线程也允许应用程序同时为来自多个用户的请求服务。线程提供了这些能力而不需多个进程那样的额外开销,例如要通过 fork() 系统调用创建多个进程。 AIX 4.3.1 中引入了一个快速的 fork 例程 f_fork()。该例程对多线程应用程序非常有用,它们将立刻调用 exec() 子例程,前提是之前应先调用 fork() 子例程。fork() 子例程运行起来较慢,因为在实际派生及让其子例程运行全部子处理程序来初始化所有锁之前,它必须调用 fork 处理程序获得所有的库锁。f_fork() 子例程忽略这些处理程序并直接调用 kfork() 系统调用。Web 服务器是一个可以使用 f_fork() 子例程的很好的应用程序示例。 进程和线程的优先级 优先级管理工具处理进程的优先级。在 AIX V4 中,进程优先级只是线程优先级的前驱。当调用 fork() 子例程时,会创建一个进程和一个要在其中运行的线程。线程的优先级归结于进程。 内核为每个线程维护一个优先级值(有时称为调度优先级)。优先级值是一个正整数且与关联线程的重要性的变化方向相反。也就是说,较小的优先级值表示一个相对重要的线程。当调度程序寻找线程进行分派时,它选择具有较小优先级值的可分派线程。 线程可以有固定的优先级或不固定的优先级。优先级固定的线程的优先级值是一个常量,而优先级不固定的线程的优先级值根据用户线程最小优先级级别(常量 40)、线程的 nice 值(缺省值是 20,可随意由 nice 或 renice 命令进行设置)和其处理器使用的损失而变化。 线程的优先级可以固定成某个值,如果用 setpri() 子例程设置(固定)它们的优先级的话,它们可以具有小于 40 的优先级值。这些线程不会受到调度程序重算算法的影响。如果它们的优先级值固定且小于 40,这些线程将在可以运行所有用户线程之前运行和完成。例如,一个具有固定值 10 的线程将在具有固定值 15 的线程之前运行。 用户可以应用 nice 命令使线程的不固定优先级变低。系统管理员可将一个负的 nice 值应用给线程,这样就给了它较好的优先级。 下图显示了一些可以更改优先级值的方法。 图 6. 如何确定优先级值. 插图显示了如何能在执行过程中或应用了 nice 命令之后更改线程调度优先级值。优先级值越小,线程优先级越高。开始时,nice 值缺省为 20 而基本优先级缺省为 40。在执行一些操作及处理器损失后,nice 的值仍为 20 且基本优先级仍为 40。在运行 renice --5 命令后及使用和以前相同的处理器的情况下,nice 值现在是 15 而基本优先级仍为 40。在以 50 的值发出子例程 setpri() 之后,固定优先级现在是 50 而 nice 值和处理器的使用无关。 线程的 nice 值在创建线程时设置并且在线程的整个生命期中都是常量,除非用户通过 renice 命令或 setpri()、setpriority()、thread_setsched() 或 nice() 系统调用明确更改了它的值。 处理器损失是一个整数,它通过线程最近的处理器使用来计算。如果每次在一个 10 ms 的时钟滴答结束时线程受处理器控制,则最近的处理器使用值近似加 1,直到达到最大值 120。每个滴答的实际优先级损失随着 nice 的值增加。所有线程的最近处理器使用值每秒重算一次。 结果如下: 不固定优先级的线程的优先级随着其最近处理器使用的增加而变低,反之亦然。这暗示一般来讲,某线程最近被分配的时间片越多,则它被分配下一个时间片的可能性越小。 不固定优先级的线程的优先级随着其 nice 值的增加而变低,反之亦然。 注: 使用多处理器运行队列及其负载平衡机制以后,nice 或 renice 的值对线程的优先级可能没有预期的影响,因为较低优先级的运行时间可能等于或大于较高优先级的运行时间。要求 nice 或 renice 产生预期效果的线程应该放在全局运行队列中。 可以使用命令 ps 显示进程的优先级值、nice 值和短期的处理器使用值。 请参阅『处理器的控制争用』中对使用 nice 和 renice 命令的更详细的讨论。 请参阅『调谐线程优先级值的计算』,里面有处理器损失计算和最近处理器使用值衰减的详细信息。 优先级机制也用于 AIX 工作负载管理器中来加强处理器资源管理。因为在工作负载管理器下分类的线程具有的优先级由工作负载管理器管理,它们可能与没有在工作负载管理器下分类的线程具有不同的优先级行为。 线程的调度策略 下面是线程调度策略的可能值: SCHED_FIFO 这种策略的线程被调度后,它会一直运行到结束,除非被阻塞或有一个较高优先级的线程可分派,它将自愿服从处理器的控制。只有固定优先级的线程才能有 SCHED_FIFO 调度策略。 SCHED_RR 当一个 SCHED_RR 线程在时间片的末尾有控制权时,它将移动到和它具有相同优先级的可分派线程队列的尾部。只有固定优先级的线程才能有 SCHED_RR 的调度策略。 SCHED_OTHER 这个策略在“POSIX 标准 1003.4a”中作为定义的执行程序进行定义。在每个时钟中断时重算运行线程的优先级值意味着该线程可能失去控制权,因为它的优先级值已经超过了另一可分派线程的优先级值。 SCHED_FIFO2 该策略和 SCHED_FIFO 相同,只是它允许一个仅睡眠了很短时间的线程在被唤醒时可放置在其运行队列的头部。这个时间周期是相似性限制(可用 schedtune -a 进行调节)。该策略仅可用于 AIX 4.3.3 及其后续版本。 SCHED_FIFO3 调度策略设置成 SCHED_FIFO3 的线程总是放置在运行队列的头部。为了防止属于 SCHED_FIFO2 调度策略的线程放置在 SCHED_FIFO3 之前,当 SCHED_FIFO3 线程入队列时更改运行队列参数,这样属于 SCHED_FIFO2 的线程就不满足使其能够加入运行队列头部时必须满足的标准。该策略仅可用于 AIX 4.3.3 及其后续版本。 SCHED_FIFO4 只要优先级值相差 1,较高优先级的 SCHED_FIFO4 调度类线程就不会抢占当前正运行的低优先级线程。缺省行为是当前运行于某给定 CPU 的低优先级线程被有资格在同一 CPU 上运行的高优先级线程抢占。该策略仅可用于 AIX 5L V5100-01 + APAR IY22854 及其后续版本。 调度策略可用系统调用 thread_setsched() 进行设置并且仅对调用线程有效。然而,通过指定进程标识发出 setpri() 调用可将线程设置成 SCHED_RR 调度策略;setpri() 的调用者和 setpri() 的目标不必匹配。 只有那些具有 root 权限的进程可以发出 setpri() 系统调用。只有那些具有 root 权限的线程可将调度策略更改成任何 SCHED_FIFO 选项或 SCHED_RR。如果调度策略是 SCHED_OTHER,则优先级参数被 thread_setsched()子例程忽略。 线程的主要优点是适用于当前由多个异步进程组成的应用程序。这些应用程序可通过转变成多线程结构使得系统中有较轻的负载。 调度程序运行队列 调度程序维护一个所有就绪等待分派的线程的运行队列。 给定优先级的所有可分派线程在运行队列中占有一定的位置。 调度程序的基本可分派实体是线程。AIX 5.1 维护 256 个运行队列(128 个在 AIX 4.3 及以前的发行版中)。在 AIX 5.1 中,运行队列与每个线程优先级字段可能值的范围(从 0 到 255)直接相关。这个方法使调度程序更容易确定哪个线程最先运行。调度程序无需搜索一个完整的运行队列,只需要考虑一个掩码,该掩码的某一位启用后可表示在相应的运行队列中存在就绪等待运行的线程。 线程的优先级值快速而频繁地变更。持续的变动归因于调度程序重算优先级的方法。然而,这并不适用于固定优先级的线程。 从 AIX 4.3.3 开始,每个处理器都有自己的运行队列。性能工具中报告的运行队列值将是每个运行队列中所有线程的总和。让每个处理器都有自己的运行队列可节省分派锁的开销并改善总体的处理器相似性。线程通常会更加趋向于留在同一处理器中。如果因为另一处理器上的事件使某线程变得可运行且有空闲的处理器的话,即使不同于最近可运行线程曾经运行过的处理器,该线程也只会立即被分派。在可以检查处理器状态(例如在该线程的处理器上的中断)之前不会出现抢占。 在具有多个运行队列的多处理器系统中,可能出现瞬间的优先级倒置。在任何一个时间点都可能出现这种情况:某个运行队列能使若干线程具有的优先级比另一运行队列更有利。AIX 有一些机制可以随着时间的推移来进行优先级平衡,但是如果要求严格的优先级(例如,对于实时应用程序)可用一个叫做 RT_GRQ 的环境变量,如果将它设置成 ON,将导致该线程位于一个全局运行队列中。在那种情况下,将搜索全局运行队列来察看哪个线程具有最佳优先级。这可以改善中断驱动线程的性能。如果将 schedtune -F 设置成 1,以固定优先级运行的线程就放置在全局运行队列中。 运行队列中的线程平均数可在命令 vmstat 输出的第一列中看到。如果用处理器数去除这个数,结果是每个处理器上可运行线程的平均数。如果这个值大于 1,这些线程必须等待轮到它们使用处理器(这个数越大,性能延迟可能越明显)。 当某线程移到运行队列的末端时(例如,当线程在时间片的末尾拥有控制权时),它会移动到具有相同优先级值的队列中最后一个线程之后的位置上。 调度程序处理器时间片 处理器时间片是调度程序转换到另一个具有相同优先级的线程之前,一个 SCHED_RR 线程能获得的时间的总和。可以使用命令 schedtune 的选项 -t 在时间片上以 10 毫秒的增量来增加时钟滴答数(参阅『用 schedtune 命令修改调度程序时间片』)。 注: 时间片并不是保证的处理器时间量。它是一个线程在面临由另一线程取代的可能性之前可以受控的最长时间。在控制时间达到完整时间片之前有很多方法可使线程失去处理器的控制。 方式转换 用户进程在需要访问系统资源时会经历一个方式转换。这通过系统调用接口或诸如缺页故障这样的中断来实现。有两种方式: 用户方式 内核方式 花在用户方式(应用程序和共享库)下的处理器时间作为用户时间在一些命令的输出中反映出来,例如,vmstat、iostat 和 sar 命令。花在内核方式下的处理器时间作为系统时间在这些命令的输出中反映出来。 用户方式 在用户保护域中执行的程序是用户进程。在这种保护域中执行的代码以用户执行方式执行,且具有下列访问: 读/写访问进程专用区域中的用户数据 读访问用户文本和共享文本区域 使用共享内存功能访问共享数据区域 在用户保护域中执行的程序不能访问内核或内核数据段,除非通过使用系统调用间接访问。在该保护域中的程序只能影响自身的执行环境并在进程或非特权状态下执行。 内核方式 在内核保护域中执行的程序包含中断处理程序、内核进程、基内核和内核扩展(设备驱动程序、系统调用和文件系统)。这个保护域暗示以内核执行方式执行代码,具有下列访问: 读/写访问全局内核地址空间 在进程中执行时读/写访问进程区域中的内核数据 内核服务必须用来访问进程地址空间中的用户数据。 在该保护域中执行的程序会影响所有程序的执行环境,因为它们具有下列特征: 它们可访问全局系统数据 它们可使用内核服务 它们免受所有安全性约束 它们执行于处理器特权状态下。 方式转换 用户方式的进程使用的系统调用允许通过用户方式调用内核函数。直接或间接地调用系统调用来访问的函数一般由程序设计库提供,它们提供对操作系统函数的访问。 方式转换应该不同于在命令 vmstat(cs 列)和 sar(cswch/s)的输出中所看到的上下文转换。在当前运行的线程不同于该处理器上先前运行的线程时会出现上下文转换。 当下列任一情况出现时调度程序执行上下文转换: 线程必须等候某个资源(自愿),比如磁盘 I/O、网络 I/O、睡眠或锁 一个较高优先级线程被唤醒(非自愿) 线程已经用完了它的时间片(通常是 10 ms)。 上下文转换的时间、系统调用、设备中断、NFS I/O 和内核中任何其它活动都看作系统时间。 2004-8-23 19:48:39 鲜花(0) 鸡蛋(0) snappyboy 等级:论坛游民 文章:123 积分:283 注册:2003-10-20 第2楼 多处理介绍 无论何时,单处理器芯片的运行速度都存在着技术上的限制。如果单处理器无法令人满意地处理系统的工作负载,一种响应是使用多处理器来解决这个问题。 这种响应是否成功不仅仅取决于系统设计者的技术熟练程度,还取决于工作负载是否服从多处理控制。就人的任务而言,如果任务是应答一个免费电话号码的呼叫,增加人员也许不失为一个好主意,但是假如任务是开车的话,这种做法是否有效就值得怀疑了。 如果建议从一个单处理器系统迁移到一个多处理器系统的目标是为了改进性能,则下列条件必须成立: 工作负载受处理器限制并且已经使得它的单处理器系统饱和。 工作负载包含多种处理器密集的元素,例如事务或者复杂计算,这些操作可以同时并且各自独立地执行。 现有的单处理器不能升级,也不能由另一个能量充足的单处理器代替。 虽然正常情况下不变的单线程应用程序在某个多处理器环境中能正确运行,但它们的性能常常会有意外的变化。迁移到多处理器可以改善系统的吞吐量,并能改进复杂的多线程应用程序的执行时间,但是很少能改进个别的单线程命令的响应时间。 要从一个多处理器系统获得最佳性能,需要对多处理器系统独有的操作系统和硬件执行动态有所了解 对称多处理器(SMP)概念和体系结构 对于增加系统复杂性的任何变化,为了获得令人满意的操作和性能,使用多处理器产生了一些设计时必须引起注意的事项。额外的复杂性使得软/硬件权衡的作用域更大,并且比在单处理器系统中更需要软/硬件的密切配合。设计响应和权衡的不同组合使得多处理器系统的体系结构更加多样化。 这一节描述了多处理器系统的主要设计注意事项和这些事项的硬件响应。 多处理的类型 有几种多处理(MP)系统,如下所述: 非共享 MP(纯群集) 每个处理器都是一个完全独立的机器,运行操作系统的一个副本。处理器之间没有共享的部分(每一个都有自己的内存,高速缓存和磁盘),但是它们是互联的。通过 LAN 连接时,处理器之间是松散耦合的。而通过转换器连接时,处理器之间是紧密耦合的。处理器之间的通信是通过消息传送来实现的。 这样一个系统的优点是它具有很好的可伸缩性和高可用性。而缺点则是该系统是一个不为人熟悉的编程模型(消息传送)。 共享磁盘 MP 处理器拥有自身的内存和高速缓存。处理器并行运行并共享磁盘。每个处理器都运行操作系统的一份副本,并且处理器之间是松散耦合的(通过 LAN 连接)。处理器之间的通信是通过信息传送实现的。 共享磁盘的优点是保留了熟悉的编程模型的一部分(磁盘数据是可寻址和连续的,而内存则不是),而且与共享内存的系统相比,这种系统更容易实现高可用性。缺点是由于在对共享数据进行物理和逻辑访问时存在瓶颈,它的可伸缩性受到限制。 共享内存群集(SMC) 一个共享内存群集中的所有处理器有自己的资源(主存储器、磁盘和 I/0),并且每个处理器运行一份操作系统的副本。处理器之间是紧密耦合的(通过一个转换器连接)。处理器之间的通信是通过共享内存实现的。 共享内存 MP 所有处理器通过一条高速总线或者一个转换器在同一机器中紧密耦合。处理器共享同样的全局内存、磁盘和 I/0 设备。只有一份操作系统的副本跨所有处理器运行,并且操作系统必须设计为能利用这种体系结构(多线程操作系统)。 SMP 有几个优点: 它们是增加吞吐量的一种划算的方法。 由于操作系统由所有处理器共享,它们提供了一个单独的系统映像(易于管理)。 它们对一个单独的问题应用多处理器(并行编程)。 负载平衡是由操作系统实现的。 这种单处理器(UP)编程模型可用于一个 SMP 中。 对于共享数据来说,它们是可伸缩的。 所有数据可由所有处理器寻址,并且由硬件监视逻辑保持连续性。 由于通信经由全局共享内存执行,在处理器之间通信不必使用消息传送库。 更多能量的需求可通过向系统添加更多处理器来解决。然而,在一个 SMP 系统里添加更多处理器时,您必须设置关于性能增强的现实期望值。 现在越来越多的应用程序和工具都可以使用。大多数 UP 应用程序可以在 SMP 体系结构中运行或者被移植到 SMP 体系结构中。 SMP 系统有一些局限性,如下所述: 由于高速缓存相关性、锁定机制、共享对象和其它问题,可伸缩性受到限制。 需要新技术来利用多处理器,例如线程编程和设备驱动程序编程。 并行化应用程序 有两种方法可以在一个 SMP 中使应用程序并行化,如下所述: 传统方法是把应用程序分解为多个进程。这些进程使用进程间通信(IPC)方法进行通信,例如管道、信号量或者共享内存。必须能够阻塞进程使其等待事件的发生(例如来自其它进程的消息),并且进程必须用类似锁的东西协调对共享对象的访问。 另一种方法是使用面向 UNIX(POSIX)线程的可移植操作系统接口。线程和进程一样存在协调的问题,并有类似的处理机制。因此一个单独的进程可以同时有很多线程运行在不同的处理器上。协调这些线程并且使得对共享数据的访问序列化是开发者的责任。 在并行化一个应用程序的时候,考虑线程和进程两者各自的优势并且决定使用哪种方法。线程可能比进程快,并且它对内存的共享也比较容易。另一方面,进程的实现更容易分布到多个机器或者群集中。如果一个应用程序需要创建或者删除新实例,则线程会更快(在派生进程中开销更大)。就其它功能而言,线程的开销和进程差不多。 数据序列化 任何可由多个线程读或写的存储元素在程序运行中都可能改变。通常,这对多程序设计环境以及多处理环境都是成立的,但是多处理器的出现以两种方式增加了这种注意事项的作用域和重要性。 多处理器和线程的支持使得编写在线程中共享数据的应用程序具有吸引力和更容易。 内核再也不能通过简单地禁用中断来解决序列化问题。 注: 为了避免产生严重问题,共享数据的程序必须安排好,以对数据进行串行访问,而不是并行访问。在一个程序更新一个共享数据项之前,必须确保没有其它程序(包括它本身在另一个线程里运行的另一副本)会改变该项。通常读操作可以并行地执行。 用来避免程序互相干扰的主要机制是锁。锁是一种抽象概念,它代表对访问一个或多个数据项的许可。锁定和解锁的请求是原子级的;也就是说,它们的实现方式为:其结果既不受中断也不受多处理器访问的影响。所有访问一个共享数据项的程序在处理它之前必须先获得与它相关的锁。如果这个锁已经由另一个程序(或者另一个运行同一程序的线程)占有,则请求的程序必须推迟访问,直到锁变得可用。 除了等待锁所花的时间之外,序列化也增加了一个线程成为不可分派线程所花的时间。当线程不可分派时,其它线程很可能会使这个不可分派线程的高速缓存线路被替换,这将导致线程最后获得锁并被分派时内存等待时间成本增加。 操作系统的内核包含很多共享的数据项,所以它必须在内部进行序列化。因此序列化延迟甚至可能在一个不与其它程序共享数据的应用程序中发生,因为由该程序使用的内核服务必须序列化共享的内核数据。 锁的类型 开放软件基金会/1(OSF/1)1.1 的锁定方法被作为一个 AIX 的多处理器锁定功能模型使用。然而,由于系统是可抢占和可调页的,对 OSF/1 1.1 锁定模型增加了一些特征。简单和复杂的锁都是可抢占的。一个线程在尝试获得一个忙状态的简单锁时也可以睡眠,如果锁的所有者当前并不在运行的话。另外,当一个处理器在一个简单锁上自旋一段时间(这段时间是一个全系统的变量)以后,这个简单锁会变成睡眠锁。 锁粒度 一个在多处理器环境中工作的程序员必须决定对共享数据一定要创建多少单独的锁。如果只有一个锁来序列化整个共享数据项的集合,则相比之下很可能出现锁争用。广泛使用锁的存在给系统吞吐量加了上限。 如果每一个不同的数据项都有自己的锁,则两个线程争用这个锁的概率相对来说就比较低。然而,每一个附加的锁定和解锁调用都会消耗处理器时间,并且多个锁的存在使得可能发生死锁。最简单的死锁情况如下图所示,其中线程 1 拥有锁 A 并且正在等待锁 B。同时,线程 2 拥有锁 B 并且正在等待锁 A。这两个程序都永远用不上会打破死锁的 unlock() 调用。通常对死锁的预防措施是建立一个协议,根据该协议,所有使用一个指定的锁集合的程序必须始终按照完全相同的顺序获得它们。 根据排队理论,一个资源闲置得越少,要得到它的平均等待时间就越长。这种关系是非线性的;如果锁的个数翻倍,平均等待这个锁的时间就比原来的两倍还要多。 减少对锁的等待时间的最有效方法是减少这个锁所保护的范围大小。下面是一些准则: 减少对任何锁的请求频率。 只锁定访问共享数据的代码,而不是一个组件的所有代码(这将减少锁的持有时间)。 只锁定特定的数据项或结构,而不是整个例程。 始终将锁和特定的数据项或结构关联起来,而不是和例程关联。 对于大的数据结构,为结构的每一元素选择一个锁,而不是为整个结构选择一个锁。 当持有一个锁时,从不执行同步 I/O 或者任何其它阻塞活动。 如果您对您组件中的同一数据有多个访问,请试着把它们移到一起,以便它们可以包含在一个锁定 — 解锁操作中。 避免双唤醒的情况。如果您在一个锁下修改了一些数据,并且不得不通知某人您做了这件事,则在公布唤醒之前请释放该锁。 如果必须同时持有两个锁,则最后请求那个最忙的锁。 另一方面,过细粒度将增加对锁的请求和释放的频率,因而会增加额外的指令。您必须在过细和过粗粒度之间找到平衡。最佳粒度不得不通过试验和错误找到,这也是一个 MP 系统中的最大挑战之一。 锁定开销 请求锁,等待锁和释放锁在几方面增加了处理开销: 一个支持多处理的程序总是进行相同的锁定和解锁处理,即使它是在一个单处理器里运行或者是一个多处理器系统里对于这个锁的唯一使用者。 当一个线程请求一个由另一线程持有的锁时,发出请求的线程可能会自旋一会或者置于睡眠状态,如果可能的话,会分派另一个线程。这会消耗处理器时间。 广泛使用锁的存在给系统吞吐量加了一个上限。例如,如果一个给定的程序花 20% 的执行时间来持有一个互斥锁,这个程序最多只有五个实例能同时运行,不管系统里有多少个处理器。事实上,即使只有五个实例,它们也很可能永远不会精确同步,以免互相等待。(参阅『多处理器吞吐量可伸缩性』)。 等待锁 当一个线程需要另一个线程已拥有的锁时,该线程被阻塞并且必须等到锁变得可用为止。有两种不同的等待方式: 对于只被持有很短时间的锁来说,自旋锁是很适合的。它允许等待中的线程保持其处理器重复检查某个死循环(自旋)里的锁定位,直到锁变得可用。自旋导致 CPU 时间(内核或内核扩展锁定的时间)增加。 睡眠锁适合于可能会被持有较长时间的锁。线程会睡眠到锁可用为止,当锁变得可用后,它会被放回到运行队列里。睡眠导致更多的闲置时间。 等待总会降低系统性能。如果使用自旋锁,处理器是繁忙的,但是它不是在做有用功(不是在为吞吐量出力)。如果使用睡眠锁,会导致上下文切换和分派的开销以及随之而来的高速缓存未命中的增加。 操作系统开发者们可以在两种类型的锁之间选择:在等待锁变得可用时允许进程自旋和睡眠的互斥简单锁,和在等待锁变得可用时可以自旋和阻塞进程的复杂读写锁。 一些约定管理着使用锁的规则。不管是硬件还是软件都没有实施或校验的机制。尽管使用锁已经使得 AIX V4 是“MP 安全”的,开发者们还是有责任定义和实现一个合适的锁定策略来保护他们自己的全局数据。 高速缓存一致性 在设计多处理器时,工程师们对保证高速缓存的一致性给予了相当多的注意。他们取得了成功;但是高速缓存一致性是以性能为代价的。我们需要理解这个遭受攻击的问题: 如果每个处理器都有一个反映内存不同部分状态的高速缓存,就可能会有两个或更多高速缓存拥有相同线路的副本。也有可能是一个给定的线路会包含不止一个可锁定的数据项。如果两个线程对那些数据项作了适当的序列化更改,结果可能是两个高速缓存都以不同的,错误版本的内存线路而告终。换句话说,系统的状态不再一致,因为系统包含了应该是一个特定内存区域的内容的两个不同版本。 对高速缓存一致性问题的解决方案通常包括在线路修改之后,除了一条线路以外,使所有重复线路都失效。尽管硬件使用监视逻辑使线路失效,没有任何软件干预的话,任何高速缓存线路已经失效的处理器由于随之而来的延迟,将会在下一次寻址到该线路时出现高速缓存未命中。 监视是用来解决高速缓存一致性问题的逻辑。处理器中的监视逻辑每次修改了其高速缓存中的一个字后,会在总线上广播一条消息。监视逻辑也在总线上监视,寻找来自其它处理器的这种消息。 当一个处理器检测到另一个处理器已经更改了存在于它本身高速缓存内的一个地址的值时,监视逻辑会使得它自己的高速缓存中的该项失效。这被称为交叉式失效。交叉式失效提醒处理器高速缓存中的值已经无效了,处理器必须在别处(内存或其它高速缓存)寻找正确的值。由于交叉式失效增加了高速缓存未命中率,而监视协议增加了总线流量,因而解决高速缓存的一致性问题会降低所有 SMP 的性能和可伸缩性。 处理器相似性和绑定 如果一个线程中断后又重新分派到同一个处理器中,该处理器的高速缓存也许仍含有属于该线程的线路。如果该线程被分派到不同的处理器,它将很可能经历一系列高速缓存未命中,直到它的高速缓存工作集从 RAM 或其它处理器的高速缓存中检索到。另一方面,如果一个可分派的线程必须等到它先前在其中运行的处理器可用,该线程也许会经历一个更长的延迟。 处理器相似性是指将一个线程分派到先前运行它的处理器之上的概率。对处理器相似性的强调程度应随线程的高速缓存工作集大小直接变化,而随自它上一次分派以来的时间长短反向变化。AIX V4 分派器强制对处理器的相似性,因此相似性是由操作系统暗中完成的。 最高程度的处理器相似性是把一个线程绑定到一个特定处理器上。绑定意味着线程将只分派到该处理器,不管其它处理器是否可用。bindprocessor 命令和 bindprocessor() 子例程将一个特定进程的线程绑定到一个特殊的处理器(参阅 bindprocessor 命令)上。显式绑定是通过 fork() 和 exec() 系统调用继承而来的。 绑定对于 CPU 密集的很少经历中断的程序是有用的。有时,它对一般的程序可能会有反作用,因为它也许会在一个 I/O 之后延迟对一个线程的重新分派,直到线程所绑定的处理器变得可用。如果线程已阻塞了一个 I/O 操作的持续时间,它的处理上下文中的大部分不太可能还保留在它所绑定的处理器的高速缓存中。如果该线程被分派到下一个可用的处理器中,它很可能会得到更好的服务。 内存和总线争用 在一个单处理器中,一些内部资源(例如内存条和 I/O 或者内存总线)的争用通常是组件使用时间的一小部分。在一个多处理器中,这些影响会变得更重要,特别是如果高速缓存一致性算法增加了对 RAM 的访问数量 SMP 性能问题 为了有效使用 SMP,当您尝试提高性能时请考虑以下问题: 工作负载并行性 SMP 系统特有的主要性能问题是工作负载的并行性,这个问题可以这样表达:“现在我们有 n 个处理器,我们如何保持它们全都有效地工作”?如果在任何指定时间,一个四路的多处理器系统中只有一个处理器在做有用功,则它比一个单处理器好不了多少。由于用来避免处理器间干扰的额外代码,它可能会更糟。 工作负载并行性是序列化的补充。在系统软件或应用程序工作负载(或者是这两者之间的交互作用)要求序列化这一点上,工作负载并行性就得遭受损失。 工作负载并行性也可以通过增加处理器相似性来更像期望的那样下降。从处理器相似性得来的提高的高速缓存效率可能会使得程序更快的完成。工作负载并行性是降低了(除非有更多可分派的线程处于可用状态),但是响应时间得到了改善。 工作负载并行性的一个组成部分,进程并行性,是指一个多线程进程在任何时候都拥有多个可分派线程的程度。 吞吐量 一个 SMP 系统的吞吐量主要由以下因素决定: 一直处于高级别的工作负载并行性。处理器在特定时间里拥有更多的可分派线程并不能补偿一些处理器在其它时间闲置的情况。 锁争用的数量。 处理器相似性的程度。 响应时间 一个处于 SMP 系统中的特定程序的响应时间取决于: 该程序的进程并行性级别。如果该程序一直拥有两个或更多可分派线程,它的响应时间很可能会在 SMP 环境里得到改善。如果程序只包含一个单独的线程,它的响应时间最多也就是和一个处于相同速度单处理器中的程序相当。 与程序其它实例或者其它使用相同锁的程序之间的锁争用的数量。 程序对处理器的相似性程度。如果程序每次都被分派到不同的处理器中,该处理器中没有它的任何高速缓存线,则该程序可能会比在一个相当的单处理器中运行得更慢。 工作负载多处理 在快速计算机上运行繁重工作负载的多程序设计操作系统给人的感觉印象是有几件事情在同时发生。事实上,很多费力的工作负载在任意给定时刻并没有大量的可分派线程,即使是当它运行在一个序列化相对来说不是大问题的单处理器系统中时。除非至少总是有与处理器一样多的可分派线程,要不然总有一个或多个处理器在一部分时间里闲置。 可分派线程的数量是系统中线程的总数 减去正在等待 I/O 的线程数, 减去正在等待共享资源的线程数, 减去正在等待另一个线程结果的线程数, 减去正对它们自己的请求睡眠的线程数。 工作负载据说是可以多处理的,从这一点来说,它不论何时都显示出与系统中的处理器数一样多的可分派线程数。请注意,这并不只意味着可分派线程的平均数量和处理器一样多。如果可分派线程数在一半时间里为零,剩余时间里是处理器计数的两倍,则可分派线程的平均数将等于处理器数,但是系统里任一给定的处理器只能在一半时间里工作。 增加工作负载的多处理性涉及到以下的一个或两个方面: 确认并解决引起线程等待的任何瓶颈 增加系统中的线程总数 这些解决方案不是独立的。假如有一个单独的、主要的系统瓶颈,增加现有的通过该瓶颈的工作负载的线程数将只会仅仅增加线程等待的比例。假如目前没有瓶颈,增加线程数可能会创建一个瓶颈。 多处理器吞吐量可伸缩性 实际工作负载并不能在 SMP 系统中极佳的伸缩。一些禁止极佳伸缩的因素如下所述: 当处理器的数量增加时,总线/开关的争用也增加。 内存争用增加(所有内存都为所有处理器共享) 随着内存不断消耗,高速缓存未命中的成本增加 高速缓存交叉式失效和读取另一个高速缓存以保持高速缓存一致性 由于更高分派率而引起的增加的高速缓存未命中(更多的进程/线程需要在系统中分派) 增加的同步指令成本 由于更大的操作系统和应用程序数据结构而增加的高速缓存未命中 为锁定/解锁而增加的操作系统和应用程序路径长度 由于等待锁而增加的操作系统和应用程序路径长度 所有这些因素都对称为工作负载的可伸缩性起作用。可伸缩性是工作负载吞吐量受益于其它处理器可用性的程度。它通常表示为一个多处理器的工作负载吞吐量由一个相当的单处理器的吞吐量所除得到的商。例如,如果一个单处理器在给定的工作负载下每秒获得 20 个请求,而一个四处理器的系统每秒获得 58 个请求,则比例因子将是 2.9。这个工作负载是高度可伸缩的。一个专门由长期运行、计算机密集的程序组成的工作负载,如果其 I/O 或其它内核活动是可忽略的,并且没有共享数据,则可以在一个四路系统中达到 3.2 到 3.9 的比例因子。然而,现实中大多数工作负载不能达到这个水平。由于可伸缩性是很难估计的,可伸缩性的假设应基于真实工作负载的评估值。 在多处理器上,两个处理器处理程序执行,但是仍然只有一个锁。为简单起见,显示了所有影响处理器 B 的锁争用。在所示的时间段里,多处理器处理 14 个命令。因此比例因子为 1.83。我们只讨论两个处理器,因为更多处理器的情况不会有什么变化。现在锁在 100% 的时间里都处于使用状态。在一个四路的多处理器中,比例因子可能是 1.83 或更小。 实际程序很少会像插图中的命令那样对称。另外,我们仅仅考虑了争用的一个尺度:锁定。如果我们把高速缓存一致性和处理器相似性的影响包括进来,无疑比例因子几乎会更小。 该示例说明了工作负载通常不能通过简单添加处理器来使它更快运行。确定和最小化线程之间的争用源也是必要的。 伸缩是与工作负载相关的。一些公布的基准程序暗示高水平的可伸缩性是容易获得的。大多数这样的基准程序是通过运行小型的 CPU 密集程序的组合而构造出来的,这些 CPU 密集程序几乎不用什么内核服务。这些基准程序的结果代表了可伸缩性的上限,而不是现实期望。 基准程序的另一个值得注意的有趣观点是通常情况下,一个单路 SMP 的运行速度会比运行操作系统的 UP 版本的同等单处理器慢(大约 5%-15%)。 多处理器响应时间 一个多处理器只能把一个独立程序的执行时间改进到让该程序可以多线程方式运行的程度。有几种方法可以让一个单独程序的某些部分实现并行执行: 显式调用 libpthreads.a 子例程(或者,在老式程序里调用 fork() 子例程)来创建多个同时运行的线程。 用一个并行化的编译器或者预处理器处理程序,该编译器或预处理器会检测到可同时执行的代码序列,并生成多个线程来并行运行这些代码。 使用一个本身是多线程的软件包。 除非使用这些技术的一种或多种,程序在一个多处理器系统中不会比在一个相当的单处理器中运行得快。事实上,由于程序会经历更多的锁定开销和在不同时间分派到不同处理器而产生的延迟,它有可能会更慢。 即使所有可用的技术都用到了,最大限度的改进也受到一个称为“Amdahl 定律”规则的限制。 举例来说,如果一个程序的 50% 的处理必须顺序执行,50% 可以并行执行,则最大的响应时间改进小于因子 2(在另一个闲置的 4 路多处理器中,该值至多为 1.6)。 转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1238694.html

性能:软件测试的重中之重

性能测试在软件的质量保证中起着重要的作用,它包括的测试内容丰富多样。中国软件评测中心将性能测试概括为三个方面:应用在客户端性能的测试、应用在网络上性能的测试和应用在服务器端性能测试。通常情况下,三方面有效、合理的结合,可以达到对系统性能全面的分析和瓶颈的预测。
  应用在客户端性能的测试
  应用在客户端性能测试的目的是考察客户端应用的性能,测试的入口是客户端。它主要包括并发性能测试、疲劳强度测试、大数据量测试和速度测试等,其中并发性能测试是重点。
  ◆ 并发性能测试是重点
  并发性能测试的过程是一个负载测试和压力测试的过程,即逐渐增加负载,直到系统的瓶颈或者不能接收的性能点,通过综合分析交易执行指标和资源监控指标来确定系统并发性能的过程。负载测试(Load Testing)是确定在各种工作负载下系统的性能,目标是测试当负载逐渐增加时,系统组成部分的相应输出项,例如通过量、响应时间、CPU负载、内存使用等来决定系统的性能。负载测试是一个分析软件应用程序和支撑架构、模拟真实环境的使用,从而来确定能够接收的性能过程。压力测试(Stress Testing)是通过确定一个系统的瓶颈或者不能接收的性能点,来获得系统能提供的最大服务级别的测试。
  并发性能测试的目的主要体现在三个方面:以真实的业务为依据,选择有代表性的、关键的业务操作设计测试案例,以评价系统的当前性能;当扩展应用程序的功能或者新的应用程序将要被部署时,负载测试会帮助确定系统是否还能够处理期望的用户负载,以预测系统的未来性能;通过模拟成百上千个用户,重复执行和运行测试,可以确认性能瓶颈并优化和调整应用,目的在于寻找到瓶颈问题。
  当一家企业自己组织力量或委托软件公司代为开发一套应用系统的时候,尤其是以后在生产环境中实际使用起来,用户往往会产生疑问,这套系统能不能承受大量的并发用户同时访问? 这类问题最常见于采用联机事务处理(OLTP)方式数据库应用、Web浏览和视频点播等系统。这种问题的解决要借助于科学的软件测试手段和先进的测试工具。
  ◆ 举例说明:电信计费软件
  众所周知,每月20日左右是市话交费的高峰期,全市几千个收费网点同时启动。收费过程一般分为两步,首先要根据用户提出的电话号码来查询出其当月产生费用,然后收取现金并将此用户修改为已交费状态。一个用户看起来简单的两个步骤,但当成百上千的终端,同时执行这样的操作时,情况就大不一样了,如此众多的交易同时发生,对应用程序本身、操作系统、中心数据库服务器、中间件服务器、网络设备的承受力都是一个严峻的考验。决策者不可能在发生问题后才考虑系统的承受力, 预见软件的并发承受力, 这是在软件测试阶段就应该解决的问题。
  目前,大多数公司企业需要支持成百上千名用户,各类应用环境以及由不同供应商提供的元件组装起来的复杂产品,难以预知的用户负载和愈来愈复杂的应用程序,使公司担忧会发生投放性能差、用户遭受反应慢、系统失灵等问题。其结果就是导致公司收益的损失。
  如何模拟实际情况呢? 找若干台电脑和同样数目的操作人员在同一时刻进行操作,然后拿秒表记录下反应时间? 这样的手工作坊式的测试方法不切实际,且无法捕捉程序内部变化情况,这样就需要压力测试工具的辅助。
  测试的基本策略是自动负载测试,通过在一台或几台PC机上模拟成百或上千的虚拟用户同时执行业务的情景,对应用程序进行测试,同时记录下每一事务处理的时间、中间件服务器峰值数据、数据库状态等。通过可重复的、真实的测试能够彻底地度量应用的可扩展性和性能,确定问题所在以及优化系统性能。预先知道了系统的承受力,就为最终用户规划整个运行环境的配置提供了有力的依据。
  ◆ 并发性能测试前的准备工作
  测试环境:配置测试环境是测试实施的一个重要阶段,测试环境的适合与否会严重影响测试结果的真实性和正确性。测试环境包括硬件环境和软件环境,硬件环境指测试必需的服务器、客户端、网络连接设备以及打印机/扫描仪等辅助硬件设备所构成的环境;软件环境指被测软件运行时的操作系统、数据库及其他应用软件构成的环境。
  一个充分准备好的测试环境有三个优点:一个稳定、可重复的测试环境,能够保证测试结果的正确;保证达到测试执行的技术需求;保证得到正确的、可重复的以及易理解的测试结果。
  测试工具:并发性能测试是在客户端执行的黑盒测试,一般不采用手工方式,而是利用工具采用自动化方式进行。目前,成熟的并发性能测试工具有很多,选择的依据主要是测试需求和性能价格比。著名的并发性能测试工具有QALoad、LoadRunner、Benchmark Factory和Webstress等。这些测试工具都是自动化负载测试工具,通过可重复的、真实的测试,能够彻底地度量应用的可扩展性和性能,可以在整个开发生命周期、跨越多种平台、自动执行测试任务,可以模拟成百上千的用户并发执行关键业务而完成对应用程序的测试。
  测试数据:在初始的测试环境中需要输入一些适当的测试数据,目的是识别数据状态并且验证用于测试的测试案例,在正式的测试开始以前对测试案例进行调试,将正式测试开始时的错误降到最低。在测试进行到关键过程环节时,非常有必要进行数据状态的备份。制造初始数据意味着将合适的数据存储下来,需要的时候恢复它,初始数据提供了一个基线用来评估测试执行的结果。
  在测试正式执行时,还需要准备业务测试数据,比如测试并发查询业务,那么要求对应的数据库和表中有相当的数据量以及数据的种类应能覆盖全部业务。
  模拟真实环境测试,有些软件,特别是面向大众的商品化软件,在测试时常常需要考察在真实环境中的表现。如测试杀毒软件的扫描速度时,硬盘上布置的不同类型文件的比例要尽量接近真实环境,这样测试出来的数据才有实际意义。
  ◆ 并发性能测试的种类与指标
  并发性能测试的种类取决于并发性能测试工具监控的对象,以QALoad自动化负载测试工具为例。软件针对各种测试目标提供了DB2、DCOM、ODBC、ORACLE、NETLoad、Corba、QARun、SAP、SQLServer、Sybase、Telnet、TUXEDO、UNIFACE、WinSock、WWW、Java Script等不同的监控对象,支持Windows和UNIX测试环境。
  最关键的仍然是测试过程中对监控对象的灵活应用,例如目前三层结构的运行模式广泛使用,对中间件的并发性能测试作为问题被提到议事日程上来,许多系统都采用了国产中间件,选择Java Script监控对象,手工编写脚本,可以达到测试目的。
  采用自动化负载测试工具执行的并发性能测试,基本遵循的测试过程有:测试需求与测试内容,测试案例制定,测试环境准备,测试脚本录制、编写与调试,脚本分配、回放配置与加载策略,测试执行跟踪,结果分析与定位问题所在,测试报告与测试评估。
  并发性能测试监控的对象不同,测试的主要指标也不相同,主要的测试指标包括交易处理性能指标和UNIX资源监控。其中,交易处理性能指标包括交易结果、每分钟交易数、交易响应时间(Min:最小服务器响应时间;Mean:平均服务器响应时间;Max:最大服务器响应时间;StdDev:事务处理服务器响应的偏差,值越大,偏差越大;Median:中值响应时间;90%:90%事务处理的服务器响应时间)、虚拟并发用户数。
  应用实例:“新华社多媒体数据库 V1.0”性能测试
  中国软件评测中心(CSTC)根据新华社技术局提出的《多媒体数据库(一期)性能测试需求》和GB/T 17544《软件包质量要求和测试》的国家标准,使用工业标准级负载测试工具对新华社使用的“新华社多媒体数据库 V1.0”进行了性能测试。
  性能测试的目的是模拟多用户并发访问新华社多媒体数据库,执行关键检索业务,分析系统性能。
  性能测试的重点是针对系统并发压力负载较大的主要检索业务,进行并发测试和疲劳测试,系统采用B/S运行模式。并发测试设计了特定时间段内分别在中文库、英文库、图片库中进行单检索词、多检索词以及变检索式、混合检索业务等并发测试案例。疲劳测试案例为在中文库中并发用户数200,进行测试周期约8小时的单检索词检索。在进行并发和疲劳测试的同时,监测的测试指标包括交易处理性能以及UNIX(Linux)、Oracle、Apache资源等。
  测试结论:在新华社机房测试环境和内网测试环境中,100M带宽情况下,针对规定的各并发测试案例,系统能够承受并发用户数为200的负载压力,最大交易数/分钟达到78.73,运行基本稳定,但随着负载压力增大,系统性能有所衰减。
  系统能够承受200并发用户数持续周期约8小时的疲劳压力,基本能够稳定运行。
  通过对系统UNIX(Linux)、Oracle和Apache资源的监控,系统资源能够满足上述并发和疲劳性能需求,且系统硬件资源尚有较大利用余地。
  当并发用户数超过200时,监控到HTTP 500、connect和超时错误,且Web服务器报内存溢出错误,系统应进一步提高性能,以支持更大并发用户数。
  建议进一步优化软件系统,充分利用硬件资源,缩短交易响应时间。
  ◆ 疲劳强度与大数据量测试
  疲劳测试是采用系统稳定运行情况下能够支持的最大并发用户数,持续执行一段时间业务,通过综合分析交易执行指标和资源监控指标来确定系统处理最大工作量强度性能的过程。
  疲劳强度测试可以采用工具自动化的方式进行测试,也可以手工编写程序测试,其中后者占的比例较大。
  一般情况下以服务器能够正常稳定响应请求的最大并发用户数进行一定时间的疲劳测试,获取交易执行指标数据和系统资源监控数据。如出现错误导致测试不能成功执行,则及时调整测试指标,例如降低用户数、缩短测试周期等。还有一种情况的疲劳测试是对当前系统性能的评估,用系统正常业务情况下并发用户数为基础,进行一定时间的疲劳测试。
  大数据量测试可以分为两种类型:针对某些系统存储、传输、统计、查询等业务进行大数据量的独立数据量测试;与压力性能测试、负载性能测试、疲劳性能测试相结合的综合数据量测试方案。大数据量测试的关键是测试数据的准备,可以依靠工具准备测试数据。
  速度测试目前主要是针对关键有速度要求的业务进行手工测速度,可以在多次测试的基础上求平均值,可以和工具测得的响应时间等指标做对比分析。
  应用在网络上性能的测试
  应用在网络上性能的测试重点是利用成熟先进的自动化技术进行网络应用性能监控、网络应用性能分析和网络预测。
  ◆ 网络应用性能分析
  网络应用性能分析的目的是准确展示网络带宽、延迟、负载和TCP端口的变化是如何影响用户的响应时间的。利用网络应用性能分析工具,例如Application Expert,能够发现应用的瓶颈,我们可知应用在网络上运行时在每个阶段发生的应用行为,在应用线程级分析应用的问题。可以解决多种问题:客户端是否对数据库服务器运行了不必要的请求?当服务器从客户端接受了一个查询,应用服务器是否花费了不可接受的时间联系数据库服务器?在投产前预测应用的响应时间;利用Application Expert调整应用在广域网上的性能;Application Expert能够让你快速、容易地仿真应用性能,根据最终用户在不同网络配置环境下的响应时间,用户可以根据自己的条件决定应用投产的网络环境。
  ◆ 网络应用性能监控
  在系统试运行之后,需要及时准确地了解网络上正在发生什么事情;什么应用在运行,如何运行;多少PC正在访问LAN或WAN;哪些应用程序导致系统瓶颈或资源竞争,这时网络应用性能监控以及网络资源管理对系统的正常稳定运行是非常关键的。利用网络应用性能监控工具,可以达到事半功倍的效果,在这方面我们可以提供的工具是Network Vantage。通俗地讲,它主要用来分析关键应用程序的性能,定位问题的根源是在客户端、服务器、应用程序还是网络。在大多数情况下用户较关心的问题还有哪些应用程序占用大量带宽,哪些用户产生了最大的网络流量,这个工具同样能满足要求。
  ◆ 网络预测
  考虑到系统未来发展的扩展性,预测网络流量的变化、网络结构的变化对用户系统的影响非常重要。根据规划数据进行预测并及时提供网络性能预测数据。我们利用网络预测分析容量规划工具PREDICTOR可以作到:设置服务水平、完成日网络容量规划、离线测试网络、网络失效和容量极限分析、完成日常故障诊断、预测网络设备迁移和网络设备升级对整个网络的影响。
  从网络管理软件获取网络拓扑结构、从现有的流量监控软件获取流量信息(若没有这类软件可人工生成流量数据),这样可以得到现有网络的基本结构。在基本结构的基础上,可根据网络结构的变化、网络流量的变化生成报告和图表,说明这些变化是如何影响网络性能的。 PREDICTOR提供如下信息:根据预测的结果帮助用户及时升级网络,避免因关键设备超过利用阀值导致系统性能下降;哪个网络设备需要升级,这样可减少网络延迟、避免网络瓶颈;根据预测的结果避免不必要的网络升级。
  应用在服务器上性能的测试
  对于应用在服务器上性能的测试,可以采用工具监控,也可以使用系统本身的监控命令,例如Tuxedo中可以使用Top命令监控资源使用情况。实施测试的目的是实现服务器设备、服务器操作系统、数据库系统、应用在服务器上性能的全面监控,测试原理如下图。
  (暂时略)
  图:应用在服务器上的性能测试原理图
  UNIX资源监控指标和描述
  监控指标 描述
  平均负载 系统正常状态下,最后60秒同步进程的
  平均个数
  冲突率 在以太网上监测到的每秒冲突数
  进程/线程交换率 进程和线程之间每秒交换次数
  CPU利用率 CPU占用率(%)
  磁盘交换率 磁盘交换速率
  接收包错误率 接收以太网数据包时每秒错误数
  包输入率 每秒输入的以太网数据包数目
  中断速率 CPU每秒处理的中断数
  输出包错误率 发送以太网数据包时每秒错误数
  包输入率 每秒输出的以太网数据包数目
  读入内存页速率 物理内存中每秒读入内存页的数目
  写出内存页速率 每秒从物理内存中写到页文件中的内存页数
  目或者从物理内存中删掉的内存页数目
  内存页交换速率 每秒写入内存页和从物理内存中读出页的个数
  进程入交换率 交换区输入的进程数目
  进程出交换率 交换区输出的进程数目
  系统CPU利用率 系统的CPU占用率(%)
  用户CPU利用率 用户模式下的CPU占用率(%)
  磁盘阻塞 磁盘每秒阻塞的字节数 转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1238697.html

性能指标参考 - 解决方案最佳实践

我们将向您展示我们是如何充分利用软件,如何合理组织硬件使 www.microsoft.com 以最佳效率稳定工作的。此处是关于站点的庞大性和复杂性的一些简要说明。站点: 流量
每天浏览六千万页
每天三亿次点击
每天四百一十万来访者
每月两千五百万用户
每天成功下载六万多亿字节 增长
在 98 年 7 月至 99 年 7 月间页面查阅增长了百分之二百零五
在此 12 个月间用户人数增长了百分之一百四十二 内容
32 GB 的内容
417,000 HTML 或 ASP 文件
434,000 GIF 或 JPG 图像文件
大约 50 GB 可下载文件
在世界范围内,随时进行内容更新。硬件
八个 100 兆比特容量的内部以太网
2 条 OC12 向 Internet 提供 1.2 兆比特的容量
运行于 Compaq Proliant 5500s 和 Dell PowerEdge 6300s 平台上,它们都具有 4 个 Pentium III 处理器以及 512 兆字节 (MB) 的 RAM。 软件
Microsoft Windows 2000 Advanced Server
Microsoft Internet Information Server (IIS)5.0
Microsoft Site Server 3.0
Microsoft SQL Server 7
其他 Microsoft 工具和应用程序功能强大的解决方案www.microsoft.com 在 1994 年起步阶段,只以一台机器就足以应付处理每天一百万次点击。 这在现在看起来似乎有点可笑。仅在设于华盛顿州雷得蒙得 (Redmond, Wash.) 的数据中心每月就要受到多达三亿次的点击。 那么在硬件费用维持在最低限度的前提下,该站点是如何应付其爆炸性的增长的呢? 它是如何管理世界最大的数据库之一的呢? 它是如何面对分散发布环境的挑战的呢? 而它又是如何使站点的有效利用率达到百分之百的呢? 据站点的工程师们介绍,答案在于所使用的得力软件。整个站点构架于 Microsoft Windows 2000 Advanced Server、IIS 5.0 和 SQL Server 7 等软件之上。 “我们的站点充分显示了 Microsoft 技术,”系统操作经理 Todd Weeks 说,“我们每天都在向世人证明我们可以通过百分之百使用 Microsoft 技术来建立并维护世界最大的站点之一。” 挑战www.microsoft.com 不仅是一个拥有几十万页内容的庞大站点;不仅要受到每天数百万次的点击;也不仅仅具有持续不断的增长速度。站点工程师们认为这些都只不过是相对容易应付的挑战。 最为有趣的挑战是 www.microsoft.com 工作在分散式的发布环境之下。为站点提供信息的 300 余名撰稿人和开发员分布在世界上 51 个地点。这些内容提供者每小时都可更新 www.microsoft.com。事实上,站点上的内容每天都有百分之五至百分之六的内容被更新。 当您想到位于雷得蒙得的 110 台内容服务器包含了组成 www.microsoft.com 的 420,000 页信息,是否会觉得发布环境的复杂性的确令人望而生畏。但最终结果是 www.microsoft.com 上的信息总是当前的并且总能得到及时更新。 一个每班八人,三班轮换的工作组不间断地工作以确保 www.microsoft.com 每周七天,每天二十四小时都能正常运转。“我们的目标是保持站点在百分之九十九点八的时间内都可被用户访问。”Weeks 说。 那么我们如何达到百分之九十九点八的有效利用率呢?这可是一个很高的目标啊。(百分之零点二的停机时间是用于进行日常维护,这是不可避免的。)
首先,在硬件方面www.microsoft.com 背后的物理结构看起来出人意料的平常。三十五台服务器用来管理一般性的 Web 内容;四十五台服务器用来管理 SQL;十二台用来响应查询;十六台负责提供下载服务;另外三十台在分布式数据中心;还有三台管理 FTP 内容。位于海外的另外的服务器处理一些国际工作量。 服务器通常运行得非常稳定。它们被设定最多同时接待 6,000 位用户,平均起来每台只需轻松运载 1,000 至 1,500 名用户。处理器的占用率通常在百分之四十至五十之间。 硬件的组织是以保证最终用户获得快速可靠的服务为目标的。每台集线器、路由器、交换机都采取了相应的备份措施,用以确保即便是在机器出现问题的时候,站点也能继续正常工作。“多台服务器分布在多个局域网上,这就确保了硬件方面的万无一失。”Weeks 说。 另外,许多 SQL 服务器具有热备份能力。“我们拥有许多随时备用的硬件,仅仅是为了以防万一。”Weeks 说。即便是用来备份的 SQL 服务器,也被充分利用上了。每天一次,联机服务器上的内容被复制到备份服务器上,后者按需要对信息进行组织分类。这意味着联机服务器全天均可提供服务。 “这极大地减小了负荷,”Weeks 说,“分配过程对服务器的处理器来说强度是很大的,并将减缓每秒的请求次数。” 为了保证速度和可用性,8 个以太网每秒可向数据中心服务器提供 600 兆比特的容量。两条 OC12 线将 www.microsoft.com 连至 Internet 骨干网上,每条每秒可提供 1,200 兆比特或 150 MB 的容量。 从长远来看,以上的容量可传输存储在 3 GB 的硬盘上的所有信息——相当于标准 PC 至 Internet 20 秒间的传输量。 那么 Microsoft 何不购买更多的服务器来处理站点上爆炸式的增长呢?答案非常明确。站点性能的改善并非简单地依靠在出现问题时就频繁地更换硬件。问题的关键在于软件方面。
有关软件方面www.microsoft.com 是一个庞大的站点。这需要可靠的软件运行在可靠的硬件之上稳定地发挥性能。但同时,还需要合理规划、独创性和创造性的战略。 下面将向您介绍我们是如何充分利用软件的。破解谜题所有服务器运行于 Windows 2000 Advanced Server。IIS 5.0 服务于 Web 内容。另外,每分钟接到 150 次请求的搜索服务器还要运行 Microsoft Site Server 3.0。用来处理超过 500 种应用程序,并且在商业高峰时间需要同时支持 400 个连接的数据库服务器,还需运行 SQL Server。 “协同工作的软件就象一道谜题中环环相扣的各个部分,”站点数据库系统经理 Robb Mitchell 说,“它们相互配合得如此之好,以致于构成了一个天衣无缝的环境。” 软件能够工作得如此之好的原因之一在于所有的应用程序均可交互式地与其他软件协同工作。“为 Windows NT 或 Windows 2000、IIS 和 SQL 量体裁衣地设计 Web 页面并非难事,”Mitchell 说,“这几乎是很自然的。” 例如,如果正在使用 Windows NT 或 Windows 2000 和一个数据库,SQL 可帮助您将其完全同 Web 页面工具和向导相集成。配置不同程序使其协同工作易如反掌。 假设正在使用 Microsoft FrontPage(一种 Web 页面生成工具)并想插入一个数据调用。FrontPage 具有帮助设置页面同 SQL 以及现有数据库进行交互的向导。不错,您拥有一个为 Web 站点工作的数据库。 另一个例子是“开放式数据库连通性 (ODBC)”,即在应用程序间传递数据的虚拟通道。ODBC 基于 Windows NT,可自动配置同其他应用程序协同工作的状态。“不必担心进行安装,也完全不必应付对 ODBC 的配置。”Mitchell 说。“所有部分都能非常好地协同工作——只要说一句‘生成 SQL 调用’,平台就会自动生成。”Mitchell 解释道。 Windows、SQL 以及 IIS 所带来的乐趣从许多方面来讲,Mitchell 都可以称得上是处在 www.microsoft.com 的最前沿。他的工作组负责维持世界范围内的 170 个数据库在联机状态下的正常运转,而其中目前处于联机状态下的内容占用了 8 万亿字节的空间。每台 SQL 服务器大约需要每秒进行 200 次处理。 Mitchell 需要借助软件;几乎没有出错的机会。“我们有大约 100 台 Web 服务器每天 24 小时用于 SQL 联机服务,”他说,“它们必须保持每周七天,每天 24 小时连续运转。” 处理这方面的任务,IIS 服务器,事实上的 Web 服务器,使用所谓的“连接统筹”以保持稳定性及效率。 IIS 服务器由每台 SQL 服务器获得连接,并保持其有效性——这不失为一种占用服务器资源较少的解决方案。称作“连接-断开”的连接在处理结束后断开连接,增加了服务器的工作。Mitchell 说。 IIS 的优点之一是极易安装,Weeks 说。使用其基本配置即可完成从极小的操作到构建世界最大 Web 站点的任何事情。“不必做过多的调整,”他补充说,“即可适应各种用户的要求。” IIS 同时允许管理者运行“过程外”应用程序。这意味着如果设计了有时会导致违规错误和失败的对象,IIS 可隔离该程序使其继续运行而不会影响站点的其他部分。 由于拥有超过 40,000 页的 ASP 页面,www.microsoft.com 有许多 SQL 内容——事实上是,两亿行联机内容。“这意味着站点可以变得更大、更好、更具动态效果。但是,有时技术组件如果当初开发得很糟糕将会更难于维护。”研发部经理 Todd Wanke 说道。 保持 www.microsoft.com 性能稳定的最大障碍,也是站点管理员在研究事件日志和性能监视器时最耗费精力的因素——那就是单个页面上的内容。 “在访问率很高的站点上,如果含有编码糟糕的页面将会带来负面效应,”Weeks 说,“这将影响站点的可用性,同时也是我们在发布内容之前一定要对其进行测试的重要性所在了。” 例如,如果某个 ASP 页面每秒要接受二十个访问请求,但其只能响应其中的十个,即使使用最快的硬件,额外的请求也将被遏制。 “低水准的内容将会影响整个站点的性能,这样即便是将服务器的数量增加一倍,也无济于事,”Weeks 说,“所有关于内容的需求必须尽可能地立即执行,不容任何拖延。” 那么管理者应当如何处理问题并进行维护呢? “SQL 的优点之一就在于,”Mitchell 说,“管理者在联机状态下即可完成所有必要的维护工作。不必影响服务器的正常工作,管理者即可管理数据堆栈以及一致性检查。即在联机状态下,可检查数据库是否存在错误,如果有也可立即修复。” MSData,一个用来处理整个公司联机注册业务的、容量高达 70 GB 的庞大数据库,便是一个合适的例子。任何来到 www.microsoft.com 的已接受 cookie 来访者,在去任何其他地方之前总会首先点击 MSData。“SQL Server 使我们能够在数据库仍处于联机状态下时对其进行更新。”Mitchell 说。 为了适应不同的需求,也可自定义 SQL。应用程序预先设定了数据类型,例如文本、日期和时间,但也可创建自己特定的配置文件。例如,使用 SQL,可自定义默认值和规则。假设需要创建带有地址域的窗体。如果用户在邮政编码域中输入了 98052,可自定义数据库分别在城市和州域中自动添入雷得蒙得,华盛顿。 但为何要使用 SQL 呢? 只使用保险的 HTML 和 ASP 又有何不可呢? “有些内容仅对数据库引擎才有效,”Mitchell 说,“可以在 HTML 或 ASP 中放置许多数据,但这样做会使页面的体积过于庞大,以致于无论从时间或是空间上考虑都是不允许的。SQL 具有极为出色的响应时间——我是说那将是毫秒级的。” Mitchell 进一步说:“使用 Windows 2000 和 SQL 建立一个 Web 站点是行之有效的方法。我们已经证明了这是非常稳定并且堪当重任的。我们每天处理一亿九千万次点击并且还在不断增加,这个事实是很显著的。”
创造性的战略这样以来,已经是软硬件皆备了。如何使之充分被利用呢? 我们已经发现这一平台有极大的可扩展性和灵活性,以致于可以量体裁衣地根据需要对其进行调整。以下是我们用于 www.microsoft.com 站点上的一些创造性的战略。 单一 IP 地址解决方案服务器错误往往给用户带来极坏的经历,而 Web 站点的管理员们也会对此愁眉不展。www.microsoft.com 的工程师们尽其所能地避免用户在访问www.microsoft.com 站点时遇到这样的问题。 一项独到的战略是“单一 IP 地址解决方案”。最初,Internet 上每台服务器都拥有其唯一的 IP 地址。在“以往”,当用户希望访问 www.microsoft.com 时,他们的浏览器将使用一个唯一的属于 www.microsoft.com 上某一台服务器的 IP 地址。如果该服务器恰巧被关闭了,那就太糟糕了。即使 www.microsoft.com 所有其他的服务器都运行良好,那位用户也将被告之以令人望而怯步的服务器错误信息。 单一 IP 地址解决方案则完全改变了这种情况。每个正在使用中的 IP 地址均可代表整个站点。当有请求时,将被送往一台处于联机状态的服务器。处于关闭状态的服务器将被忽略。该过程需要一个叫做 HTTPmon 的 Microsoft 应用程序参与,其作用是检测服务器的有效性及状态,Wanke 解释道。 结果: 达到对外部用户的百分之百的可用性。 将内容分类www.microsoft.com 还采用了一种叫做“按簇分类 (Clustering)”的策略来维护站点上内容的可用性。按照所在的不同机器,从物理角度将站点上大量的内容分类,或按服务器的名称从逻辑的角度将站点上的内容分类。以此方式,站点工程师们将确保用户可以访问到他们所需的内容。 按照这种方式,如果一台服务器无法使用,同一组别中其他的服务器将承担起它的任务。并且这将避免造成站点上其他内容一起失效。 “如果任何服务器无法工作,将不会影响 Internet 上的服务,用户也不会察觉。”Weeks 说。 IIS 的好处之一是不同服务器上的运行时间(在一台服务器“超时”之前的一段时间其客户可被其他服务器接管)可进行不同的调整。分类利用这一点,区别对待使用静态 HTML 页面的站点(只需较短的超时值)和使用大量“应用程序强化”页面的站点(需要较长的运行时间可能拥有较大的超时值)。 “静态环境可被置于前方,”站点的总经理 Tim Sinclair 说,“地址 www.microsoft.com 是 Web 站点的介绍——来到站点的人总是首先见到该页。” “对于像注册、支持、事件以及奖金之类任务要求苛刻的服务,均属于应用程序强化类型,可按各自的服务器进行分类。因为这些站点对于用户很重要(同时应用程序也属于数据库强化类型),所以我们将此类应用程序分布在独立组别的服务器上以更加准确地构建性能模型。”Sinclair 说。 站点工程师还按产品的类别将一些 www.microsoft.com 站点上的内容进行了分类。Windows、Office 和 BackOffice 都保存在 www.microsoft.com 下各自的子域中。“随着 Microsoft 产品不断地发展,我们的 Web 站点从许多方面来看正在变成这些产品在支持和功能增强方面的重要补充。”Sinclair 说。他间接提到了在未来的产品中,用户只需点击按钮即可接受产品的支持以及进行增强功能的下载。 未来的发展方向是什么呢?站点工程师们计划指定一组专门针对不同层次用户的服务器。例如,MSDN 和 SiteBuilder——针对开发者的兴趣——将按各自的组别分类。 进行试运行www.microsoft.com 提供了一个很好的模拟测试 Microsoft Internet 产品的机会。可以这样认为,如果我们的产品在世界第三大 Web 站点毫不费力地经受住了严峻的流量及技术要求的考验,它们多半可以工作在任何地方。难道还有比将产品置于世界上最繁忙的信息高速路上进行试运行更好的改进产品的办法吗? 新产品在 microsoft.com 实验室环境下按照标准测试周期进行彻底的测试。然后再移至 Intranet 上,最终再转移至 www.microsoft.com 中任务要求不十分苛刻的服务器上。 但是由于再现站点上极其繁重的流量几乎是不可能的,即便是站点中不很关键的服务器上的内容也将面临其在真实环境下最严峻的考验。 测试并监视站点的工作情况www.microsoft.com 的管理员们仔细地对站点进行测试,验证其是否能够以最高的效率传递信息。测试的内容分为三大功能性职责:ASP 测试和代码检查、当前下部结构测试以及新技术测试和实施。 通过 ASP 测试及代码检查可以看到所有运行在站点上的代码。在其处于联机状态之前,www.microsoft.com 开发组使用 Visual Basic 编制了一个叫做“PubWiz”的内部工具用来检查链接引用的情况和编程的类型。在有 350 多人向 www.microsoft.com 发布内容的分散式发布环境下,PubWiz 是一个举足轻重的质量保证工具。 当前下部结构测试主要侧重于改进当前的 www.microsoft.com 站点性能。测试人员工作在一个实验室中的站点模拟环境中。该环境使用几台客户机来模拟服务器负载。用来进行模拟的工具叫做 WebCat,由 Windows NT 4.0 Resource Kit 提供。 我们以两种方式监视 www.microsoft.com 的性能。 从全世界不同的地点监视站点同 Internet 的连接,有助于确定 Internet 上的瓶颈。同时对数据中心中不同组别的服务器进行监视。我们的兴趣之一是查看通过服务器连入 Internet 的网络是否处于过载状态。 www.microsoft.com 的未来目前所走的这条路在我们面前还要延伸多远?Internet 将如何继续对我们的硬件、软件以及创造性带来新的挑战? 在虚拟化的进程中没有速度极限。在站点的飞速发展中保持其正常运转,至少我们可以从中体会到不断增长的乐趣。 但是我们已经得到了许多帮助。 幸运地及时发布了新产品,这使我们能够有条不紊地进行调整和增长。 并且通过倾听我们已经学到了很多。我们认真对待用户的反馈意见,尽可能及时地向着新方向做出反应。 我们衷心希望本文能够使您获得有关更好地管理 Web 站点的新的启发,不管是大企业站点还是个人站点。通过展示站点的结构,我们希望我们已经向您证明了 Microsoft Internet 产品的良好可扩展性以及出色的性能。 转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1238707.html

白盒测试 讨论

白盒测试,也称为结构化测试、基于代码的测试,是一种测试用例设计方法,它从程序的控制结构导出测试用例。用白盒测试产生的测试用例能够:

  1)保证一个模块中的所有独立路径至少被使用一次;
  2)对所有逻辑值均需测试true和false;
  3)在上下边界及可操作范围内运行所有循环;
  4)检查内部数据结构以确保其有效性。
  “我们应该更注重于保证程序需求的实现,为什么要花费时间和精力来担心(和测试)逻辑细节?”答案在于软件自身的缺陷:
  
·逻辑错误和不正确假设与一条程序路径被运行的可能性成反比。当我们设计和实现主流之外的功能、条件或控制时,错误往往开始出现在我们工作中。日常处理往往被很好地了解,而“特殊情况”的处理则难于发现。

  
·我们经常相信某逻辑路径不可能被执行,而事实上,它可能在正常的基础上被执行。程序的逻辑流有时是违反直觉的,这意味着我们关于控制流和数据流的一些无意识的假设可能导致设计错误,只有路径测试才能发现这些错误。

 
 ·笔误是随机的。当一个程序被翻译为程序设计语言源代码时,有可能产生某些笔误,很多将被语法检查机制发现,但是,其他的会在测试开始时才会被发现。笔误出现在主流上和不明显的逻辑路径上的机率是一样的。


  正如Beizer所说的:“错误潜伏在角落里,聚集在边界上”,而白盒测试更可能发现它

国内很少公司花很大的精力去做白盒测试,一般在单元测试过程中,白盒测试全是由开发人员来完成,商业软件所使用到的技术主要是黑盒测试技术,这是其特点所决定的。还有少量的白盒技术,但在实际中很少有公司愿意投入人力来作。

白盒测试的方法:
1.确定软件中的模块(数据计算、校验模块、功能模块)
2.在每一个模块中用常用的覆盖率覆盖方法计算所有满足的路径(覆盖率方法有很多,看软件要求程度,比如航空、医疗软件要求严格,使用Do-178B的MC/DC覆盖率标准)
3.设计测试用例,满足覆盖要求(注:想满足所有路径都覆盖是不可能的,花费也随之上升,没有公司愿意这么做,不现实)。

在经费和时间不足的情况下,应采取对关键点的白盒测试,就是针对重要环节的测试,然后用黑盒测试做补充,目前国内大多数公司采用:先对软件进行黑盒测试,然后查看覆盖率再对未覆盖的代码进行白盒测试,这样做可以节省时间和花费,当然缺点也有,毕竟黑盒测试不能代替白盒测试,即使在正确的输入下得到正确的输出也未必是所设想的路径。


来看一个实际案例:有两个产品形态接近的项目,A项目正式实施单元测试与集成测试,另一个项目B项目没正式做白盒测试(简单的拿调试当测试)。最后项目结束时对研发全过程的全部问题进行缺陷分析。


A项目的缺陷类型分析
B项目的缺陷类型分析


 A项目的所有问题中,应该发现问题的阶段是白盒测试(单元测试与集成测试)的占50%,而B项目所有问题中,应在白盒测试阶段发现的仅占33%,另外,A项目发现逻辑错误占13%,而B项目只占8%。由这个统计数据表明,不做白盒测试必然导致大量问题漏测。

白盒测试要做到什么程度才算合适


既然白盒测试不可或缺,那么,白盒测试应做到什么程度才算合适呢?具体来说,白盒测试与黑盒测试应维持什么样的比例才算合适?

一般而言,白盒测试做多做少与产品形态有关,如果产品更多的具备软件平台特性,白盒测试应占总测试的80%以上,甚至接近100%,而如果产品具备复杂的业务操作,有大量GUI界面,黑盒测试的份量应该更重些。根据经验,对于大多数嵌入式产品,白盒方式展开测试(包括代码走读)应占总测试投入的一半以上,白盒测试发现的问题数也应超过总问题数的一半。

由于产品的形态不一样,很难定一个标准说某产品必须做百分之多少白盒测试,但依据历史经验,我们还可以进行定量分析的。比如,收集某产品的某历史版本在开发与维护中发生的所有问题,对这些问题进行正交缺陷分析(Orthogonal Defect Classification,ODC),把“问题根源对象”属于概要设计、详细设计与编码的问题整理出来,这些都是属于白盒测试应发现的问题,统计这些问题占总问题数的比例,大致就是白盒测试应投入的比例。

通过正交缺陷分析,还能推论历史版本各阶段测试的遗留缺陷率,根据“发现问题的活动”,能统计出与“问题根源对象”不相匹配的问题数,这些各阶段不匹配问题的比例就是该阶段的漏测率。
转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1238737.html

白盒测试心得

白盒测试心得六步骤:

第一步,编写测试案例:程序出口检查案例+路径覆盖选择测试案例+编码规范检查(为防止测试完成后再编写案例文档时会发生案例遗漏补写情况,提议应先给出测试案例的规范计划)

第二步,针对程序出口做数据测试:程序中一般情况下不免存在数据输入出口,包括如return等的返回值,此时可先对其输入出口数据进行正确性测试。如果程序是无直接数据出口的程序,那么我们也可以在程序中恰当变量位置,适当加入一些如print()的输出语句来检查程序执行中数据的准确性。(其实这一步严格地说应属于程序的黑盒测试)

第三步,绘制程序流程,以方便接下来对程序做路径或条件覆盖等测试,流程图应正确清晰给出。流程要是都给错了,那么可能导致你接下来做的东西都是错误的, 所以在这一步时应认真对待,最好可以与编写该程序的开发人员进行沟通,让他确认你给出的流程是否正确(因为现在大多数开发人员在编码前未能事先给出相应的流程图,编码后可能也很少会给出,此时就只能靠测试人员与开发人员的沟通确认)。并对其功能的完整性进行检查。

第四步,依照流程图,选择合适的测试方法,并进行测试:白盒测试方法有很多种类,语句覆盖->判定覆盖->条件覆盖测试->判定/条件覆盖->条件组合覆盖->路径覆盖测试等,其检错能力左到右是由弱到强的,但是不能盲目选择检错性较强的测试方法,因根据程序需要而定,有些程序路径千千万万,这时,就不要硬钻脑门子啦,可以适当选择条件覆盖测试方法。

第五步,检查程序规范性,包括命名是否符合规范等。

第六步,补充测试案例文档:将测试结果补充进先前案例文档中。

黑盒测试 简单讨论

黑盒测试注重于测试软件的功能性需求,也即黑盒测试使软件工程师派生出执行程序所有功能需求的输入条件。黑盒测试并不是白盒测试的替代品,而是用于辅助白盒测试发现其他类型的错误。黑盒测试试图发现以下类型的错误:

  1)功能错误或遗漏;
  2)界面错误;
  3)数据结构或外部数据库访问错误;
  4)性能错误;
  5)初始化和终止错误。
  
白盒测试在测试的早期采用,而黑盒测试主要用于测试的后期。黑盒测试故意不考虑控制结构,而是注意信息域。黑盒测试用于回答以下问题:

  ·如何测试功能的有效性?
  ·何种类型的输入会产生好的测试用例?
  ·系统是否对特定的输入值尤其敏感?
  ·如何分隔数据类的边界?
  ·系统能够承受何种数据率和数据量?
  ·特定类型的数据组合会对系统产生何种影响?
  
运用黑盒测试方法,可以导出满足以下标准的测试用例集:

  1)所设计的测试用例能够减少达到合理测试所需的附加测试用例数;
  2)所设计的测试用例能够告知某些类型错误的存在或不存在,而不是仅仅与特定测试相关的错误
1、架构和算法的可行性测试分析:主要包括性能、并发等方面
2、CodeReview:很简单,但很多人没有这样做;其实这比“黑盒测试”更容易发现边界问题、流程正确性问题等。值得推广!
3、测试驱动和单元测试,很多人为了达到那些并不太懂编程管理层的要求,往往是少了这些看似不容易出成绩的步骤。(很大程度上改变设计思维) 知道可能出错的地方,当然容易避免出错。
4、UML和用例:特别是UI建模,个人觉得最不可少;UML和用例至少要能覆盖核心内容。 具体如何做,要根据每个人、每个单位甚至每个项目做具体优化,这里提供思路。
5、用心架构(各功能模块的关系、分布式计算的节点关系、类的层次结构、升级的备案、数据库架构、数据库表的范式、并发访问和多线程等):不好的架构,Code起来吃力、问题又多又不好解决!以个人的经验认为,一般可分为六个阶段
A 用户需求收集、访谈和确认-->B 架构和可行性分析并文档化--> C 开发测试部署和维护详细计划--> D1 单元编码、测试<-->D2 部署测试--> E1 开发环境集成和测试-->E2 真实环境模拟测试-->E3 部署预演-->(原系统备份)-->F 部署

1) 各阶段主要的“产出”(不考虑投入)
A:需求说明书、UI建模、用例和UML图和系统使用帮助文件初稿、系统可能存在的风险报告
B:系统架构图(逻辑图和物理图)、核心算法代码和测试报告、第二方和第三方组件的风险分析报告
C:系统各模块的详细说明书,数据库架构和测试,各功能的开发、CodeRivew和测试计划、部署计划(相当于每日构建)、集成测试计划、系统的升级考虑和方案。注意,尽可能在Code前将数据库可能的问题解决掉!
D:代码、BUG管理、可执行文件、开发环境、测试环境
E:可发布的安装包、最终用户文档、修改之前相关的文档到和现在的系统一致
F:可能的问题预案、安装和不断依实际情况优化,进入维护阶段

2)时间分配:A:B:C:D:E:F=10:10:20:50:5:5,A阶段的时间这里指项目启动时算起,启动前的时间不计在内。

转自:junzhongxu.cnblogs.com

强大的Web开源测试工具—Selenium[4]

Test suite和Test case文件:需要编写的由一个表格组成的html文件;
引擎库js文件:位于selenium根目录下的核心文件,其中html-xpath目录下的那个文件,也是必须的库文件;

user-extensions.js:用来扩展selenium的文件;用户自己编写的函数和扩展的命令都应该放在这个文件中;

这四类文件中,除了引擎库以外,其他三类文件都是可以根据具体情况去修改的。selenium 部署完毕后,可以打开浏览器来通过url来访 问TestRunner.html文件。初始的时候,TestRunner.html文件中的 TestSuite是链接到tests目录下的TestSuite.html文件,TestCase的frame(上部中间)中打开了 TestSuite.html文件中的第一个Test Case “TestOpen.html”。

可以直接修改TestSuite.html文件,让其指向自己开发的Test case html文件。我们也可以建立另外一个目录,然后将自己的TestSuite文件和Test case文件都保存在这个目录中。如果使用后一种方式,那么在打开TestRunner.html的时候需要传递一个参数,例子如下:
http://localhost/selenium/TestRunner.html?test=/testDir/myTestSuite.html

下面就是开发测试用例——即编辑测试用例的表格。无论Test Suite还是Test Case,表格的第一行都是描述性文字,selenium的引擎是不会处理这一行的内容的。实际内容都是从第二行开始的。Test case的表格列数一定不能少于3列,否则Selenium会出错。而基本的三列组成是:
|command| Target| value| 

清单 2就是四个测试用例的例子,将执行以下操作:

通过进入 /change_address_form.html 打开变更地址页面。

在 ID 为 address_field 的文本框中输入 Betelgeuse state prison。

单击名为 Submit 的输入区。注意,这里使用 XPath 找到 Submit 按钮,这导致表单数据被发送到服务器。

验证页面是否包含文本 Address change successful。

清单 2. 在测试用例中使用命令和断言的例子

command

Target

Value


open


/change_address_form.html


type

address_field

Betelgeuse state prison

clickAndWait

//input[@name='Submit']


verifyTextPresent

Address change successful<

测试套件要达到对应用程序的完全测试覆盖,通常需要不止一个测试用例。这就是 Selenium 使用测试套件的原因。测试套件用于将具有类似功能的一些测试用例编成一组,以便让它们按顺序运行。

测试套件和测试用例一样,都是用简单的 HTML 表编写的。Selenium 执行的缺省测试套件的名称是 TestSuite.html。测试套件使用一个只包含一列的表,表中的每一行指向一个包含某个测试用例的文件。

对于一个有着多个功能模块、组件的web应用,编写的测试脚本html必然比较多。因此,应该建立一个合理的目录结构来组织这些脚本,一般按web服务、模块、功能来组织,形成层次性结构。

转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1239077.html

强大的Web开源测试工具—Selenium[3]

第一列包含命令 或断言。

第二列包含命令或断言的目标(target)。可以用多种受支持的组件定位符中的一种来指定目标。通常使用的是组件的 ID 或名称,但 XPath 和 DOM 定位符也是受支持的。

第三列包含用于为命令或断言指定参数的值。例如,当使用 type 命令时,这一列可能就是一个文本域所期望的值。
Test runner 脚本通常与所测试的应用程序(AUT)部署在同一个服务器上。这是因为 browser bot 使用 JavaScript 来模拟用户操作。这些脚本在一个受限制的沙箱环境中运行。如果需要绕过这些限制,可以使用一个代理。

driven 脚本开发模式driven Selenium 脚本是用多种受支持的编程语言(Java, .NET, Perl, Python 和 Ruby)中的一种编写的。这些脚本在浏览器之外的一个单独的进程中运行。驱动程序的任务是执行测试脚本,并通过与运行在浏览器中的 browser bot 进行通信来驱动浏览器。驱动程序与 browser bot 之间的通信使用一种简单的特定于 Selenium 的连接语言 Selenese。

driven 脚本比 test runner 脚本更强大、更灵活,可以将它们与 xUnit 框架集成。driven 脚本编写和部署更复杂些,它必须执行以下任务:

启动服务器。

部署所测试的应用程序(AUT)。

部署测试脚本。

启动浏览器。

发送命令到 browser bot。

验证 browser bot 执行的命令的结果。

driven 脚本更依赖于应用程序运行时环境。例如,Java 驱动程序使用一个嵌入式 Jetty 或 Tomcat 实例来部署所测试的应用程序,如将 Selenium 集成到 Ruby on Rails 中。

开发测试用例测试用例开发涉及四类文件

主文件: TestRunner.html/TestRunner.hta(.hta文件是html application,windows平台特有);


Test suite和Test case文件:需要编写的由一个表格组成的html文件;
引擎库js文件:位于selenium根目录下的核心文件,其中html-xpath目录下的那个文件,也是必须的库文件;

user-extensions.js:用来扩展selenium的文件;用户自己编写的函数和扩展的命令都应该放在这个文件中;

这四类文件中,除了引擎库以外,其他三类文件都是可以根据具体情况去修改的。selenium 部署完毕后,可以打开浏览器来通过url来访 问TestRunner.html文件。初始的时候,TestRunner.html文件中的 TestSuite是链接到tests目录下的TestSuite.html文件,TestCase的frame(上部中间)中打开了 TestSuite.html文件中的第一个Test Case “TestOpen.html”。

可以直接修改TestSuite.html文件,让其指向自己开发的Test case html文件。我们也可以建立另外一个目录,然后将自己的TestSuite文件和Test case文件都保存在这个目录中。如果使用后一种方式,那么在打开TestRunner.html的时候需要传递一个参数,例子如下:
http://localhost/selenium/TestRunner.html?test=/testDir/myTestSuite.html

下面就是开发测试用例——即编辑测试用例的表格。无论Test Suite还是Test Case,表格的第一行都是描述性文字,selenium的引擎是不会处理这一行的内容的。实际内容都是从第二行开始的。Test case的表格列数一定不能少于3列,否则Selenium会出错。而基本的三列组成是:
|command| Target| value| 

清单 2就是四个测试用例的例子,将执行以下操作:

通过进入 /change_address_form.html 打开变更地址页面。

在 ID 为 address_field 的文本框中输入 Betelgeuse state prison。

单击名为 Submit 的输入区。注意,这里使用 XPath 找到 Submit 按钮,这导致表单数据被发送到服务器。

验证页面是否包含文本 Address change successful。

清单 2. 在测试用例中使用命令和断言的例子

command

Target

Value

转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1239072.html

强大的Web开源测试工具—Selenium[2]

支持的平台


Windows:

Internet Explorer 6.0

Firefox 0.8 to 1.5, Mozilla Suite 1.6+, 1.7+

Seamonkey 1.0, Opera 8

Mac OS X:

Safari 1.3+

Firefox 0.8 to 1.5, Mozilla Suite 1.6+, 1.7+

Seamonkey 1.0, Camino 1.0a1

Linux:

Firefox 0.8 to 1.5, Mozilla Suite 1.6+, 1.7+

Konqueror

部署Selenium

selenium目录下的内容:

devtests:试验性功能 dom-images: 查看DOM用图片

dom-styles: 查看DOM用样式表

html-xpath: Xpath库

jsmock: javascript mock library
jsunit: javascript unit test library

tests: samples(以这个为基础开发测试用例)

核心js文件和html文件

如果想要测试自己开发的发布在服务器端的页面,需要把selenium配置在同一个服务器下:

Apache :直接将selenium目录拷贝至htdocs(Apache的确省根目录)目录下,然后启动Apache,用地址http://server:8080/selenium/TestRunner.html访问例子。

Tomcat :直接将selenium目录拷贝至webapps目录下,启动Tomcat,用地址http://server:8080/selenium/TestRunner.html访问例子。:

IIS:建立一个虚拟目录selenium,将该虚拟目录直接指向实际的selenium目录,用地址http://server/selenium/TestRunner.html访问例子Selenium test runner 脚本,就是测试用例(test case),是用 HTML 语言通过一个简单的表布局编写的,即使对于非技术人员来说,test runner 脚本也易于阅读和编写。如 清单 1 所示。

清单 1. Selenium 测试用例的结构 (HTML格式)

Command1/Assertion1

Target1

Value1

Command2 Assertion1

Target2

Value2


test runner 脚本使用与 xUnit 框架相同的测试套件(test suite)和测试用例概念。测试用例和命令按照它们在测试套件和测试用例中出现的顺序依次执行。在 清单 1 中:

转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1239070.html

强大的Web开源测试工具—Selenium[1]

介绍
Selenium 是 ThoughtWorks 专门为 Web 应用而开发的自动化测试工具,适合进行功能测试、验收测试,其最大的优势有几点:

可直接运行在浏览器之上,所见即所得,就像真实用户所做的一样。Selenium 的核心,也称 browser bot,是用 JavaScript 编写的。这使得测试脚本可以在受支持的浏览器中运行。browser bot 负责执行从测试脚本接收到的命令支持多操作系统(Windows, Mac OS和Linux)和各种浏览器Internet Explorer、Mozilla 和 Firefox,更容易发现浏览器的不兼容性支持两种开发脚本的模式test runner (HTML文件)和 driven(脚本语言编写),其语言包括Java, .NET, Perl, Python 和 Ruby. 使用 driven 脚本,测试有一部分在浏览器之外运行,而如果使用 test runner 脚本的话,测试是完全在浏览器中运行的。

但是Selenium是轻量的测试框架, 脚本所处理的测试用例构成简单,其实质就是通过HTTP协议,发送请求(request)来完成测试用例,所以很困难处理业务逻辑关系强的测试用例。

Selenium 命令

Selenium 命令分成两类 —— 操作(action) 和断言(assertion):

操作模拟用户与 Web 应用程序的交互。例如,单击一个按钮和填写一个表单,这些都是常见的用户操作,可以用 Selenium 命令来自动化这些操作。

断言验证一个命令的预期结果。常见的断言包括验证页面内容或当前位置是否正确。

在 Selenium 网站上可以找到可用命令的完整列表。通过 Selenium 命令,脚本编写者可以描述 browser bot 在浏览器中所执行的操作

组成

Selenium IDE:一个firefox的plug-in,可以录制和回放并保存一些test cases, 可以生成一些简单的基于rc 模式的简单code. (相当于 Jmeter的gui模式和jmeter脚本的生成-badboy)

Selenium Core. 整个测试机制的核心部分,即有assertion(断言) 机制的test suite runner。它由一些纯js代码组成, 可以运行在 windows/linux的不同browser上 (相当于Jmeter 的runner 跟 Assertion)

Selenium Remote Control:一个代理与控制端, 可代替Selenium core/ Selenium IDE的client端(相当于通过编程来实现一切),是支持 多语言的. (相当于Jmeter的client/server模式,但Selenium Remote Control更强一些)

转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1239068.html

2008年12月16日星期二

XMLHTTPRequest的属性和方法简介

由于现在在公司负责制作标准的静态页面,为了增强客户体验,所以经常要做些AJAX效果,也学你也和我一样在,学习AJAX。而设计AJAX时使用的一个重要的技术(工具)就是XMLHTTPRequest对象了。这里海啸把我学习XMLHTTPRequest对象的一点资料拿出来跟大家一起分享。文中的资料都是海啸在学习时在网上收集的,如果您开过,那就再加深下印象吧!(如果您觉得侵犯了您的版权,请联系海啸。(haixiao_yao[at]yahoo.com.cn))

1、XMLHTTPRequest对象什么是?

最通用的定义为:XmlHttp是一套可以在Javascript、VbScript、Jscript等脚本语言中通过http协议传送或从接收XML及其他数据的一套API。XmlHttp最大的用处是可以更新网页的部分内容而不需要刷新整个页面。(这个功能正是AJAX的一大特点之一:))

来自MSDN的解释:XmlHttp提供客户端同http服务器通讯的协议。客户端可以通过XmlHttp对象(MSXML2.XMLHTTP.3.0)向http服务器发送请求并使用微软XML文档对象模型Microsoft? XML Document Object Model (DOM)处理回应。

这里说些题外话,其实这个东西很早就出现了,只是以前浏览器的支持不够,只有IE中才支持,所以大多数的WEB程序员都没有怎么用他,但是现在情况发生了很大地改变,Mozilla和Safari把它采用为事实上的标准,主流的浏览器都开始支持XMLHTTPRequest对象了。但是这里需要重点说明的是XMLHTTPRequest目前还不是一个W3C的标准,所以在不同的浏览器上表现也稍有些区别。

2、创建XMLHTTPRequest对象

对了,说到区别,我们这里来看看怎么来声明(使用)它,在使用XMLHTTPRequest对象发送请求和处理响应之前,我们必须要用javascript创建一个XMLHTTPRequest对象。(IE把XMLHTTPRequest实现为一个ActiveX对象,其他的浏览器[如Firefox/Safari/Opear]则把它实现为一个本地的javascript对象)。下面我们就来看看具体怎么运用javascript来创建它吧:

< script language="javascript" type="text/javascript">
< !--
var xmlhttp;
// 创建XMLHTTPRequest对象
function createXMLHTTPRequest(){
if(window.ActiveXObject){ // 判断是否支持ActiveX控件
xmlhttp = new ActiveObject("Microsoft.XMLHTTP"); // 通过实例化ActiveXObject的一个新实例来创建XMLHTTPRequest对象
}
else if(window.XMLHTTPRequest){ // 判断是否把XMLHTTPRequest实现为一个本地javascript对象
xmlhttp = new XMLHTTPRequest(); // 创建XMLHTTPRequest的一个实例(本地javascript对象)
}
}
//-->
< /script>

3、属性和方法

由于东西太多现在先用个页面来列举出说有的方法和属性,以后再来详细举例(主要是本人也在学习中)。


< html>
< head>
< title>XMLHTTPRequest对象的说明DEMO< /title>
< script language="javascript" type="text/javascript">
< !--
var xmlhttp;
// 创建一个XMLHTTPRequest对象
function createXMLHTTPRequext(){
if(window.ActiveXObject) {
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
}
else if(window.XMLHTTPRequest){
xmlhttp = new XMLHTTPRequest();
}
}
function PostOrder(xmldoc)
{
createXMLHTTPRequext();

// 方法:open
// 创建一个新的http请求,并指定此请求的方法、URL以及验证信息
// 语法:oXMLHttpRequest.open(bstrMethod, bstrUrl, varAsync, bstrUser, bstrPassword);
// 参数
// bstrMethod
// http方法,例如:POST、GET、PUT及PROPFIND。大小写不敏感。
// bstrUrl
// 请求的URL地址,可以为绝对地址也可以为相对地址。
// varAsync[可选]
// 布尔型,指定此请求是否为异步方式,默认为true。如果为真,当状态改变时会调用onreadystatechange属性指定的回调函数。
// bstrUser[可选]
// 如果服务器需要验证,此处指定用户名,如果未指定,当服务器需要验证时,会弹出验证窗口。
// bstrPassword[可选]
// 验证信息中的密码部分,如果用户名为空,则此值将被忽略。

// 备注:调用此方法后,可以调用send方法向服务器发送数据。
xmlhttp.Open("get", "http://localhost/example.htm", false);
// var book = xmlhttp.responseXML.selectSingleNode("//book[@id='bk101']");
// alert(book.xml);


// 属性:onreadystatechange
// onreadystatechange:指定当readyState属性改变时的事件处理句柄
// 语法:oXMLHttpRequest.onreadystatechange = funcMyHandler;
// 如下的例子演示当XMLHTTPRequest对象的readyState属性改变时调用HandleStateChange函数,
// 当数据接收完毕后(readystate == 4)此页面上的一个按钮将被激活
// 备注:此属性只写,为W3C文档对象模型的扩展.
xmlhttp.onreadystatechange= HandleStateChange;

// 方法:send
// 发送请求到http服务器并接收回应
// 语法:oXMLHttpRequest.send(varBody);
// 参数:varBody (欲通过此请求发送的数据。)
// 备注:此方法的同步或异步方式取决于open方法中的bAsync参数,如果bAsync == False,此方法将会等待请求完成或者超时时才会返回,如果bAsync == True,此方法将立即返回。
// This method takes one optional parameter, which is the requestBody to use. The acceptable VARIANT input types are BSTR, SAFEARRAY of UI1 (unsigned bytes), IDispatch to an XML Document Object Model (DOM) object, and IStream *. You can use only chunked encoding (for sending) when sending IStream * input types. The component automatically sets the Content-Length header for all but IStream * input types.
// 如果发送的数据为BSTR,则回应被编码为utf-8, 必须在适当位置设置一个包含charset的文档类型头。
// If the input type is a SAFEARRAY of UI1, the response is sent as is without additional encoding. The caller must set a Content-Type header with the appropriate content type.
// 如果发送的数据为XML DOM object,则回应将被编码为在xml文档中声明的编码,如果在xml文档中没有声明编码,则使用默认的UTF-8。
// If the input type is an IStream *, the response is sent as is without additional encoding. The caller must set a Content-Type header with the appropriate content type.
xmlhttp.Send(xmldoc);

// 方法:getAllResponseHeaders
// 获取响应的所有http头
// 语法:strValue = oXMLHttpRequest.getAllResponseHeaders();
// 备注:每个http头名称和值用冒号分割,并以\r\n结束。当send方法完成后才可调用该方法。
alert(xmlhttp.getAllResponseHeaders());
// 方法:getResponseHeader
// 从响应信息中获取指定的http头
// 语法:strValue = oXMLHttpRequest.getResponseHeader(bstrHeader);
// 备注:当send方法成功后才可调用该方法。如果服务器返回的文档类型为"text/xml", 则这句话
// xmlhttp.getResponseHeader("Content-Type");将返回字符串"text/xml"。可以使用getAllResponseHeaders方法获取完整的http头信息。
alert(xmlhttp.getResponseHeader("Content-Type")); // 输出http头中的Content-Type列:当前web服务器的版本及名称。


document.frmTest.myButton.disabled = true;
// 方法:abort
// 取消当前请求
// 语法:oXMLHttpRequest.abort();
// 备注:调用此方法后,当前请求返回UNINITIALIZED 状态。
// xmlhttp.abort();

// 方法:setRequestHeader
// 单独指定请求的某个http头
// 语法:oXMLHttpRequest.setRequestHeader(bstrHeader, bstrValue);
// 参数:bstrHeader(字符串,头名称。)
// bstrValue(字符串,值。)
// 备注:如果已经存在已此名称命名的http头,则覆盖之。此方法必须在open方法后调用。
// xmlhttp.setRequestHeader(bstrHeader, bstrValue);
}
function HandleStateChange()
{
// 属性:readyState
// 返回XMLHTTP请求的当前状态
// 语法:lValue = oXMLHttpRequest.readyState;
// 备注:变量,此属性只读,状态用长度为4的整型表示.定义如下:
// 0 (未初始化) 对象已建立,但是尚未初始化(尚未调用open方法)
// 1 (初始化) 对象已建立,尚未调用send方法
// 2 (发送数据) send方法已调用,但是当前的状态及http头未知
// 3 (数据传送中) 已接收部分数据,因为响应及http头不全,这时通过responseBody和responseText获取部分数据会出现错误,
// 4 (完成) 数据接收完毕,此时可以通过通过responseBody和responseText获取完整的回应数据
if (xmlhttp.readyState == 4){
document.frmTest.myButton.disabled = false;

// 属性:responseBody
// 返回某一格式的服务器响应数据
// 语法:strValue = oXMLHttpRequest.responseBody;
// 备注:变量,此属性只读,以unsigned array格式表示直接从服务器返回的未经解码的二进制数据。
alert(xmlhttp.responseBody);

// 属性:responseStream
// 以Ado Stream对象的形式返回响应信息
// 语法:strValue = oXMLHttpRequest.responseStream;
// 备注:变量,此属性只读,以Ado Stream对象的形式返回响应信息。
alert(xmlhttp.responseStream);

// 属性:responseText
// 将响应信息作为字符串返回
// 语法:strValue = oXMLHttpRequest.responseText;
// 备注:变量,此属性只读,将响应信息作为字符串返回。XMLHTTP尝试将响应信息解码为Unicode字符串,
// XMLHTTP默认将响应数据的编码定为UTF-8,如果服务器返回的数据带BOM(byte-order mark),XMLHTTP可
// 以解码任何UCS-2 (big or little endian)或者UCS-4 数据。注意,如果服务器返回的是xml文档,此属
// 性并不处理xml文档中的编码声明。你需要使用responseXML来处理。
alert(xmlhttp.responseText);

// 属性:responseXML
// 将响应信息格式化为Xml Document对象并返回
// 语法:var objDispatch = oXMLHttpRequest.responseXML;
// 备注:变量,此属性只读,将响应信息格式化为Xml Document对象并返回。如果响应数据不是有效的XML文档,
// 此属性本身不返回XMLDOMParseError,可以通过处理过的DOMDocument对象获取错误信息。
alert("Result = " + xmlhttp.responseXML.xml);

// 属性:status
// 返回当前请求的http状态码
// 语法:lValue = oXMLHttpRequest.status;
// 返回值:长整形标准http状态码,定义如下:
// Number:Description
// 100:Continue
// 101:Switching protocols
// 200:OK
// 201:Created
// 202:Accepted
// 203:Non-Authoritative Information
// 204:No Content
// 205:Reset Content
// 206:Partial Content
// 300:Multiple Choices
// 301:Moved Permanently
// 302:Found
// 303:See Other
// 304:Not Modified
// 305:Use Proxy
// 307:Temporary Redirect
// 400:Bad Request
// 401:Unauthorized
// 402:Payment Required
// 403:Forbidden
// 404:Not Found
// 405:Method Not Allowed
// 406:Not Acceptable
// 407:Proxy Authentication Required
// 408:Request Timeout
// 409:Conflict
// 410:Gone
// 411:Length Required
// 412:Precondition Failed
// 413:Request Entity Too Large
// 414:Request-URI Too Long
// 415:Unsupported Media Type
// 416:Requested Range Not Suitable
// 417:Expectation Failed
// 500:Internal Server Error
// 501:Not Implemented
// 502:Bad Gateway
// 503:Service Unavailable
// 504:Gateway Timeout
// 505:HTTP Version Not Supported
// 备注:长整形,此属性只读,返回当前请求的http状态码,此属性仅当数据发送并接收完毕后才可获取。
alert(xmlhttp.status);

// 属性:statusText
// 返回当前请求的响应行状态
// 语法:strValue = oXMLHttpRequest.statusText;
// 备注:字符串,此属性只读,以BSTR返回当前请求的响应行状态,此属性仅当数据发送并接收完毕后才可获取。
alert(xmlhttp.statusText);
}
}
//-->
< /script>
< /head>
< body>
< form name="frmTest">
< input name="myButton" type="button" value="Click Me" >
< /form>
< /body>
< /html>

怎样设计帮助最有效?

从小的方面讲,帮助一般是指:手册、说明书、文档、FAQ 等等。从大的方面讲,可以是交互过程中的提示、指引、演示等信息,帮助无处不在!这一切,可以一并称为“帮助系统”。

无论怎样,帮助系统的目的,是解决使用者遇到的问题、帮助使用者更好、更快的完成任务。

不同产品的帮助系统有很大区别,我觉得讨论下比如 PHP 语言的帮助系统、电视机的帮助系统、数码相机的帮助系统可能会更有意思。但如果大家都是搞网络的,那么谈谈 Web 2.0 网站的帮助系统设计,或许更容易引起共鸣。

如果要我来为“帮助系统”分类,那么我会分为以下三种:

文档型帮助:

详细,大而全。一般是 FAQ 形式,可以由情景式帮助链接过来,或者从页面的其他地方链接到某一个问题的解答上。实际上就是一本帮助手册,噢,让我想起了 RTFM(Read The Fucking Manual)。文档性帮助必须有比较好的组织方式和搜索。

可能大家会直接把文档型帮助给枪毙了。但是,别忘了我们还有专家用户!

情景式帮助:

即时,针对性强。情景式帮助最常见的例子就是:“忘记密码?”。还有比如一些应用具有帮助开关,打开的时候,就会出现气泡提示,告诉你这里是什么那里是什么。哦,还有,就是 Office 那个恶心的戴高帽的老头和长着眼睛的回形针。

这么一说,可能很多人会觉得情景式帮助是最有效的,因为最直接,并且符合在需要的时候出现这样的设计原则。但是情景式帮助最忌讳宾主倒置,过多的情景式帮助会影响用户的使用。

演示型帮助:

生动,易学。主要用在富应用的新手教学上,比如游戏里面的 Tutorial,比如安装一个软件的向导,安装完了之后的 Samples 等等。对于网站,演示型帮助可以放在首页上,我印象中,有一个在线的 Mindmap 应用(MindMeister),首页看上去是一张再普通不过的脑图,但点击后会发现,那居然可以在线操作!

从商业角度讲,演示型帮助可以引导和培养用户习惯。

除了以上三种,还有那些帮助系统呢?导航是帮助吗?一本书的目录,算不算帮助?快捷键,算不算帮助?

帮助的帮助:
如果你来设计一款 GPS 导航,它本身就是一个帮助系统,那它的帮助是什么?

载体:
文字,声音,图像,视频,Flash,三维虚拟…想象一下,你在使用一个三维浏览器,给你介绍你打算买的一套房子,并且有美女全程导购解释……

帮助的描述:
需要学会用用户的思维去考虑问题……图形往往是具备良好的解释性……

还有更多的话题可以讨论,真是意犹未尽。我觉得大家可以交流下各自遇见的最酷的帮助,说不定什么时候可以借鉴一下呢。

转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1238591.html

提高CSS代码的可读性

当完成一项前端的工作之后,许多人都会忘记该项目的结构与细节。然而代码并不是马上就能完全定型,在余下的时间里还有不断的维护工作,而这些工作也许不会是你自己完成。所以,结构优良的代码能很大程度上优化它的可维护性。下面列出五种提高CSS文件可维护性的方法,也就是一种较好的CSS样式指南。

1.分解你的样式

对于小项目,在写代码之前,按页面结构或页面内容将代码分为几块并给予注释。例如,可以分别将 全局样式、布局、字体样式、表单、评论和其他分为几个不同的块来继续工作。

而对于较大的工程,这样显然不会有什么效果。此时,就需要将样式分解到几个不同的样式表文件。下面的master stylesheet 就是这一方法的例子,它的工作主要是导入其他样式文件。使用这一方法不仅能优化样式结构,而且有利于减少一些不必要的服务器请求。而分解文件的方法就有许多种,master stylesheet 使用了最常见的一种。

/*------------------------------------------------------------------

[Master Stylesheet]
Project: Smashing Magazine
Version: 1.1
Last change: 05/02/08 [fixed Float bug, vf]
Assigned to: Vitaly Friedman (vf), Sven Lennartz (sl)
Primary use: Magazine
-------------------------------------------------------------------*/
@import "reset.css";
@import "layout.css";
@import "colors.css";
@import "typography.css";
@import "flash.css";
/* @import "debugging.css"; */

同时对于大型项目,你也可以加上CSS文件的升级标志或者一些诊断措施,这里不再详述。

2.建立CSS文件索引

为了能够迅速的了解整个CSS文件的结构,在文件开头建立文件索引是一个不错的选择。一种可行的方法是建立树形的索引:结构上的id 和 class 都可以成为该树的一个分支。如下:

/*------------------------------------------------------------------
[Layout]
* body
+ Header / #header
+ Content / #content
- Left column / #leftcolumn
- Right column / #rightcolumn
- Sidebar / #sidebar
- RSS / #rss
- Search / #search
- Boxes / .box
- Sideblog / #sideblog
+ Footer / #footer
Navigation #navbar
Advertisements .ads
Content header h2
——————————————————————-*/

或者也可以这样:

/*------------------------------------------------------------------

[Table of contents]
1. Body
2. Header / #header
2.1. Navigation / #navbar
3. Content / #content
3.1. Left column / #leftcolumn
3.2. Right column / #rightcolumn
3.3. Sidebar / #sidebar
3.3.1. RSS / #rss
3.3.2. Search / #search
3.3.3. Boxes / .box
3.3.4. Sideblog / #sideblog
3.3.5. Advertisements / .ads
4. Footer / #footer

-------------------------------------------------------------------*/

另一种方式可以只是先简单的将内容列举出来,也不需要缩进。下面的一个例子中,如果你需要跳至RSS部分你只需要简单的搜索 8.RSS。

/*------------------------------------------------------------------

[Table of contents]
1. Body
2. Header / #header
3. Navigation / #navbar
4. Content / #content
5. Left column / #leftcolumn
6. Right column / #rightcolumn
7. Sidebar / #sidebar
8. RSS / #rss
9. Search / #search
10. Boxes / .box
11. Sideblog / #sideblog
12. Advertisements / .ads
13. Footer / #footer

-------------------------------------------------------------------*/



/*------------------------------------------------------------------
[8. RSS / #rss]
*/
#rss { ... }
#rss img { ... }

定义这样一个样式检索可以很有效的使其他人阅读学习你的代码变得容易。在制作大项目的时候,你也可以将检索打印出来从而在你阅读代码的时候方便查阅。

3.定义你的颜色和版式

CSS 中我们无法使用常量,但是在编写颜色和版式方面的代码是我们会经常遇到可以使用很多次的类,在这里可以将之视为CSS 的常量。


一种可以减小CSS无常量定义确定的方法是在CSS文件顶部的注释中下一些定义,也就是定义常量。一种最简单的应用就是创建一个颜色表。这样你就可以快速的了解整个页面的色彩,从而避免一些反复修改过程中的错误。如果你需要对颜色进行修改,你也可以很快找到它。

/*------------------------------------------------------------------
# [Color codes]

# Dark grey (text): #333333
# Dark Blue (headings, links) #000066
# Mid Blue (header) #333399
# Light blue (top navigation) #CCCCFF
# Mid grey: #666666
# */

或者,你也可以选择描述你布局当中使用的颜色。对于一个给定的颜色,你可以将用到该颜色的块罗列出来。当然,你也可以选择按页面元素来罗列颜色。

/*------------------------------------------------------------------

[Color codes]

Background: #ffffff (white)
Content: #1e1e1e (light black)
Header h1: #9caa3b (green)
Header h2: #ee4117 (red)
Footer: #b5cede (dark black)

a (standard): #0040b6 (dark blue)
a (visited): #5999de (light blue)
a (active): #cc0000 (pink)

-------------------------------------------------------------------*/

对于版式有同样的例子。

/*------------------------------------------------------------------
[Typography]

Body copy: 1.2em/1.6em Verdana, Helvetica, Arial, Geneva, sans-serif;
Headers: 2.7em/1.3em Helvetica, Arial, "Lucida Sans Unicode", Verdana, sans-serif;
Input, textarea: 1.1em Helvetica, Verdana, Geneva, Arial, sans-serif;
Sidebar heading: 1.5em Helvetica, Trebuchet MS, Arial, sans-serif;

Notes: decreasing heading by 0.4em with every subsequent heading level
-------------------------------------------------------------------*/

4.格式化CSS属性

当我们编写代码的时候,使用一些特殊的编码风

格会对提高CSS代码的可读性有很大帮助。许多人都有各自不同的编码风格。一部分人习惯于将颜色和字体的代码放在前面,另外一部分则更喜欢将类似浮动和定位的更“重要”的属性放在前面。类似的,也可以将页面元素按照它在布局中的结构进行排序:



body,
h1, h2, h3,
p, ul, li,
form {
border: 0;
margin: 0;
padding: 0;
}

一些开发者用一种更为有意思的方法:他们将属性按首字母的顺序排列。值得注意的是,这样一种方法可能对某些浏览器会产生问题。

不管自己的格式如何,你要确保你已经清晰的定义了这些格式方法。这样,你的同事在阅读你的代码的时候将会感谢你的努力。

5.缩进会是你的朋友!

为了让你的代码给人感觉更为直观,你可以使用一行来定义大纲元素的样式。当指定的选择器里的属性超过三个的时候,这种方式将带来混乱。但是,适度的使用这种方式,你可以很清楚的区分相同类的不同点。

#main-column { display: inline; float: left; width: 30em; }
#main-column h1 { font-family: Georgia, "Times New Roman", Times, serif; margin-bottom: 20px; }
#main-column p { color: #333; }

同时,样式修改的维护也是个比较麻烦的问题。很多人修改样式之后就忘记了,结果后来又发现修改的样式导致了页面出错,不得不苦苦寻找。因此,为修改的样式构建一个特殊的格式就很必要了。一种很简单的方式是,给修改过的样式缩进,同时,也可以使用一些注释(比如"@new")来做一个标识。

#sidebar ul li a {
display: block;
background-color: #ccc;
border-bottom: 1px solid #999; /* @new */
margin: 3px 0 3px 0;
padding: 3px; /* @new */
}

总的来说,只有建立一个合适的样式指南才会对样式表的可读性有所帮助。记住,移去每一个对你理解文件没有帮助的样式指南,避免对过多的元素使用过多的样式指南。然后,为了一个可读性可维护性良好的CSS文件而努力吧。

转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1238588.html

Javascript的匿名函数

一、什么是匿名函数?

在Javascript定义一个函数一般有如下三种方式:

函数关键字(function)语句:
function fnMethodName(x){alert(x);}
函数字面量(Function Literals):
var fnMethodName = function(x){alert(x);}
Function()构造函数:
var fnMethodName = new Function('x','alert(x);')
上面三种方法定义了同一个方法函数fnMethodName,第1种就是最常用的方法,后两种都是把一个函数复制给变量fnMethodName,而这个函数是没有名字的,即匿名函数。实际上,相当多的语言都有匿名函数。

二、函数字面量和Function()构造函数的区别

虽然函数字面量是一个匿名函数,但语法允许为其指定任意一个函数名,当写递归函数时可以调用它自己,使用Function()构造函数则不行。
var f = function fact(x) { if (x < = 1) return 1; else return x*fact(x-1); };
Function()构造函数允许运行时Javascript代码动态的创建和编译。在这个方式上它类似全局函数eval()。
Function()构造函数每次执行时都解析函数主体,并创建一个新的函数对象。所以当在一个循环或者频繁执行的函数中调用Function()构造函数的效率是非常低的。相反,函数字面量却不是每次遇到都重新编译的。
用Function()构造函数创建一个函数时并不遵循典型的作用域,它一直把它当作是顶级函数来执行。
var y = "global"; function constructFunction() { var y = "local"; return new Function("return y"); // 无法获取局部变量 } alert(constructFunction()()); // 输出 “global”
和函数关键字定义相比Function()构造器有自己的特点且要难以使用的多,所以这项技术通常很少使用。而函数字面量表达式和函数关键字定义非常接近。考虑前面的区别,虽然有消息说字面量的匿名函数在OS X 10.4.3下的某些webkit的引擎下有bug,但我们平常所说的匿名函数均指采用函数字面量形式的匿名函数。

三、匿名函数的代码模式

昨天 hedger wang 在他的blog介绍了 几种匿名函数的代码模式:

错误模式:其无法工作,浏览器会报语法错。

function(){ alert(1); }();

函数字面量:首先声明一个函数对象,然后执行它。
(function(){ alert(1); } ) ( );
优先表达式:由于Javascript执行表达式是从圆括号里面到外面,所以可以用圆括号强制执行声明的函数。
( function(){ alert(2); } ( ) );
Void操作符:用void操作符去执行一个没有用圆括号包围的一个单独操作数。
void function(){ alert(3); }()
这三种方式是等同的,hedger wang因为个人原因比较喜欢第3种,而在实际应用中我看到的和使用的都是第1种。

四、匿名函数的应用

《Javascript的一种模块模式》中的第一句话就是“全局变量是魔鬼”。配合var关键字,匿名函数可以有效的保证在页面上写入Javascript,而不会造成全局变量的污染。这在给一个不是很熟悉的页面增加Javascript时非常有效,也很优美。实际上,YUI以及其相应的范例中大量使用匿名函数,其他的Javascript库中也不乏大量使用。
Javascript的函数式编程(functional programming)的基石。具体请看《用函数式编程技术编写优美的 JavaScript》和《函数式JavaScript编程指南》。
转自:http://www.cnblogs.com/junzhongxu/archive/2008/07/09/1238586.html

2008年12月10日星期三

需要掌握的八个CSS布局技巧

1.若有疑问立即检测

在出错时若能对原始代码做简单检测可以省去很多头痛问题。W3C对于XHTML与CSS 都有检测工具可用,请见 http://validator.w3.org/ 。请注意,在文件开头的错误,可能因为不当的结构等因素造成更多错误;我们建议先修正一些最明显的错误之后重新检测,这样也许会让错误数量爆减。

2.使用浮动功能时记得适当清除指令

浮动是个危险的功能,未必会产生您所期望的结果。如果您遇到浮动元素延伸到外围容器的边框或者其他不正常情况,请先确定您的做法是正确的。请参阅Eric Meyer 在Complex Spiral Consulting Web 网站上的教学。

3.边界重合时利用padding或border来避免

您可能会为了一点不应该出现的空间而焦头烂额,或者您需要一点点空间时,怎样都挤不出来。如果您有用到margin,那么很容易产生边界的重合;Andy Budd在他的网站上解释了可能的做法。

4.尝试避免同时对元素指定padding/border以及高度或宽度

Windows版IE经常导致width与height的计算问题。有些方法可以解决此问题,但如果母元素需要指定高度与宽度时,最好能够在母元素之内的子元素套用margin,或者当子元素需要指定高度与宽度时,在母元素套用padding以达效果。

5.不要依赖min-width/min-height

Windows版IE并不支援两种语法。但是在某种程度下,windows版IE可以达到相当于min-width/min-height的效果,所以只要对IE做点过滤功能,即可达到您想要的结果。

6.若有疑问,先减少百分比

有时候某些错误会使50%+50%成为100.1%,使网页出现问题。这时请尝试将这些值改为49%,甚至49.9%。

7.记住“TRouBLed”写法

Border,margin与padding的简写语法有特定顺序,从上方开始顺时针方向转动:top,right,bottom,left. 所以margin:0 1px 3px 5px;的结果是上方无边界,右边1像素,以此类推。记住“TRouBLe”,您就不会弄错次序了。

8.只要不是零的值,都要指定单位

CSS需要您对每个font,Margin等各种值指定单位。(唯一的例外是line-height)

用javascript 转换外部链接样式

用css属性选择器可以有选择性地对链接样式进行控制,如让所有的外部链接都加一个小图标来标识其是一外部链接。

但用css有弊端:
1、只支持FireFox等对web标准支持很好的浏览器。
2、只能判断链接,不能判断锚点或javascript。如遇到< a href="javascript:void(0);">就无能为力了。

这里可以结合js来完成,首先写一个样式:
a.other:link,a.other:visited,a.other:active
{
background:url("external.gif") no-repeat top right;
padding-right:15px;
}

再写一个js,但js要考虑到链接的多样性,如上面提到的javascript、锚点等。 如果是图片链接,就不要应用样式了。

< script type="text/javascript">
window.onload = function()
{
var aList = document.getElementsByTagName('a');
var iCount = aList.length;
for(var i = 0;i < iCount; i++)
{

if(!chkMyLink(aList [i] .href,aList [i].innerHTML))
{
aList [i].className ='other';
}
}
}

//s是链接的url,innerhtml是链接文本
function chkMyLink(s,innerhtml)
{
if(innerhtml. replace( /^ \ s*/, "") .match(/^\< img/gi)) return true;
var reg = /^http\:\/\//gi;
if(s.match(reg))
{
reg = / ^ http \: \ /\/www.lemongtree.com/gi;
if(s.match(reg))
{
return true;
}
else
{
return false;
}
}
return true;
}
< /script>
现在可以看到效果了。

ActionScript 3 日积月累之三

摒弃了attachMovie 之后的AS3,采用了类似DOM 的操作方式。addChild、removeChild 、getChildAt 等方法开始成为AS3中显示(在屏幕上渲染)、操作图形的主要方法。由于AS1、AS2完全是依赖于attchMovie 的思想,因此对于传统Flash开发人员来说,转变到新的addChild 的确需要下一番功夫。

由于新的“DisplayObject”在内存的使用上非常“敏感”。往往由于不良的编程习惯会造成不必要的内存泄漏,因此,我们不得不比AS1、 AS2时代更加深入到内存管理了。我想,每一个Flash开发人员,包括我自己,都应该花一番功夫仔细体会“内存管理”这几个字的含义。毕竟我们学习 AS3是为了开发比AS1、2时代更加先进、高效而且内存占用小的应用程序,如果还是开发一些简单的应用,也就失去我们每一个人使用AS3的意义了。

我觉得,由于GC是Flash 应用程序内存管理的核心,我应该先从AS3中的Garbage Collector(简称GC)开始说起。AS3的GC功能比AS1、2中的要强大的多。然而,强大的同时,也带来了一定程度的复杂性。但是也不至于非常复杂,我觉得比C++等传统语言要容易掌握得多。

在研究内存如何回收之前,先说一下变量的创建:

在Flash中,我们每建立一个非原生变量时(Boolean, String, Number, uint, int 这些是原生变量),这个变量名只是一个reference(指向,有时候也成为“引用”)而已,而并非这个变量本身。例如:
var a:int = 5; //a就是5
var b:int = a; //b是a,也就是5
a = 4;
trace(b); // 是5,而不是4!
//改变b的值,a不发生变化。反之亦然
var c:Object = {name:”aw”, blog:”www.awflasher.com/blog”}; //c只是指向一个内部的Object,为了描述方便,称其为“O”
var d:Object = c; //d指向c,指向了同一个Object“O”
c.name = “bw”
trace(d.name); //不是aw,而是bw了
//改变c也好,改变d也罢,其实是改变了那个“O”,因此改变c的时候,d的值也就变了。因此d.name已经被改变为了“bw”。

首先,明确一点,GC会按照一定的时间周期进行内存清理(memory sweep)。因此不要因为delete掉一个object后检查System.totalMemory 内存就没有反应而怀疑GC是否正常。那么GC为什么要按照一定的时间周期进行清理呢。这还要得从GC的具体工作原理说起。

GC的两个回收体系:
1、“Reference Counting” - 引用计数器
这个体系是自从AS1时代就有的体系,它的工作原理非常简单:系统计算每一个对象被指向,或者说引用的次数。比如
var a:Object = {name:”aw”, blog:”www.awflasher.com/blog”};

这时候,这个Object(我们仍然称为“O”),有一次引用,它的引用计数器为1(来自a)。我们再建立一个对象b,并指向到a:
var b:Object = a;

这时候“O”引用计数器变为了2(来自a、b)。
我们删除一个,比如先删除b:
delete b;
这时候引用计数器为(2-1=1)1,GC不操作
再删除另外一个a:
delete a;“O”引用计数器变为(1-1=0)0,GC出面干掉这个对象。
这套体系很轻便,CPU压力较小。但是它也有缺陷。当我们的对象内部互相引用的时候,麻烦就来了。例如:
var a:Object = {name:”aw”, description:”unknown”};

// 建立一个对象a,仍然假设内部对象为“O”,这时O的引用次数为1
var b:Object = {nameObj:a, url:”awflasher.com”};
// b引用了a,同时创建了新的内部对象“P”。这时O的引用次数为2,P为1
a.myDescription = b;
// a的myDescription属性指向到了b。这样,P的引用次数也为2了。
// 是不是有点头晕?静下来,画个图慢慢看看:)
delete a;
delete b;
两次delete操作后,O、P的引用次数都是1,它们将继续占用你的内存。“Reference Counting” 体系无能为力了。

2、“Mark Sweeping” - 标记清除法则

GC的第一种机制“Reference Counting”,在FlashPlayer8之前是GC唯一的机制。FlashPlayer6和7由于引入了复杂的OOP开发模式,尤其是7引入了类似Java、C++等强大OOP语言的语法。利用Flash设计的复杂项目越来越多。由于Flash开发人员大多不了解GC,而Java、C++的开发人员又已经习惯了强大的GC(无论是自动的还是手动的)。因此FlashPlayer6、7的内存问题开始浮现出来。

Okay,Flash Player8引入了新的“Mark Sweeping”机制。我想这也是当年Macromedia(Adobe)基于退出Player8的原因吧!(还记得当年Flash8的介绍视频么,效率提高是一个革命性的改进)

下面就来讲述“Mark Sweeping” - 标记清除法则的工作原理。
FlashPlayer会从root开始,遍历系统的每一个变量,并对有指向的对象之间,记录一次联系。在遍历结束之后,凡是与root不相联系的对象,被FlashPlayer无情地干掉。

Okay,回到刚才的例子,当我们delete a,并delete b之后,root与O、P就划清了界线。这时候,GC就可以进行一次肃清了。然而,由于这种“Mark Sweeping”要遍历所有的对象,因此非常消耗资源。这也就回到了当初的问题:“GC为什么要按照一定的时间周期进行清理“ - 因为不能给CPU造成太大的负担。

我个人猜测,GC的内部清除策略应该是在某一次事件(例如delete)发生后,在CPU比较空闲、RAM分配相对合理的情况下执行的。

Okay,不要以为有了“Mark Sweeping”就万事大吉了。由于GC不会即时进行,因此你的对象会在一段时间内“阴魂不散”!对于一个追求完美的开发人员来说,这意味着它们内部的某些机制会在被删除之后继续工作:AS语句会继续执行、声音会继续播放、事件会继续触发!

KirupaForum有网友说,“All you got to do is pray the garbage collector doesn’t break down.”,确实,如果一个应用程序要运行上一个多小时,那么慢慢流逝的内存会让你的用户对你的产品失望(例如游戏)。因此我们需要有一个良好的资源管理策略。

ActionScript 3 日积月累之二

摒弃了attachMovie 之后的AS3,采用了类似DOM 的操作方式。addChild、removeChild 、getChildAt 等方法开始成为AS3中显示(在屏幕上渲染)、操作图形的主要方法。由于AS1、AS2完全是依赖于attchMovie 的思想,因此对于传统Flash开发人员来说,转变到新的addChild 的确需要下一番功夫。

由于新的“DisplayObject”在内存的使用上非常“敏感”。往往由于不良的编程习惯会造成不必要的内存泄漏,因此,我们不得不比AS1、 AS2时代更加深入到内存管理了。我想,每一个Flash开发人员,包括我自己,都应该花一番功夫仔细体会“内存管理”这几个字的含义。毕竟我们学习 AS3是为了开发比AS1、2时代更加先进、高效而且内存占用小的应用程序,如果还是开发一些简单的应用,也就失去我们每一个人使用AS3的意义了。

我觉得,由于GC是Flash 应用程序内存管理的核心,我应该先从AS3中的Garbage Collector(简称GC)开始说起。AS3的GC功能比AS1、2中的要强大的多。然而,强大的同时,也带来了一定程度的复杂性。但是也不至于非常复杂,我觉得比C++等传统语言要容易掌握得多。

在研究内存如何回收之前,先说一下变量的创建:

在Flash中,我们每建立一个非原生变量时(Boolean, String, Number, uint, int 这些是原生变量),这个变量名只是一个reference(指向,有时候也成为“引用”)而已,而并非这个变量本身。例如:
var a:int = 5; //a就是5
var b:int = a; //b是a,也就是5
a = 4;
trace(b); // 是5,而不是4!
//改变b的值,a不发生变化。反之亦然
var c:Object = {name:”aw”, blog:”www.awflasher.com/blog”}; //c只是指向一个内部的Object,为了描述方便,称其为“O”
var d:Object = c; //d指向c,指向了同一个Object“O”
c.name = “bw”
trace(d.name); //不是aw,而是bw了
//改变c也好,改变d也罢,其实是改变了那个“O”,因此改变c的时候,d的值也就变了。因此d.name已经被改变为了“bw”。

首先,明确一点,GC会按照一定的时间周期进行内存清理(memory sweep)。因此不要因为delete掉一个object后检查System.totalMemory 内存就没有反应而怀疑GC是否正常。那么GC为什么要按照一定的时间周期进行清理呢。这还要得从GC的具体工作原理说起。

GC的两个回收体系:
1、“Reference Counting” - 引用计数器
这个体系是自从AS1时代就有的体系,它的工作原理非常简单:系统计算每一个对象被指向,或者说引用的次数。比如
var a:Object = {name:”aw”, blog:”www.awflasher.com/blog”};

这时候,这个Object(我们仍然称为“O”),有一次引用,它的引用计数器为1(来自a)。我们再建立一个对象b,并指向到a:
var b:Object = a;

这时候“O”引用计数器变为了2(来自a、b)。
我们删除一个,比如先删除b:
delete b;
这时候引用计数器为(2-1=1)1,GC不操作
再删除另外一个a:
delete a;“O”引用计数器变为(1-1=0)0,GC出面干掉这个对象。
这套体系很轻便,CPU压力较小。但是它也有缺陷。当我们的对象内部互相引用的时候,麻烦就来了。例如:
var a:Object = {name:”aw”, description:”unknown”};

// 建立一个对象a,仍然假设内部对象为“O”,这时O的引用次数为1
var b:Object = {nameObj:a, url:”awflasher.com”};
// b引用了a,同时创建了新的内部对象“P”。这时O的引用次数为2,P为1
a.myDescription = b;
// a的myDescription属性指向到了b。这样,P的引用次数也为2了。
// 是不是有点头晕?静下来,画个图慢慢看看:)
delete a;
delete b;
两次delete操作后,O、P的引用次数都是1,它们将继续占用你的内存。“Reference Counting” 体系无能为力了。

2、“Mark Sweeping” - 标记清除法则

GC的第一种机制“Reference Counting”,在FlashPlayer8之前是GC唯一的机制。FlashPlayer6和7由于引入了复杂的OOP开发模式,尤其是7引入了类似Java、C++等强大OOP语言的语法。利用Flash设计的复杂项目越来越多。由于Flash开发人员大多不了解GC,而Java、C++的开发人员又已经习惯了强大的GC(无论是自动的还是手动的)。因此FlashPlayer6、7的内存问题开始浮现出来。

Okay,Flash Player8引入了新的“Mark Sweeping”机制。我想这也是当年Macromedia(Adobe)基于退出Player8的原因吧!(还记得当年Flash8的介绍视频么,效率提高是一个革命性的改进)

下面就来讲述“Mark Sweeping” - 标记清除法则的工作原理。
FlashPlayer会从root开始,遍历系统的每一个变量,并对有指向的对象之间,记录一次联系。在遍历结束之后,凡是与root不相联系的对象,被FlashPlayer无情地干掉。

Okay,回到刚才的例子,当我们delete a,并delete b之后,root与O、P就划清了界线。这时候,GC就可以进行一次肃清了。然而,由于这种“Mark Sweeping”要遍历所有的对象,因此非常消耗资源。这也就回到了当初的问题:“GC为什么要按照一定的时间周期进行清理“ - 因为不能给CPU造成太大的负担。

我个人猜测,GC的内部清除策略应该是在某一次事件(例如delete)发生后,在CPU比较空闲、RAM分配相对合理的情况下执行的。

Okay,不要以为有了“Mark Sweeping”就万事大吉了。由于GC不会即时进行,因此你的对象会在一段时间内“阴魂不散”!对于一个追求完美的开发人员来说,这意味着它们内部的某些机制会在被删除之后继续工作:AS语句会继续执行、声音会继续播放、事件会继续触发!

KirupaForum有网友说,“All you got to do is pray the garbage collector doesn’t break down.”,确实,如果一个应用程序要运行上一个多小时,那么慢慢流逝的内存会让你的用户对你的产品失望(例如游戏)。因此我们需要有一个良好的资源管理策略。

ActionScript 3 日积月累之一

本文是我(aw)在整理了相关文档和讨论之后,结合自己的亲自实验总结出来的一些经验和心得。我尽量描述详尽,避免模糊概念,当然也希望所有看官提出批评意见。为了表述方便,其中术语不限定语言,如我可能会一会儿用class,一会儿用类。

面向对象的难点部分就是理解变量作用域修饰符(modifier)其实也就是面向对象中我们已经熟悉的public、protected、private等等。本文还深入讨论了ActionScript3中新增的internal 等概念。下面我依次列出:

一、关于package以及internal
package,用“形而上学”的方式理解,就是物理目录下的类集合。在AS2中只需要保证文件系统的路径匹配,然后用类似“import com.awflasher.someUtils”的方法导入即可。而AS3则要求您在所有的类中声明package关键词。package的大括号对 “{}”内,我们只能定义一个类,我们可以在这个大括号外面定义一些辅助类,不过这些类只能被当前这个类(你在package大括号对内定义的类)访问。当然,一个package大括号对内只有一个类,这并不代表一个package内只有一个类。你可以在同一目录下定义多个属于该package(指代这个目录)的类。它的意义绝不是简单的“类文件集合容器”,而是一个让各种应该协同工作的类集中到一起的项目包。值得一提的是,所谓“协同工作”是指至少有一个class要引入其他一些class 来进行功能设计,而这时候采用internal修饰可以省去很多getters和setters。我自己回忆起在湖南卫视的项目中用AS2开发的 Vplayer,两个类AVCore和AVControl就有很多getter和setter,搞的特别麻烦。

internal类似public,但限定在一个 package内了。在同一个package 内的类可以访问同一个package 内其他类的internal 变量,而其他包内的类无法访问。 package 与类的继承性毫无关系,比如TextField 和Sprite、MovieClip 都继承自DisplayObject 类,但TextField属于 flash.text包,而MovieClip和Sprite 属于flahs.display 包。也就是说,包对类的限定是与继承链毫无关联的、一个新的 “维度”的限定。

附:使用一个类的时候,我们必须import这个类,或者包含这个类的package。AS2时直接写完整包路径的使用方法在AS3中不管用了,本文后面有详细介绍。

二、关于public
public定义的类或者属性可以在任何作用域内由任何来源访问。构造函数永远都是public的,Flex中的应用程序类(Application Class)和Flash CS3中的文档类(Document Class)必须是public的。且不能缺省public这个关键词声明。我在测试中发现,如果不声明public,Flash根本就不会获取类的定义,进而编译无法通过。

三、关于protected
protected声明类似AS2的private,它定义的属性只能在自己子类中可见,而其它场合都是不可见的。这一点与Java等传统OOP语言类似。

四、关于private
注意AS3的private和AS2的private大不相同,它定义的属性只属于自己,子类可以定义毫无牵连的同名属性。
dynamic 和原来AS2的dynamic一样,用dynamic声明的类可以动态的加入属性。这些属性也可以通过delete来删除。动态加入的属性一旦被切断所有的引用就会被垃圾回收机制自动回收。有时候用System.totalMemory检测不到内存释放是因为垃圾回收机制并不是即时运行的。

五、关于dynamic
动态(dynamic)类允许在运行时动态地添加属性,常见的动态类有MovieClip和顶级(top-level)的Array。如果您的自定义类要继承于动态类,那么请也定义为动态的,不要省略dynamic关键词。

六、关于继承(extends)和override
继承其实并不太复杂,唯一要说明的就是:子类的构造函数一定要用“super”调用一次父类的构造函数,否则报错!对于继承后的子类,如果要重新定义父类的非private方法,必须使用override关键词。在override的时候,如果我们需要调用父类的方法,可以使用super关键词(由于继承方法在逻辑上与父类往往有相似性,因此没有必要把方法逻辑完全重写)官方帮助中的这个例子非常易懂:
package {
import flash.display.MovieClip;
public class SuperExample extends MovieClip
{
public function SuperExample()
{
var myExt:Extender = new Extender()
trace(myExt.thanks()); // output: Mahalo nui loa
}
}
}

class Base {
public function thanks():String
{
return “Mahalo”;
}
}

class Extender extends Base
{
override public function thanks():String
{
return super.thanks() + ” nui loa”;
}
}

override不能用于重载变量(成员属性)。但是却可以用于重写getter 和setter 函数,例如:(官方帮助的例子)
package
{
import flash.display.MovieClip;
public class OverrideExample extends MovieClip
{
public function OverrideExample()
{
trace(currentLabel)
}
override public function get currentLabel():String
{
var str:String = “Override: “;
str += super.currentLabel;
return str;
}
}
}

这个例子中,我们直接重写了MovieClip类的currentLabel 属性。注意调用父类属性的时候,用了super.currentLabel。

关于静态方法,比较麻烦。首先,静态方法是无法被重载的。必须通过类来访问。但是您也可以自己定义与静态方法同名的方法,我把官方的例子做了一下修改就一目了然了:

package
{
import flash.display.MovieClip;
public class StaticExample extends MovieClip
{
public function StaticExample()
{
var myExt:Extender = new Extender();
}
}
}

class Base {
public static var test:String = “static”;
}

class Extender extends Base
{
private var test:String = “instance”;
public function Extender()
{
trace(Base.test); // output: static
trace(test); //added by awflasher.com, output: instance
}
}

七、关于import语法
在AS2时代,“import”语法只是为了让编程时代码简洁(省去了包名),比如我们import了mx.transitions.Tween之后,就可以直接new Tween()了。而如果不import,我们也可以直接用全类名来构造实例,例如:new mx.transitions.Tween();

然而,在AS3中,无论是否采用全名类声明,只要你用到这个类,就必须import。import 估计是传统Flash程序员所需要养成的一个最大的习惯,在刚切入AS3开发平台的时候,我常常忘记import一些常用的类,例如从IDE 指向过来的文本和渲染元件,以及flash.event.*、flash.display.StageAlign等比较泛用的类。

AS3中不像AS2那样,我们不能用_root和Stage[”displayState”]来进行hacks了。

八、关于编译时的注意事项
AS3不再像AS2那样简单地Compile-Time(编译时,即FlashCS3/FlexBuidler/其他编译器发布ActionScript 及所有资源为SWF文件的那一时刻)进行类型检测了,AS3在Run-Time(运行时,级Flashplayer 或者其他播放SWF的软件在播放SWF 的时候)也有类型检测,因此AS2的Hacks(强制访问不能访问的属性)将不再有效。

九、一个不得不说的好消息
AS3中类所有的变量(属性)和函数(方法)的作用域都是运行时跟类/实例走的。这与AS2大有不同,我们不再需要去Delegate了。只要是类自己的方法,在调用的过程中,this永远指向类自己(实例)。

简要总结:
1、如果我需要属性公开,并且可以被自己的子类继承,那么定义成public的。子类也可重写(override)之。
2、如果我需要属性隐藏,但是可以被自己的子类继承,那么定义成protected的。与public类似,子类也可重写(override)之。
3、如果我的某一个类有一个属性不想在任何位置可见,包括其子类,那么定义为private的。其子类无需重写(override),因为它根本就不存在于子类中。

2008年12月8日星期一

JavaScript开发时的五个小技巧

只在< form>元素上使用submit事件

如果要在form中绑定事件处理程序时,应该只在< form>元素上绑定submit事件,而不是给提交按钮绑定click事件。
March:这个方式固然很好,但是,公司开发时使用了Web Flow,一个页面就一个大form,而里面可能有若干个提交按钮,所以不得不把部分事件处理程序绑定在了提交按钮的click事件上。

可点击的都应该是链接

不要给除锚元素(< a>)以外的元素绑定click事件。这一点对于键盘用户很重要,因为他们在仅通过键盘获取元素焦点时会遇到困难。

March:不过个人感觉锚元素还是应该只用作链接,而一些功能性的操作(比如Google Reader的Mark all as new),最好还是用< span>来标注,accessibility的问题可以通过快捷键等方式解决。这样做可以更好的还原HTML元素的语义。

简单的for循环优化

在你写一个for循环时,有个很简单的技巧能够提高性能。

for ( var i = 0; i < elements.length; ++i )

使用下面的语句代替上面的:

for ( var i = 0, j = elements.length; i < j; ++i )

这样可以把元素的个数(elements.length的值)储存在一个变量j中,这样就不必在每次循环时都计算一遍元素的个数。

用匿名函数来作为事件处理程序

尤其是对于短小的函数,创建一个匿名函数会比使用一个命名函数的引用更具可读性。

anchor.onclick = function() { map.goToPosition( home ); return false; }

March:在较复杂的JavaScript开发时还是使用命名函数效率更高。

使用Array.join代替字符串连接(concatenating strings)

在将很多字符串、变量等连接成一个很长的字符串时,将所有字符串和变量放入一个数组,然后用join方法将他们组成一个长字符串,这样无论从代码可读性还是从性能上都更胜于字符串连接。

var text = 'There are' + elements.length + 'members in the elements array.';
var text = ['There are', elements.length, 'members in the elements array.'].join(' ');

Javascript的匿名函数

一、什么是匿名函数?

在Javascript定义一个函数一般有如下三种方式:

函数关键字(function)语句:
function fnMethodName(x){alert(x);}
函数字面量(Function Literals):
var fnMethodName = function(x){alert(x);}
Function()构造函数:
var fnMethodName = new Function('x','alert(x);')
上面三种方法定义了同一个方法函数fnMethodName,第1种就是最常用的方法,后两种都是把一个函数复制给变量fnMethodName,而这个函数是没有名字的,即匿名函数。实际上,相当多的语言都有匿名函数。

二、函数字面量和Function()构造函数的区别

虽然函数字面量是一个匿名函数,但语法允许为其指定任意一个函数名,当写递归函数时可以调用它自己,使用Function()构造函数则不行。
var f = function fact(x) { if (x < = 1) return 1; else return x*fact(x-1); };
Function()构造函数允许运行时Javascript代码动态的创建和编译。在这个方式上它类似全局函数eval()。
Function()构造函数每次执行时都解析函数主体,并创建一个新的函数对象。所以当在一个循环或者频繁执行的函数中调用Function()构造函数的效率是非常低的。相反,函数字面量却不是每次遇到都重新编译的。
用Function()构造函数创建一个函数时并不遵循典型的作用域,它一直把它当作是顶级函数来执行。
var y = "global"; function constructFunction() { var y = "local"; return new Function("return y"); // 无法获取局部变量 } alert(constructFunction()()); // 输出 “global”
和函数关键字定义相比Function()构造器有自己的特点且要难以使用的多,所以这项技术通常很少使用。而函数字面量表达式和函数关键字定义非常接近。考虑前面的区别,虽然有消息说字面量的匿名函数在OS X 10.4.3下的某些webkit的引擎下有bug,但我们平常所说的匿名函数均指采用函数字面量形式的匿名函数。

三、匿名函数的代码模式

昨天 hedger wang 在他的blog介绍了 几种匿名函数的代码模式:

错误模式:其无法工作,浏览器会报语法错。

function(){ alert(1); }();

函数字面量:首先声明一个函数对象,然后执行它。
(function(){ alert(1); } ) ( );
优先表达式:由于Javascript执行表达式是从圆括号里面到外面,所以可以用圆括号强制执行声明的函数。
( function(){ alert(2); } ( ) );
Void操作符:用void操作符去执行一个没有用圆括号包围的一个单独操作数。
void function(){ alert(3); }()
这三种方式是等同的,hedger wang因为个人原因比较喜欢第3种,而在实际应用中我看到的和使用的都是第1种。

关于Javascript的内存泄漏问题

常规循环引用内存泄漏和Closure内存泄漏

要了解javascript的内存泄漏问题,首先要了解的就是javascript的GC原理。

我记得原来在犀牛书《JavaScript: The Definitive Guide》中看到过,IE使用的GC算法是计数器,因此只碰到循环 引用就会造成memory leakage。后来一直觉得和观察到的现象很不一致,直到看到Eric的文章,才明白犀牛书的说法没有说得很明确,估计该书成文后IE升级过算法吧。在IE 6中,对于javascript object内部,jscript使用的是mark-and-sweep算法,而对于javascript object与外部object(包括native object和vbscript object等等)的引用时,IE 6使用的才是计数器的算法。

Eric Lippert在http://blogs.msdn.com/ericlippert/archive/2003/09/17/53038.aspx一文中提到IE 6中JScript的GC算法使用的是nongeneration mark-and-sweep。对于javascript对算法的实现缺陷,文章如是说:

"The benefits of this approach are numerous, but the principle benefit is that circular references are not leaked unless the circular reference involves an object not owned by JScript. "

也就是说,IE 6对于纯粹的Script Objects间的Circular References是可以正确处理的,可惜它处理不了的是JScript与Native Object(例如Dom、ActiveX Object)之间的Circular References。

所以,当我们出现Native对象(例如Dom、ActiveX Object)与Javascript对象间的循环引用时,内存泄露的问题就出现了。当然,这个bug在IE 7中已经被修复了[http://www.quirksmode.org/blog/archives/2006/04/ie_7_and_javasc.html]。

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp 中有个示意图和简单的例子体现了这个问题:

< html>
< head>
< script language = " JScript ">
var myGlobalObject;
function SetupLeak() // 产生循环引用,因此会造成内存泄露
{
// First set up the script scope to element reference
myGlobalObject = document.getElementById("LeakedDiv");
// Next set up the element to script scope reference
document.getElementById(" LeakedDiv ").expandoProperty = myGlobalObject;
}
function BreakLeak() // 解开循环引用,解决内存泄露问题
{
document.getElementById( " LeakedDiv " ).expandoProperty = null ;
}
< /script>
< /head>
< body onload = "SetupLeak()" onunload = "BreakLeak()">
< div id = "LeakedDiv" >< /div>
< /body>
< /html>

上面这个例子,看似很简单就能够解决内存泄露的问题。可惜的是,当我们的代码中的结构复杂了以后,造成循环引用的原因开始变得多样,我们就没法那么容易观察到了,这时候,我们必须对代码进行仔细的检查。

尤其是当碰到Closure,当我们往Native对象(例如Dom对象、ActiveX Object)上绑定事件响应代码时,一个不小心,我们就会制造出Closure Memory Leak。其关键原因,其实和前者是一样的,也是一个跨javascript object和native object的循环引用。只是代码更为隐蔽,这个隐蔽性,是由于javascript的语言特性造成的。但在使用类似内嵌函数的时候,内嵌的函数有拥有一个reference指向外部函数的scope,包括外部函数的参数,因此也就很容易造成一个很隐蔽的循环引用,例如:

DOM_Node.onevent ->function_object.[ [ scope ] ] ->scope_chain ->Activation_object.nodeRef ->DOM_Node。

[http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp]有个例子极深刻地显示了该隐蔽性:

< html>
< head>
< script language = "JScript">
function AttachEvents(element)
{
// This structure causes element to ref ClickEventHandler
// element有个引用指向函数ClickEventHandler()
element.attachEvent("onclick", ClickEventHandler);
function ClickEventHandler(){
// This closure refs element
// 该函数有个引用指向AttachEvents(element)调用Scope,也就是执行了参数element。
}
}
function SetupLeak()
{
// The leak happens all at once
AttachEvents(document.getElementById("LeakedDiv"));
}
< /script>
< /head>
< body onload = "SetupLeak()" onunload = "BreakLeak()">
< div id = "LeakedDiv">< /div>
< /body>
< /html>

还有这个例子在IE 6中同样原因会引起泄露

function leakmaybe() {
var elm = document.createElement("DIV");
elm.onclick = function(){
return 2 + 2 ;
}
}
for( var i = 0 ;i < 10000;i ++){
leakmaybe();
}

关于Closure的知识,大家可以看看 这篇文章 ,习惯中文也可以看看zkjbeyond的blog,他对Closure 这篇文章进行了 简要的翻译 。之所以会有这一系列的问题,关键就在于javascript是种函数式脚本解析语言,因此javascript中“函数中的变量的作用域是定义作用域,而不是动态作用域”,这点在犀牛书《JavaScript: The Definitive Guide》中的“Funtion”一章中有所讨论。

http://support.microsoft.com/default.aspx?scid=KB;EN-US;830555中也对这个问题举了很详细的例子。

一些简单的解决方案

目前大多数ajax前端的javascript framework都利用对事件的管理,解决了该问题。

如果你需要自己解决这个问题,可以参考以下的一些方法:

http://outofhanwell.com/ieleak/index.php?title=Main_Page:有个不错的检测工具

http://youngpup.net/2005/0221010713 中提到:可以利用递归Dom树,解除event绑定,从而解除循环引用:

if (window.attachEvent){
var clearElementProps = ['data','onmouseover','onmouseout','onmousedown','onmouseup',
'ondblclick','onclick','onselectstart','oncontextmenu'];

window.attachEvent("onunload", function(){
var el;
for(var d = document.all.length;d--;){
el = document.all[d];
for(var c = clearElementProps.length;c--;){
el[clearElementProps[c]] = null;
}
}
}
);
}

而http://novemberborn.net/javascript/event-cache一文中则通过增加EventCache,从而给出一个相对结构化的解决方案

/*
EventCache Version 1.0
Copyright 2005 Mark Wubben
Provides a way for automagically removing events from nodes and thus preventing memory leakage.
See < http://novemberborn.net/javascript/event-cache> for more information.

This software is licensed under the CC-GNU LGPL < http://creativecommons.org/licenses/LGPL/2.1/>
*/
/*
Implement array.push for browsers which don't support it natively.
Please remove this if it's already in other code
*/
if (Array.prototype.push == null ){
Array.prototype.push = function (){
for (var i = 0;i< arguments.length;i ++){
this[this.length] = arguments[i];
};
return this.length;
};
};
/*
Event Cache uses an anonymous function to create a hidden scope chain.
This is to prevent scoping issues.
*/
var EventCache = function (){
var listEvents=[];
return{
listEvents : listEvents,

add: function(node, sEventName, fHandler, bCapture){
listEvents.push(arguments);
},

flush: function (){
var i,item;
for (i = listEvents.length-1;i>=0;i=i-1 ){
item=listEvents[i];
if(item[0].removeEventListener){
item[0].removeEventListener(item[1],item[2],item[3]);
};

/* From this point on we need the event names to be prefixed with 'on" */
if(item[1].substring(0, 2)!="on" ){
item[1]="on"+item[1];
};

if(item[0].detachEvent){
item[0].detachEvent(item[1], item[2]);
};

item[0][item[1]]=null;
};
}
};
}();

使用方法也很简单:



< script type="text/javascript">
function addEvent(oEventTarget,sEventType,fDest){
if(oEventTarget.attachEvent){
oEventTarget.attachEvent("on" + sEventType, fDest);
}
elseif(oEventTarget.addEventListener){
oEventTarget.addEventListener(sEventType, fDest, true);
}
elseif(typeof oEventTarget[sEventType]=="function"){
var fOld = oEventTarget[sEventType];
oEventTarget[sEventType] = function(e){
fOld(e);
fDest(e);
};
}
else {
oEventTarget[sEventType] = fDest;
};

/* Implementing EventCache for all event systems */
EventCache.add(oEventTarget, sEventType, fDest, true);};
function createLeak(){
var body = document.body;
function someHandler(){
return body;
};
addEvent(body, "click", someHandler);
};

window.onload = function(){
var i = 500;
while(i > 0){
createLeak();
i = i - 1;
}
};

window.onunload = EventCache.flush;
< /script>

http://talideon.com/weblog/2005/03/js-memory-leaks.cfm 一文中的方法类似:

/*
* EventManager.js
* by Keith Gaughan
*
* This allows event handlers to be registered unobtrusively, and cleans
* them up on unload to prevent memory leaks.
*
* Copyright (c) Keith Gaughan, 2005.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* (CPL) which accompanies this distribution, and is available at
* http://www.opensource.org/licenses/cpl.php
*
* This software is covered by a modified version of the Common Public License
* (CPL), where Keith Gaughan is the Agreement Steward, and the licensing
* agreement is covered by the laws of the Republic of Ireland.
*/
// For implementations that don't include the push() methods for arrays.
if(!Array.prototype.push){
Array.prototype.push=function(elem){
this[this.length]=elem;
}
}
var EventManager={
_registry: null,
Initialise: function(){
if(this._registry==null){
this._registry=[];
// Register the cleanup handler on page unload.
EventManager.Add(window,"unload" ,this.CleanUp);
}
},
/*
* Registers an event and handler with the manager.
*
* @param obj Object handler will be attached to.
* @param type Name of event handler responds to.
* @param fn Handler function.
* @param useCapture Use event capture. False by default.
* If you don't understand this, ignore it.
*
* @return True if handler registered, else false.
*/
Add: function(obj, type, fn, useCapture){
this.Initialise();
// If a string was passed in, it's an id.
if(typeof obj=="string"){
obj = document.getElementById(obj);
}
if(obj==null || fn==null){
return false ;
}
// Mozilla/W3C listeners?
if(obj.addEventListener){
obj.addEventListener(type, fn, useCapture);
this._registry.push({obj: obj, type: type, fn: fn, useCapture: useCapture});
return true ;
}
// IE-style listeners?
if(obj.attachEvent && obj.attachEvent("on" + type,fn)){
this._registry.push({obj: obj, type: type, fn: fn, useCapture: false });
return true ;
}
return false ;
},
/* *
* Cleans up all the registered event handlers.
*/
CleanUp: function(){
for(var i=0;i< EventManager._registry.length;i++){
with(EventManager._registry[i]) {
// Mozilla/W3C listeners?
if(obj.removeEventListener) {
obj.removeEventListener(type, fn, useCapture);
}
else if(obj.detachEvent){// IE-style listeners?
obj.detachEvent("on"+type,fn);
}
}
}
// Kill off the registry itself to get rid of the last remaining
// references.
EventManager._registry = null ;
}
};

使用起来也很简单:

< html>
< head>
< script type=text/javascript src=EventManager.js>< /script>
< script type=text/javascript>
function onLoad(){
EventManager.Add(document.getElementById(testCase),click,hit);
return true;
}
function hit(evt) {
alert(click);
}
< /script>
< /head>
< body >
< h1>Click me!< /h1>
< /div>
< /body>
< /html>

google map api同样提供了一个类似的函数用在页面的unload事件中,解决Closure带来的内存泄露问题。

当然,如果你不嫌麻烦,你也可以为每个和native object有关的就阿vascript object编写一个destoryMemory函数,用来手动调用,从而手动解除Dom对象的事件绑定。

还有一种就是不要那么OO,抛弃Dom的一些特性,用innerHTML代替appendChild,避开循环引用。详细见http://birdshome.cnblogs.com/archive/2005/02/16/104967.html中的讨论贴。

Cross-Page Leaks

Cross-Page Leaks和下一节提到的Pseudo-Leaks在我看来,就是IE的bug,虽然MS死皮赖脸不承认

大家可以看看这段例子代码:

< html>
< head>
< script language="JScript">
// 这个函数会引发Cross-Page Leaks
function LeakMemory()
{
var hostElement=document.getElementById("hostElement");
// Do it a lot, look at Task Manager for memory response
for (i=0 ;i< 5000;i++){
var parentDiv = document.createElement("< div >");
var childDiv = document.createElement("< div >");
// This will leak a temporary object
parentDiv.appendChild(childDiv);
hostElement.appendChild(parentDiv);
hostElement.removeChild(parentDiv);
parentDiv.removeChild(childDiv);
parentDiv = null ;
childDiv = null ;
}
hostElement = null ;
}
// 而这个函数不会引发Cross-Page Leaks
function CleanMemory()
{
var hostElement = document.getElementById("hostElement");
// Do it a lot, look at Task Manager for memory response
for (i=0;i< 5000;i++)
{
var parentDiv = document.createElement("< div >");
var childDiv = document.createElement("< div >");
// Changing the order is important, this won't leak
hostElement.appendChild(parentDiv);
parentDiv.appendChild(childDiv);
hostElement.removeChild(parentDiv);
parentDiv.removeChild(childDiv);
parentDiv = null ;
childDiv = null ;
}
hostElement = null ;
}
< /script>
< /head>
< body>
< button > Memory Leaking Insert < /button>
< button > Clean Insert < /button>
< div id="hostElement">< / div >
< /body>
< /html>

LeakMemory和CleanMemory这两段函数的唯一区别就在于他们的代码的循序,从代码上看,两段代码的逻辑都没有错。



但LeakMemory却会造成泄露。原因是LeakMemory()会先建立起parentDiv和childDiv之间的连接,这时候,为了让 childDiv能够获知parentDiv的信息,因此IE需要先建立一个临时的scope对象。而后parentDiv建立了和 hostElement对象的联系,parentDiv和childDiv直接使用页面document的scope。可惜的是,IE不会释放刚才那个临时的scope对象的内存空间,直到我们跳转页面,这块空间才能被释放。而CleanMemory函数不同,他先把parentDiv和 hostElement建立联系,而后再把childDiv和parentDiv建立联系,这个过程不需要单独建立临时的scope,只要直接使用页面 document的scope就可以了, 所以也就不会造成内存泄露了

详细原因,大家可以看看http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp这篇文章。

IE 6中垃圾回收算法,就是从那些直接"in scope"的对象开始进行mark清除的:

Every variable which is "in scope" is called a "scavenger". A scavenger may refer to a number, an object, a string, whatever. We maintain a list of scavengers – variables are moved on to the scav list when they come into scope and off the scav list when they go out of scope.

Pseudo-Leaks

这个被称为“秀逗泄露”真是恰当啊^-^

看看这个例子:

< html>
< head>
< script language="JScript">
function LeakMemory()
{
// Do it a lot, look at Task Manager for memory response
for (i=0;i< 5000;i++)
{
hostElement.text = "function foo(){}" ; // 看内存会不断增加
}
}
< /script>
< /head>
< body>
< button > Memory Leaking Insert < /button>
< script id="hostElement">function foo(){}< /script>
< /body>
< /html>

MS是这么解释的,这不是内存泄漏。如果您创建了许多无法获得也无法释放的对象,那才是内存泄漏。在这里,您将创建许多元素,Internet Explorer 需要保存它们以正确呈现页面。Internet Explorer 并不知道您以后不会运行操纵您刚刚创建的所有这些对象的脚本。当页面消失时(当您浏览完,离开浏览器时)会释放内存。它不会泄漏。当销毁页面时,会中断循环引用。

唉~~~

详细原因,大家可以看看http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp这篇文章。

其它一些琐碎的注意点:

变量定义一定要用var,否则隐式声明出来的变量都是全局变量,不是局部变量;
全局变量没用时记得要置null;
注意正确使用delete,删除没用的一些函数属性;
注意正确使用try...cache,确保去处无效引用的代码能被正确执行;
open出来的窗口即使close了,它的window对象还是存在的,要记得删除引用;
frame和iframe的情况和窗口的情况类似。
考资料参:

http://jibbering.com/faq/faq_notes/closures.html
http://javascript.weblogsinc.com/2005/03/07/javascript-memory-leaks/
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp
http://72.14.203.104/search?q=cache:V9Bt4_HBzQ8J:jgwebber.blogspot.com/2005/01/dhtml-leaks-like-sieve.html+DHTML+Leaks+Like+a+Sieve+&hl=zh-CN&ct=clnk&cd=9 (这是DHTML Leaks Like a Sieve)一文在google上的cache,原文已经连不上了)
http://spaces.msn.com/siteexperts/Blog/cns!1pNcL8JwTfkkjv4gg6LkVCpw!338.entry
http://support.microsoft.com/default.aspx?scid=KB;EN-US;830555
http://www.ajaxtopics.com/leakpatterns.html
http://blogs.msdn.com/ericlippert/archive/2003/09/17/53028.aspx
http://www.quirksmode.org/blog/archives/2005/02/javascript_memo.html
http://youngpup.net/2005/0221010713
http://blogs.msdn.com/ericlippert/archive/2003/09/17/53038.aspx =
http://support.microsoft.com/kb/266071/EN-US ==>IE 5.0至5.5一些版本中的GC bug
http://www.quirksmode.org/blog/archives/2006/04/ie_7_and_javasc.html ==>ie 7的改进
http://erik.eae.net/archives/2006/04/26/23.23.02/ ==>ie 7的改进
http://www.feedbackarchive.com/spamvampire/today.html ==> Try this script for memory leaks - it leaked 50 megabytes in 15 minutes with firefox on linux:
http://birdshome.cnblogs.com/archive/2005/02/15/104599.html
http://www.quirksmode.org/dom/innerhtml.html
http://www.crockford.com/javascript/memory/leak.html
《JavaScript: The Definitive Guide》4th Edition
http://outofhanwell.com/ieleak/index.php?title=Main_Page