前面说到过select/poll/epoll这三个系统调用,我们都知道,Unix/Linux下把所有的设备都当成文件来进行I/O,所以,那三个操作更应该算是I/O相关的系统调用。说到I/O模型,这对于我们的I/O性能相当重要,我们知道,Unix/Linux经典的I/O方式是(关于Linux下的I/O模型,大家可以读一下这篇文章《使用异步I/O大大提高性能》):
第一种,同步阻塞式I/O,这个不说了。
第二种,同步无阻塞方式。其通过fctnl设置O_NONBLOCK来完成。
第三种,对于select/poll/epoll这三个是I/O不阻塞,但是在事件上阻塞,算是:I/O异步,事件同步的调用。
第四种,AIO方式。这种I/O模型是一种处理与I/O并行的模型。I/O请求会立即返回,说明请求已经成功发起了。在后台完成I/O操作时,向应用程序发起通知,通知有两种方式:一种是产生一个信号,另一种是执行一个基于线程的回调函数来完成这次I/O处理过程。
第四种因为没有任何的阻塞,无论是I/O上,还是事件通知上,所以,其可以让你充分地利用CPU,比起第二种同步无阻塞好处就是,第二种要你一遍一遍地去轮询。Nginx之所所以高效,是其使用了epoll和AIO的方式来进行I/O的。
再说一下Windows下的I/O模型:
a)一个是WriteFile系统调用,这个系统调用可以是同步阻塞的,也可以是同步无阻塞的,关于看文件是不是以Overlapped打开的。关于同步无阻塞,需要设置其最后一个参数Overlapped,微软叫Overlapped I/O,你需要WaitForSingleObject才能知道有没有写完成。这个系统调用的性能可想而知。
b)另一个叫WriteFileEx的系统调用,其可以实现异步I/O,并可以让你传入一个callback函数,等I/O结束后回调之,但是这个回调的过程Windows是把callback函数放到了APC(Asynchronous Procedure Calls)的队列中,然后,只用当应用程序当前线程成为可被通知状态(Alterable)时,才会被回调。只有当你的线程使用了这几个函数时WaitForSingleObjectEx,WaitForMultipleObjectsEx, MsgWaitForMultipleObjectsEx,SignalObjectAndWait和SleepEx,线程才会成为Alterable状态。可见,这个模型,还是有wait,所以性能也不高。
c)然后是IOCP–IO Completion Port,IOCP会把I/O的结果放在一个队列中,但是,侦听这个队列的不是主线程,而是专门来干这个事的一个或多个线程去干(老的平台要你自己创建线程,新的平台是你可以创建一个线程池)。IOCP是一个线程池模型。这个和Linux下的AIO模型比较相似,但是实现方式和使用方式完全不一样。
当然,真正提高I/O性能方式是把和外设的I/O的次数降到最低,最好没有,所以,对于读来说,内存cache通常可以从质上提升性能,因为内存比外设快太多了。对于写来说,cache住要写的数据,少写几次,但是cache带来的问题就是实时性的问题,也就是latency会变大,我们需要在写的次数上和相应上做权衡。
B)多核CPU调优
关于CPU的多核技术,我们知道,CPU0是很关键的,如果0号CPU被用得过狠的话,别的CPU性能也会下降,因为CPU0是有调整功能的,所以,我们不能任由操作系统负载均衡,因为我们自己更了解自己的程序,所以,我们可以手动地为其分配CPU核,而不会过多地占用CPU0,或是让我们关键进程和一堆别的进程挤在一起。
- 对于Windows来说,我们可以通过“任务管理器”中的“进程”而中右键菜单中的“设置相关性……”(Set Affinity…)来设置并限制这个进程能被运行在哪些核上。
- 对于Linux来说,可以使用taskset命令来设置(你可以通过安装schedutils来安装这个命令:apt-get install schedutils)
多核CPU还有一个技术叫NUMA技术(Non-Uniform Memory Access)。传统的多核运算是使用SMP(Symmetric Multi-Processor )模式,多个处理器共享一个集中的存储器和I/O总线。于是就会出现一致存储器访问的问题,一致性通常意味着性能问题。NUMA模式下,处理器被划分成多个node,每个node有自己的本地存储器空间。关于NUMA的一些技术细节,你可以查看一下这篇文章《Linux的NUMA技术》,在Linux下,对NUMA调优的命令是:numactl 。如下面的命令:(指定命令“myprogram arg1 arg2”运行在node 0上,其内存分配在node 0 和1上)
numactl --cpubind=0 --membind=0,1 myprogram arg1 arg2
当然,上面这个命令并不好,因为内存跨越了两个node,这非常不好。最好的方式是只让程序访问和自己运行一样的node,如:
numactl --membind 1 --cpunodebind 1 --localalloc myapplication
C)文件系统调优
关于文件系统,因为文件系统也是有cache的,所以,为了让文件系统有最大的性能。首要的事情就是分配足够大的内存,这个非常关键,在Linux下可以使用free命令来查看 free/used/buffers/cached,理想来说,buffers和cached应该有40%左右。然后是一个快速的硬盘控制器,SCSI会好很多。最快的是Intel SSD固态硬盘,速度超快,但是写次数有限。
接下来,我们就可以调优文件系统配置了,对于Linux的Ext3/4来说,几乎在所有情况下都有所帮助的一个参数是关闭文件系统访问时间,在/etc/fstab下看看你的文件系统有没有noatime参数(一般来说应该有),还有一个是dealloc,它可以让系统在最后时刻决定写入文件发生时使用哪个块,可优化这个写入程序。还要注间一下三种日志模式:data=journal、data=ordered和data=writeback。默认设置data=ordered提供性能和防护之间的最佳平衡。
当然,对于这些来说,ext4的默认设置基本上是最佳优化了。
这里介绍一个Linux下的查看I/O的命令——iotop,可以让你看到各进程的磁盘读写的负载情况。
其它还有一些关于NFS、XFS的调优,大家可以上google搜索一些相关优化的文章看看。关于各文件系统,大家可以看一下这篇文章——《Linux日志文件系统及性能分析》。