请问为什么到底什么样的业务是计算密集型,什么样的业务是IO密集型?为什么说PHP最初设计是针对计算机密集型的,node.js是针对IO密集型的?
昨天看廖雪峰博客中刚好有一章节讲到:计算密集型 vs. IO密集型,摘录如下:
我们可以把任务分为计算密集型和IO密集型。 计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。 计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。 IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。 IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。
我们可以把任务分为计算密集型和IO密集型。
计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。
有一个被用烂了的栗子,解释 Node.js 语言 非阻塞,事件驱动 概念,大约是这样的:
非阻塞
事件驱动
你到餐馆吃饭,餐馆里的服务员在帮你点单之后,会直接把菜单塞给厨房,然后立刻去到下一位顾客处,而非在厨房等待(阻塞)。直到烹饪结束后,厨师大喊“来拿菜”(事件)。服务员跑回厨房,把菜品端到你的桌子上(事件处理/回调)
阻塞
事件
事件处理
回调
在这个栗子中,我们可以简单的理解为:服务员相当于 CPU,而厨房的工作就是I/O。显然的,在一个饭店里,服务员的工作并不复杂,”等待上菜的时间“多数是花在”等待厨房“上。这与如今大多数网站的情况相似:网站通常不需要做太多复杂的运算,但需要花费大量的时间等待 I/O 处理,比如数据库查询,比如图片、视频的读取。
CPU
I/O
于是 Node.js 舍弃了传统 Web 服务中“每当一次请求来临,都打开一个线程来单独处理”的做法,而是采用事件驱动的模型,默认情况下,仅用单线程就可以负担相当高的并发量。
于是在这种情境下,我们说:Node.js 更适合 IO密集型的任务处理。
Node.js 更适合 IO密集型的任务处理
但如果我们把上面的栗子更换一下,比如说咱们不开饭馆,开银行。每当客户到来,要求取出指定的款项,作为服务员,你需要根据客户的账户等级计算利率,计算利息,计算分红,等等……(随意想到的比方,可能不太恰当),而“取钱并交给客户”这个动作本身却并不复杂。
这时候,就不能指望像饭店那样,只靠一个服务员就能应付大量的客户,因为每个请求都需要独占服务员大量的时间(不可避免的阻塞)。那么此时,传统的模型,例如 PHP 或许就变得更加合适了。
以上,希望能解你所惑
IO密集型:就是IO比较多的应用,比如网络传输、数据库等调用。web应用大多数是这种
计算密集型:顾名思义就是需要大量的CPU计算的应用类型。像云计算一类的应用应该属于这种。
什么是计算密集?举个例子,把SQLite数据库放到Linux内存文件系统/dev/shm上对100万数据进行SELECT查询操作,那么这个SELECT查询,在使用了B+树索引时,在B+树索引上的二分查找就是典型的密集计算,如果没有使用索引,那单纯的扫描整个表也是密集计算.所以说,像关系数据库这种东西,普遍都是使用C/C++实现,保证性能和控制内存占用.
什么是IO密集?比如SQLite数据库不在内存,而在普通机械磁盘上,这时写操作(INSERT/UPDATE/DELETE)都是典型的IO密集操作,因为这时就算CPU再快,SQLite引擎再快,也会被机械磁盘的写入操作拖慢.所以为了并发,SQLite后来引入了WAL(write-ahead log)预写式日志支持,具体配置就是执行一下SQLite查询:
WAL(write-ahead log)
PRAGMA synchronous = NORMAL; PRAGMA journal_mode = WAL;
WAL机制的原理是: 修改并不直接写入到数据库文件中,而是写入到另外一个称为WAL的文件中(data.db3-wal). 如果事务失败,WAL中的记录会被忽略,撤销修改. 如果事务成功,它将在随后的某个时间(PRAGMA synchronous = NORMAL)被写回到数据库文件中,提交修改. 同步WAL文件和数据库文件的行为被称为checkpoint(检查点),它由SQLite自动执行, 默认是在WAL文件积累到1000页修改的时候(PRAGMA wal_autocheckpoint). 在适当的时候,也可以手动执行checkpoint,SQLite提供了相关的接口,执行 PRAGMA wal_checkpoint 之后,WAL文件会被清空. 在读的时候,SQLite将在WAL文件中搜索,找到最后一个写入点,记住它,并忽略在此之后的写入点(这保证了读写和读读可以并行执行). 随后,它确定所要读的数据所在页是否在WAL文件中,如果在,则读WAL文件中的数据,如果不在,则直接读数据库文件中的数据. 在写的时候,SQLite将之写入到WAL文件中即可,但是必须保证独占写入,因此写与写之间不能并行执行. WAL在实现的过程中,使用了共享内存技术(data.db3-shm),因此,所有的读写进程必须在同一个机器上,否则,无法保证数据一致性.
像WAL和checkpoint这种概念,在其他数据库比如MySQL中也存在,只不过MySQL会更复杂,能支持更大规模的并发写操作.像WAL+checkpoint这种写入方式,你就可以看做是一种异步的写入.
WAL+checkpoint
Node的JS解释器基于Chromium的V8,而V8具有JIT即时编译机制,所以Node的密集计算的性能是要比PHP强的.虽然PHP官方现在也在开发PHP的JIT试验分之,但其性能仍然不如V8.不过就算Node计算性能好,也几乎不会有人推荐在Node里执行大规模的密集计算,因为密集计算耗必定阻塞Node服务,这和Node倡导的无阻塞理念相背.
Node利用JS事件驱动的特性,做到了无阻塞,但基于回调的事件编程方式不利于代码维护.而且Node这种服务不能像Tomcat这类Java服务使用单进程多线程利用多核,而V8也并不是为多线程设计,所以Node官方只能自己搞了一个cluster多进程模块来利用多核.
PHP比较中庸,计算速度比不上JIT语言,但在非JIT的通用脚步语言中,PHP并不慢,比方说PHP5就已经比Python快,PHP7更是比Python快得多,比如php-src/Zend/bench.php测试中,PHP 7.1的耗时只有5.4的1/4.
另外PHP的PHP-FPM和Apache MOD_PHP这类多进程的FastCGI运行方式,也很容易利用多核.而且还能开启opcache缓存PHP脚本的opcode到共享内存.也就是说,假设你定义了很多函数在functions.php里,opcache缓存该脚本在内存后,其后每次请求PHP都不必重新解析脚本,直接就能执行opcode,性能提升是非常明显的,尤其对于复杂的PHP应用.
昨天看廖雪峰博客中刚好有一章节讲到:计算密集型 vs. IO密集型,摘录如下:
有一个被用烂了的栗子,解释 Node.js 语言
非阻塞
,事件驱动
概念,大约是这样的:在这个栗子中,我们可以简单的理解为:服务员相当于
CPU
,而厨房的工作就是I/O
。显然的,在一个饭店里,服务员的工作并不复杂,”等待上菜的时间“多数是花在”等待厨房“上。这与如今大多数网站的情况相似:网站通常不需要做太多复杂的运算,但需要花费大量的时间等待 I/O 处理,比如数据库查询,比如图片、视频的读取。于是 Node.js 舍弃了传统 Web 服务中“每当一次请求来临,都打开一个线程来单独处理”的做法,而是采用事件驱动的模型,默认情况下,仅用单线程就可以负担相当高的并发量。
于是在这种情境下,我们说:
Node.js 更适合 IO密集型的任务处理
。但如果我们把上面的栗子更换一下,比如说咱们不开饭馆,开银行。每当客户到来,要求取出指定的款项,作为服务员,你需要根据客户的账户等级计算利率,计算利息,计算分红,等等……(随意想到的比方,可能不太恰当),而“取钱并交给客户”这个动作本身却并不复杂。
这时候,就不能指望像饭店那样,只靠一个服务员就能应付大量的客户,因为每个请求都需要独占服务员大量的时间(不可避免的
阻塞
)。那么此时,传统的模型,例如 PHP 或许就变得更加合适了。以上,希望能解你所惑
IO密集型:就是IO比较多的应用,比如网络传输、数据库等调用。web应用大多数是这种
计算密集型:顾名思义就是需要大量的CPU计算的应用类型。像云计算一类的应用应该属于这种。
什么是计算密集?举个例子,把SQLite数据库放到Linux内存文件系统/dev/shm上对100万数据进行SELECT查询操作,那么这个SELECT查询,在使用了B+树索引时,在B+树索引上的二分查找就是典型的密集计算,如果没有使用索引,那单纯的扫描整个表也是密集计算.所以说,像关系数据库这种东西,普遍都是使用C/C++实现,保证性能和控制内存占用.
什么是IO密集?比如SQLite数据库不在内存,而在普通机械磁盘上,这时写操作(INSERT/UPDATE/DELETE)都是典型的IO密集操作,因为这时就算CPU再快,SQLite引擎再快,也会被机械磁盘的写入操作拖慢.所以为了并发,SQLite后来引入了
WAL(write-ahead log)
预写式日志支持,具体配置就是执行一下SQLite查询:WAL机制的原理是: 修改并不直接写入到数据库文件中,而是写入到另外一个称为WAL的文件中(data.db3-wal). 如果事务失败,WAL中的记录会被忽略,撤销修改. 如果事务成功,它将在随后的某个时间(PRAGMA synchronous = NORMAL)被写回到数据库文件中,提交修改. 同步WAL文件和数据库文件的行为被称为checkpoint(检查点),它由SQLite自动执行, 默认是在WAL文件积累到1000页修改的时候(PRAGMA wal_autocheckpoint). 在适当的时候,也可以手动执行checkpoint,SQLite提供了相关的接口,执行 PRAGMA wal_checkpoint 之后,WAL文件会被清空. 在读的时候,SQLite将在WAL文件中搜索,找到最后一个写入点,记住它,并忽略在此之后的写入点(这保证了读写和读读可以并行执行). 随后,它确定所要读的数据所在页是否在WAL文件中,如果在,则读WAL文件中的数据,如果不在,则直接读数据库文件中的数据. 在写的时候,SQLite将之写入到WAL文件中即可,但是必须保证独占写入,因此写与写之间不能并行执行. WAL在实现的过程中,使用了共享内存技术(data.db3-shm),因此,所有的读写进程必须在同一个机器上,否则,无法保证数据一致性.
像WAL和checkpoint这种概念,在其他数据库比如MySQL中也存在,只不过MySQL会更复杂,能支持更大规模的并发写操作.像
WAL+checkpoint
这种写入方式,你就可以看做是一种异步的写入.Node的JS解释器基于Chromium的V8,而V8具有JIT即时编译机制,所以Node的密集计算的性能是要比PHP强的.虽然PHP官方现在也在开发PHP的JIT试验分之,但其性能仍然不如V8.不过就算Node计算性能好,也几乎不会有人推荐在Node里执行大规模的密集计算,因为密集计算耗必定阻塞Node服务,这和Node倡导的无阻塞理念相背.
Node利用JS事件驱动的特性,做到了无阻塞,但基于回调的事件编程方式不利于代码维护.而且Node这种服务不能像Tomcat这类Java服务使用单进程多线程利用多核,而V8也并不是为多线程设计,所以Node官方只能自己搞了一个cluster多进程模块来利用多核.
PHP比较中庸,计算速度比不上JIT语言,但在非JIT的通用脚步语言中,PHP并不慢,比方说PHP5就已经比Python快,PHP7更是比Python快得多,比如php-src/Zend/bench.php测试中,PHP 7.1的耗时只有5.4的1/4.
另外PHP的PHP-FPM和Apache MOD_PHP这类多进程的FastCGI运行方式,也很容易利用多核.而且还能开启opcache缓存PHP脚本的opcode到共享内存.也就是说,假设你定义了很多函数在functions.php里,opcache缓存该脚本在内存后,其后每次请求PHP都不必重新解析脚本,直接就能执行opcode,性能提升是非常明显的,尤其对于复杂的PHP应用.