什么是计算密集?举个例子,把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查询:
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这种写入方式,你就可以看做是一种异步的写入.
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应用.