如果已安装anaconda将会很方便:
conda install xeus-cling -c conda-forge
官方项目地址:
如果已安装anaconda将会很方便:
conda install xeus-cling -c conda-forge
官方项目地址:
repost: link
计算机领域的很多技术都是需求推动的,上世纪90年代,由于互联网的飞速发展,网络服务器无法支撑快速增长的用户规模。1999年,Dan Kegel提出了著名的C10问题:一台服务器上同时处理10000个客户网络连接。10000个网络连接并不会发送请求到服务器,有些连接并不活跃,同一时刻,只有极少的部分连接发送请求。不同的服务类型,每个连接发送请求的频率也不相同,游戏服务器的连接会频繁的发送请求,而Web服务器的连接发送请求的频率就低很多。无论如何,根据经验法则,对于特定的服务类型,连接越多,同一时刻发送请求的连接也越多。
时至今日,C10K问题当然早已解决,不仅如此,一台机器能支撑的连接越来越多,后来提出了C10M问题,在一台机器上支撑1000万的连接,2015年,MigratoryData在单机承载12M的连接,解决了C10M问题。
本文先回顾C10问题的解决方案,再探讨如何构建支撑C10M的应用程序,聊聊其中涉及的各种技术。
时间退回到1999年,当时要实现一个网络服务器,大概有这样几种模式
这是一种非常简单的模式,服务器启动后监听端口,阻塞在accept上,当新网络连接建立后,accept返回新连接,服务器启动一个新的进程/线程专门负责这个连接。从性能和伸缩性来说,这种模式是非常糟糕的,原因在于
进程/线程创建和销毁的时间,操作系统创建一个进程/线程显然需要时间,在一个繁忙的服务器上,如果每秒都有大量的连接建立和断开,采用每个进程/线程处理一个客户连接的模式,每个新连接都要创建创建一个进程/线程,当连接断开时,销毁对应的线程/进程。创建和销毁进程/线程的操作消耗了大量的CPU资源。使用进程池和线程池可以缓解这个问题。
内存占用。主要包含两方面,一个是内核数据结构所占用的内存空间,另外一个是Stack所占用的内存。有些应用的调用栈很深,比如Java应用,经常能看到几十上百层的调用栈。
上下文切换的开销。上下文切换时,操作系统的调度器中断当前线程,选择另外一个可运行的线程在CPU上继续运行。调度器需要保存当前线程的现场信息,然后选择一个可运行的线程,再将新线程的状态恢复到寄存器中。保存和恢复现场所需要的时间和CPU型号有关,选择一个可运行的线程则完全是软件操作,Linux 2.6才开始使用常量时间的调度算法。 以上是上下文切换的直接开销。除此之外还有一些间接开销,上下文切换导致相关的缓存失效,比如L1/L2 Cache,TLB等,这些也会影响程序的性能,但是间接开销很难衡量。
有意思的是,这种模式虽然性能极差,但却依然是我们今天最常见到的模式,很多Web程序都是这样的方式在运行。
另外一种方式是使用select/poll,在一个线程内处理多个客户连接。select和poll能够监控多个socket文件描述符,当某个文件描述符就绪,select/soll从阻塞状态返回,通知应用程序可以处理用户连接了。使用这种方式,我们只需要一个线程就可以处理大量的连接,避免了多进程/线程的开销。之所以把select和poll放在一起说,原因在于两者非常相似,性能上基本没有区别,唯一的区别在于poll突破了select 1024个文件描述符的限制,然而当文件描述符数量增加时,poll性能急剧下降,因此所谓突破1024个文件描述符实际上毫无意义。select/poll并不完美,依然存在很多问题:
每次调用select/poll,都要把文件描述符的集合从用户地址空间复制到内核地址空间select/poll返回后,调用方必须遍历所有的文件描述符,逐一判断文件描述符是否可读/可写。
这两个限制让select/poll完全失去了伸缩性。连接数越多,文件描述符就越多,文件描述符越多,每次调用select/poll所带来的用户空间到内核空间的复制开销越大。最严重的是当报文达到,select/poll返回之后,必须遍历所有的文件描述符。假设现在有1万个连接,其中只一个连接发送了请求,但是select/poll就要把1万个连接全部检查一遍。
FreeBSD 4.1引入了kqueue,此时是2000年7月,而在Linux上,还要等待2年后的2002年才开始引入kqueue的类似实现: epoll。epoll最初于 2.5.44进入Linux kernel mainline,此时已经是2002年,距离C10K问题提出已经过了3年。
epoll是如何提供一个高性能可伸缩的IO多路复用机制呢?首先,epoll引入了epoll instance这个概念,epoll instance在内核中关联了一组要监听的文件描述符配置:interest list,这样的好处在于,每次要增加一个要监听的文件描述符,不需要把所有的文件描述符都配置一次,然后从用户地址空间复制到内核地址空间,只需要把单个文件描述符复制到内核地址空间,复制开销从O(n)降到了O(1)。
注册完文件描述符后,调用epoll_wait开始等待文件描述符事件。epoll_wait可以只返回已经ready的文件描述符,因此,在epoll_wait返回之后,程序只需要处理真正需要处理的文件描述符,而不用把所有的文件描述符全部遍历一遍。假设在全部N个文件描述符中,只有一个文件描述符Ready,select/poll要执行N次循环,epoll只需要一次。
epoll出现之后,Linux上才真正有了一个可伸缩的IO多路复用机制。基于epoll,能够支撑的网络连接数取决于硬件资源的配置,而不再受限于内核的实现机制。CPU越强,内存越大,能支撑的连接数越多。
不同的操作系统上提供了不同的IO多路复用实现,Linux上有epoll,FreeBSD有kqueue,Windows有IOCP。对于需要跨平台的程序,必然需要一个抽象层,提供一个统一的IO多路复用接口,屏蔽各个系统接口的差异性。
Reactor是实现这个目标的一次尝试,最早出现在Douglas C. Schmidt的论文”The Reactor An Object-Oriented Wrapper for Event-Driven Port Monitoring and Service Demultiplexing”中。从论文的名字可以看出,Reactor是poll这种编程模式的一个面向对象包装。考虑到论文的时间,当时正是面向对象概念正火热的时候,什么东西都要蹭蹭面向对象的热度。论文中,DC Schmidt描述了为什么要做这样的一个Wrapper,给出了下面几个原因
操作系统提供的接口太复杂,容易出错。select和poll都是通用接口,因为通用,增加了学习和正确使用的复杂度。
接口抽象层次太低,涉及太多底层的细节。
不能跨平台移植。
难以扩展。
实际上除了第三条跨平台,其他几个理由实在难以站得住脚。select/poll这类接口复杂吗,使用起来容易出错吗,写出来的程序难以扩展吗?不过不这么说怎么体现Reactor的价值呢。正如论文名称所说的,Reactor本质是对操作系统IO多路复用机制的一个面向对象包装,为了证明Reactor的价值,DC Schmidt还用C++面向对象的特性实现了一个编程框架:ACE,实际上使用ACE比直接使用poll或者epoll复杂多了。
后来DC Schmidt写了一本书《面向模式的软件架构》,再次提到了Reactor,并重新命名为Reactor Pattern,现在网络上能找到的Reactor资料,基本上都是基于Reactor Pattern,而不是早期的面向Object-Orientend Wrapper。
《面向模式的软件》架构中还提到了另外一种叫做Proactor的模式,和Reactor非常类似,Reactor针对同步IO,Proactor则针对异步IO。
Reactor看上去并不复杂,但是想编写一个完整的应用程序时候就会发现其实没那么简单。为了避免Reactor主逻辑阻塞,所有可能会导致阻塞的操作必须注册到epoll上,带来的问题就是处理逻辑的支离破碎,大量使用callback,产生的代码复杂难懂。如果应用程序中还有非网络IO的阻塞操作,问题更严重,比如在程序中读写文件。Linux中文件系统操作都是阻塞的,虽然也有Linux AIO,但是一直不够成熟,难堪大用。很多软件采用线程池来解决这个问题,不能通过epoll解决的阻塞操作,扔到一个线程池执行。这又产生了多线程内存开销和上下文切换的问题。
Future机制是对Callback的简单优化,本质上还是Callback,但是提供了一致的接口,代码相对来说简单一些,不过在实际使用中还是比较复杂的。Seastar是一个非常彻底的future风格的框架,从它的代码可以看到这种编程风格真的非常复杂,阻塞式编程中一个函数几行代码就能搞定的事情,在Seastar里需要上百行代码,几十个labmda (在Seastar里叫做continuation)。
纤程是一种用户态调度的线程,比如Go语言中的goroutine,有些人可能会把这种机制成为coroutine,不过我认为coroutine和纤程还是有很大区别的,coroutine是泛化的子进程,具有多个进入和退出点,用来一些一些相互协作的程序,典型的例子就是Python中的generator。纤程则是一种运行和调度机制。
纤程真正做到了高性能和易用,在Go语言中,使用goroutine实现的高性能服务器是一件轻松愉快的事情,完全不用考虑线程数、epoll、回调之类的复杂操作,和编写阻塞式程序完全一样。
网络子系统是Linux内核中一个非常庞大的组件,提供了各种通用的网络能力。通用通常意味在在某些场景下并不是最佳选择。实际上业界的共识是Linux内核网络不支持超大并发的网络能力。根据我过去的经验,Linux最大只能处理1MPPS,而现在的10Gbps网卡通常可以处理10MPPS。随着更高性能的25Gbps,40Gbps网卡出现,Linux内核网络能力越发捉襟见肘。
为什么Linux不能充分发挥网卡的处理能力?原因在于:
大多数网卡收发使用中断方式,每次中断处理时间大约100us,另外要考虑cache miss带来的开销。部分网卡使用NAPI,轮询+中断结合的方式处理报文,当报文放进队列之后,依然要触发软中断。
数据从内核地址空间复制到用户地址空间。
收发包都有系统调用。
网卡到应用进程的链路太长,包含了很多不必要的操作。
Linux高性能网络一个方向就是绕过内核的网络栈(kernel bypass),业界有不少尝试
PF_RING 高效的数据包捕获技术,比libpcap性能更好。需要自己安装内核模块,启用ZC Driver,设置transparent_mode=2的情况下,报文直接投递到客户端程序,绕过内核网络栈。
Snabbswitch 一个Lua写的网络框架。完全接管网卡,使用UIO(Userspace IO)技术在用户态实现了网卡驱动。
Intel DPDK,直接在用户态处理报文。非常成熟,性能强大,限制是只能用在Intel的网卡上。根据DPDK的数据,3GHz的CPU Core上,平均每个报文的处理时间只要60ns(一次内存的访问时间)。
Netmap 一个高性能收发原始数据包的框架,包含了内核模块以及用户态库函数,需要网卡驱动程序配合,因此目前只支持特定的几种网卡类型,用户也可以自己修改网卡驱动。
XDP,使用Linux eBPF机制,将报文处理逻辑下放到网卡驱动程序中。一般用于报文过滤、转发的场景。
kernel bypass技术最大的问题在于不支持POSIX接口,用户没办法不修改代码直接移植到一种kernel bypass技术上。对于大多数程序来说,还要要运行在标准的内核网络栈上,通过调整内核参数提升网络性能。
报文到达网卡之后,在一个CPU上触发中断,CPU执行网卡驱动程序从网卡硬件缓冲区读取报文内容,解析后放到CPU接收队列上。这里所有的操作都在一个特定的CPU上完成,高性能场景下,单个CPU处理不了所有的报文。对于支持多队列的网卡,报文可以分散到多个队列上,每个队列对应一个CPU处理,解决了单个CPU处理瓶颈。
为了充分发挥多队列网卡的价值,我们还得做一些额外的设置:把每个队列的中断号绑定到特定CPU上。这样做的目的,一方面确保网卡中断的负载能分配到不同的CPU上,另外一方面可以将负责网卡中断的CPU和负责应用程序的CPU区分开,避免相互干扰。
在Linux中,/sys/class/net/${interface}/device/msi_irqs下保存了每个队列的中断号,有了中断号之后,我们就可以设置中断和CPU的对应关系了。网上有很多文章可以参考。
回忆下TCP数据的发送过程:应用程序将数据写到套接字缓冲区,内核将缓冲区数据切分成不大于MSS的片段,附加上TCP Header和IP Header,计算Checksum,然后将数据推到网卡发送队列。这个过程中需要CPU全程参与, 随着网卡的速度越来越快,CPU逐渐成为瓶颈,CPU处理数据的速度已经赶不上网卡发送数据的速度。经验法则,发送或者接收1bit/s TCP数据,需要1Hz的CPU,1Gbps需要1GHz的CPU,10Gbps需要10GHz的CPU,已经远超单核CPU的能力,即使能完全使用多核,假设单个CPU Core是2.5GHz,依然需要4个CPU Core。
为了优化性能,现代网卡都在硬件层面集成了TCP分段、添加IP Header、计算Checksum等功能,这些操作不再需要CPU参与。这个功能叫做tcp segment offloading,简称tso。使用ethtool -k 可以检查网卡是否开启了tso
除了tso,还有其他几种offloading,比如支持udp分片的ufo,不依赖驱动的gso,优化接收链路的lro
随着摩尔定律失效,CPU已经从追求高主频转向追求更多的核数,现在的服务器大都是96核甚至更高。构建一个支撑C10M的应用程序,必须充分利用所有的CPU,最重要的是程序要具备水平伸缩的能力:随着CPU数量的增多程序能够支撑更多的连接。
很多人都有一个误解,认为程序里使用了多线程就能利用多核,考虑下CPython程序,你可以创建多个线程,但是由于GIL的存在,程序最多只能使用单个CPU。实际上多线程和并行本身就是不同的概念,多线程表示程序内部多个任务并发执行,每个线程内的任务可以完全不一样,线程数和CPU核数没有直接关系,单核机器上可以跑几百个线程。并行则是为了充分利用计算资源,将一个大的任务拆解成小规模的任务,分配到每个CPU上运行。并行可以 通过多线程实现,系统上有几个CPU就启动几个线程,每个线程完成一部分任务。
并行编程的难点在于如何正确处理共享资源。并发访问共享资源,最简单的方式就加锁,然而使用锁又带来性能问题,获取锁和释放锁本身有性能开销,锁保护的临界区代码不能只能顺序执行,就像CPython的GIL,没能充分利用CPU。
这两种方式的思路是一样的,都是创建变量的多个副本,使用变量时只访问本地副本,因此不需要任何同步。现代编程语言基本上都支持Thread Local,使用起来也很简单,C/C++里也可以使用__thread标记声明ThreadLocal变量。
Per-CPU则依赖操作系统,当我们提到Per-CPU的时候,通常是指Linux的Per-CPU机制。Linux内核代码中大量使用Per-CPU变量,但应用代码中并不常见,如果应用程序中工作线程数等于CPU数量,且每个线程Pin到一个CPU上,此时才可以使用。
如果共享资源是int之类的简单类型,访问模式也比较简单,此时可以使用原子变量。相比使用锁,原子变量性能更好。在竞争不激烈的情况下,原子变量的操作性能基本上和加锁的性能一致,但是在并发比较激烈的时候,等待锁的线程要进入等待队列等待重新调度,这里的挂起和重新调度过程需要上下文切换,浪费了更多的时间。
大部分编程语言都提供了基本变量对应的原子类型,一般提供set, get, compareAndSet等操作。
lock-free这个概念来自
An algorithm is called non‐blocking if failure or suspension of any thread cannot cause failure or suspension of another thread; an algorithm is called lock‐free if, at each step, some thread can make progress.
non-blocking算法任何线程失败或者挂起,不会导致其他线程失败或者挂起,lock-free则进一步保证线程间无依赖。这个表述比较抽象,具体来说,non-blocking要求不存在互斥,存在互斥的情况下,线程必须先获取锁再进入临界区,如果当前持有锁的线程被挂起,等待锁的线程必然需要一直等待下去。对于活锁或者饥饿的场景,线程失败或者挂起的时候,其他线程完全不仅能正常运行,说不定还解决了活锁和饥饿的问题,因此活锁和饥饿符合non-blocking,但是不符合lock-free。
实现一个lock-free数据结构并不容易,好在已经有了几种常见数据结构的的lock-free实现:buffer, list, stack, queue, map, deque,我们直接拿来使用就行了。
有时候没有条件使用lock-free,还是得用锁,对于这种情况,还是有一些优化手段的。首先使用尽量减少临界区的大小,使用细粒度的锁,锁粒度越细,并行执行的效果越好。其次选择适合的锁,比如考虑选择读写锁。
使用CPU affinity机制合理规划线程和CPU的绑定关系。前面提到使用CPU affinity机制,将多队列网卡的中断处理分散到多个CPU上。不仅是中断处理,线程也可以绑定,绑定之后,线程只会运行在绑定的CPU上。为什么要将线程绑定到CPU上呢?绑定CPU有这样几个好处
Linux中,程序内使用的内存地址是虚拟地址,并不是内存的物理地址。为了简化虚拟地址到物理地址的映射,虚拟地址到物理地址的映射最小单位是“Page”,默认情况下,每个页大小为4KB。CPU指令中出现的虚拟地址,为了读取内存中的数据,指令执行前要把虚拟地址转换成内存物理地址。Linux为每个进程维护了一张虚拟地址到物理地址的映射表,CPU先查表找到虚拟地址对应的物理地址,再执行指令。由于映射表维护在内存中,CPU查表就要访问内存。相对CPU的速度来说,内存其实是相当慢的,一般来说,CPU L1 Cache的访问速度在1ns左右,而一次内存访问需要60-100ns,比CPU执行一条指令要慢得多。如果每个指令都要访问内存,比如严重拖慢CPU速度,为了解决这个问题,CPU引入了TLB(translation lookaside buffer),一个高性能缓存,缓存映射表中一部分条目。转换地址时,先从TLB查找,没找到再读内存。
显然,最理想的情况是映射表能够完全缓存到TLB中,地址转换完全不需要访问内存。为了减少映射表大小,我们可以使用“HugePages”:大于4KB的内存页。默认HugePages是2MB,最大可以到1GB。
内存分配是个复杂且耗时的操作,涉及空闲内存管理、分配策略的权衡(分配效率,碎片),尤其是在并发环境中,还要保证内存分配的线程安全。如果内存分配成为了应用瓶颈,可以尝试一些优化策略。比如内存复用i:不要重复分配内存,而是复用已经分配过的内存,在C++/Java里则考虑复用已有对象,这个技巧在Java里尤其重要,不仅能降低对象创建的开销,还避免了大量创建对象导致的GC开销。另外一个技巧是预先分配内存,实际上相当于在应用内实现了一套简单的内存管理,比如Memcached的Slab。
对于一个Web服务器来说,响应一个静态文件请求需要先将文件从磁盘读取到内存中,再发送到客户端。如果自信分析这个过程,会发现数据首先从磁盘读取到内核的页缓冲区,再从页缓冲区复制到Web服务器缓冲区,接着从Web服务器缓冲区发送到TCP发送缓冲区,最后经网卡发送出去。这个过程中,数据先从内核复制到进程内,再从进程内回到内核,这两次复制完全是多余的。Zero Copy就是类似情况的优化方案,数据直接在内核中完成处理,不需要额外的复制。
Linux中提供了几种ZeroCopy相关的技术,包括sendfile,splice,copy_file_range,Web服务器中经常使用sendfile优化性能。
最后
千万牢记:不要过早优化。
优化之前,先考虑两个问题:
现在的性能是否已经满足需求了
如果真的要优化,是不是已经定位了瓶颈
在回答清楚这两个问题之前,不要盲目动手。
高性能服务器必须考虑的4个方面:
数据拷贝
内存管理
进程/线程上下文切换
锁争用
说明:以下文章中会包含一些研究服务器性能的链接,这些链接也是非常重要的文档,本文不再列出,查看下面的文章内容时,可点击文章里面的链接访问。
影响服务器性能的TCP选项:TCP_CORK,TCP_NODELAY
http://bbs.net130.com/showthread.php?t=128111
搜狗关于epoll的技术文档
http://www.sogou.com/labs/report/1-1.pdf
CU超高性能网络编程, Asynchronous network I/O
http://bbs2.chinaunix.net/viewthread.php?tid=1214570
高性能服务器设计(个人经验)
http://blog.chinaunix.net/u/5251/showart_236329.html
Linux环境进程间通信(五): 共享内存(上)
http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html
Linux环境进程间通信(五): 共享内存(下)
http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html
介绍Linux下高性能web server开发必须注意的细节
http://bbs2.chinaunix.net/thread-1400650-1-3.html
杨建:网站加速–服务器编写篇(上)
http://blog.sina.com.cn/s/blog_466c66400100bi2n.html
高性能服务器软件开发
http://amyz.itpub.net/post/34151/419608
较高性能前端Linux服务器/etc/sysctl.conf配置
http://guzz.javaeye.com/blog/357829
高性能服务器程序开发
http://blog.csdn.net/elssann/archive/2004/11/16/183811.aspx
单进程高性能服务器存在这样的弊端
http://bbs.chinaunix.net/thread-1613306-1-1.html
高性能服务器底层网络通信模块的设计方法 (虽然是windows下IOCP的方法,但也有参考作用)
http://articles.e-works.net.cn/pc_server/article75536.htm
完成端口与高性能服务器程序开发
http://www.yuanma.org/data/2006/0604/article_625.htm
详谈高性能TCP服务器开发http://siqiang0312.blog.163.com/blog/static/36881404200862245850217/
详谈高性能UDP服务器的开发http://siqiang0312.blog.163.com/blog/static/368814042008627104427570/
异步IO、APC、IO完成端口、线程池与高性能服务器
http://www.delphibbs.com/keylife/iblog_show.asp
使用 libevent 和 libev 提高网络应用性能
http://www.ibm.com/developerworks/cn/aix/library/au-libev/index.html
配置开发支持高并发TCP连接的Linux应用程序全攻略
配置开发支持高并发TCP连接的Linux应用程序总结
http://apps.hi.baidu.com/share/detail/41797873
高性能服务器设计时需要考虑的几个问题
单串全匹配的常用算法有朴素查找,Knuth-Morris-Pratt(kmp),boyer moore(bm), boyer moore horspool(bmh), boyer moore horspool sunday(sunday)等算法,本文也是对这个几个算法在不同场景下的比较。
优点是不需要辅助空间,缺点就是效率可能较低。朴素查找的实现通常有字符比对,内存比对,优化过的库函数,本文中的对应实现分别为bf,find_str,sys_str。
bf容易扩展,实现大小写的忽略及特殊类型序列的查找。
find_str利用内存比较函数,导致不易扩展。
库函数strstr,功能单一,常常无法适合场景,比如忽略大小写没有结尾符的数据。
kmp是利用匹配串的特性,每次比较后尽可能多的移动下标。需要匹配串长度*sizeof(类型)的辅助空间, 时间复杂度O(m+n)
bm也是利用匹配串的特性,每次比较后尽可能多的移动下标。bm设计了坏字符,好后缀的规则来加快移动下标。通常需要(匹配串长度+字符集大小)*sizeof(类型)的辅助空间。一般情况下,比KMP算法快3-5倍。该算法常用于文本编辑器中的搜索匹配功能,比如大家所熟知的GNU grep命令使用的就是该算法,这也是GNU grep比BSD grep快的一个重要原因。
bmh改变了bm的坏字符的规则,相比bm实现起来容易一些。通常需要字符集大小*sizeof(类型)的辅助空间。
sunday再次改变了bm的移动规则,相比bm更加简易。通常需要字符集大小*sizeof(类型)的辅助空间。
测试场景的解释:
sys_str是在有结尾符时的匹配结果,sys_str_是无结尾符又不能修改原数据时模拟拷贝一遍数据后的匹配。
未预处理是所有数据重新计算的测试。
已预处理是在循环测试时,sys_str_只拷贝一次数据,其他算法只计算一次辅助数组的测试。
样本示例
1 | ... |
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.215000 | 19.888000 | 0.366000 | 0.866000 | 0.806000 | 0.658000 | 0.157000 | 0.165000 |
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.203000 | 0.253000 | 0.338000 | 0.731000 | 0.785000 | 0.062000 | 0.063000 | 0.062000 |
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
1.099000 | 2.923000 | 1.485000 | 3.148000 | 2.777000 | 0.336000 | 0.140000 | 0.222000 |
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.848000 | 0.848000 | 1.448000 | 3.352000 | 2.961000 | 0.219000 | 0.173000 | 0.175000 |
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.234000 | 0.435000 | 0.419000 | 0.914000 | 0.761000 | 0.069000 | 0.047000 | 0.034000 |
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.290000 | 0.219000 | 0.428000 | 0.855000 | 0.812000 | 0.078000 | 0.047000 | 0.031000 |
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.124000 | 0.331000 | 0.188000 | 0.376000 | 0.369000 | 2.005000 | 0.016000 | 0.000000 |
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.110000 | 0.109000 | 0.188000 | 0.400000 | 0.380000 | 0.015000 | 0.000000 | 0.000000 |
样本示例
...
>NM_001195662
AAGCTCAGCCTTTGCTCAGATTCTCCTCTTGATGAAACAAAGGGATTTCTGCACATGCTT
GAGAAATTGCAGGTCTCACCCAAAATGAGTGACACACCTTCTACTAGTTTCTCCATGATT
CATCTGACTTCTGAAGGTCAAGTTCCTTCCCCTCGCCATTCAAATATCACTCATCCTGTA
GTGGCTAAACGCATCAGTTTCTATAAGAGTGGAGACCCACAGTTTGGCGGCGTTCGGGTG
GTGGTCAACCCTCGTTCCTTTAAGACTTTTGACGCTCTGCTGGACAGTTTATCCAGGAAG
GTACCCCTGCCCTTTGGGGTAAGGAACATCAGCACGCCCCGTGGACGACACAGCATCACC
...
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.047000 | 0.078000 | 0.063000 | 0.176000 | 0.093000 | 0.098000 | 0.031000 | 0.032000 |
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.047000 | 0.032000 | 0.078000 | 0.153000 | 0.112000 | 0.016000 | 0.016000 | 0.031000 |
样本示例
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.047000 | 0.094000 | 0.063000 | 0.171000 | 0.125000 | 1.663000 | 0.046000 | 0.047000 |
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.047000 | 0.035000 | 0.079000 | 0.166000 | 0.109000 | 0.016000 | 0.031000 | 0.034000 |
长度为157,615,301
样本示例
...
/proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/\\
Component Based Servicing/Packages/Package_for_KB2552343_SP1~31bf3856ad364e35\\
~amd64~~6.1.1.0/InstallName
/proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/\\
Component Based Servicing\\
/Packages/Package_for_KB2552343_SP1~31bf3856ad364e35~amd64~~6.1.1.0/InstallL\\
ocation
/proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/\\
Component \\
Based Servicing/Packages/Package_for_KB2552343_SP1~31bf3856ad364e35~amd64~~6\\
.1.1.0/CurrentState
/proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/\\
Component \\
Based Servicing/Packages/Package_for_KB2552343_SP1~31bf3856ad364e35~amd64~~6\\
.1.1.0/SelfUpdate
/proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/\\
Component \\
Based Servicing/Packages/Package_for_KB2552343_SP1~31bf3856ad364e35~amd64~~6\\
.1.1.0/Visibility
...
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.478000 | 0.999000 | 0.840000 | 1.490000 | 0.989000 | 0.034000 | 0.032000 | 0.081000 |
sys_str | sys_str_ | bf | find_str | kmp | bm | bmh | sunday |
---|---|---|---|---|---|---|---|
0.421000 | 0.893000 | 0.767000 | 1.534000 | 1.078000 | 0.045000 | 0.031000 | 0.107000 |
通常情况下库函数strstr要比bf,find_str方式要快一些。而kmp在理论上较快,但是也有慢的情况。bm算法通常情况表现的比kmp快一些,但也有慢的情况。bmh和sunday在不同情况下表现较为均衡。在重复测试时,可以观察到,做了预处理后,对效率有了显著的提升。
实际情况往往具有不同程度特殊性,如果像优化的更好,就要根据特殊性选择算法或者改进算法。
多线程情况下,可以将数据进行分段,分段后又变成这样的子问题。