zoukankan      html  css  js  c++  java
  • IO流程及优化

    http://blog.csdn.net/xypzwl/article/details/51416883

    一、存储设备的存储原理

    机械硬盘:

             机械硬盘使用磁性物质作为存储介质,用N、S极性来代表0或1;

             以无磁性的盘片作为基片(一般材质为铝合金或者玻璃),磁性物质在盘片上以同心圆的方式排列,这些同心圆的圆周被称为磁道;

    磁道又被细分成一个个扇区,作为读写的最小单位(即就算需要读写的数据小于一个扇区,硬盘在实际读写时也会读取一个扇区的数据)。一般一个扇区的大小为512字节(1.非固定,现在也有4K大小的扇区;2.也叫做物理扇区,由硬盘的物理结构决定,无法更改);每个扇区都有一个从1开始的唯一的编号。

             数据的读写是有磁头来进行的,硬盘使用时,盘片在高速旋转而磁头保持不动,由磁盘控制器控制磁头的半径依次遍历所有的扇区,从而达到读写数据的功能;

    例:

    假设硬盘有十个磁道,每次磁道上有十个扇区;现在需要读取第五磁道的第五扇区上的数据:

                       Step1.外部设备告知磁盘控制器数据的位置,第五磁道第五扇区;磁头由磁盘控制器控制半径大小从第1磁道开始依次经由第2、3、4磁道,最终到达第五磁道;

                       Step2.磁头保持半径不动,等待第五扇区旋转到磁头下方;

                       Step3.第五扇区已经到达磁头下方了,磁头开始读取第五扇区内的数据;并将数据发送给外部设备;

    固态硬盘(SSD):

             不了解,以后补充

    U盘(flash存储设备):

             不了解,以后补充

    IO写入的代码表示:

    [cpp] view plain copy
     
    1. #define SECTOR_SIZE     //物理扇区大小,常见为512B,也有4KB的  
    2. #define TOTAL_SIZE      //硬盘的总大小  
    3.   
    4. int Disk_write(int offset, void *data, int size)  
    5. {  
    6.     static int pos = 0;  
    7.   
    8.     int count = 0;  
    9.     int ret = -1;  
    10.     int startOffset, remain;  
    11.     char buf[SECTOR_SIZE] = {0};  
    12.   
    13.     while(1)  
    14.     {  
    15.         startOffset = offset / SECTOR_SIZE;  
    16.         remain = offset % SECTOR_SIZE;  
    17.   
    18.         if (startOffset == pos)  
    19.         {  
    20.             memcpy(buf + remain, data + count, MIN(SECTOR_SIZE - remain, size - count));  
    21.             count += MIN(SECTOR_SIZE - remain, size - count);  
    22.             offset += SECTOR_SIZE - remain;  
    23.             ret = write(buf, SECTOR_SIZE);  //是SECTOR_SIZE没错,每次写入一定是整数个扇区  
    24.             if (ret < 0 || count >= size)  
    25.             {  
    26.                 return count;  
    27.             }  
    28.         }  
    29.   
    30.         pos += SECTOR_SIZE;  
    31.         pos %= TOTAL_SIZE;  
    32.   
    33.         sleep(10);  //表示寻道/写惩罚消耗的时间  
    34.     }  
    35. }  

    二、IO流程:

    这里针对的是基于操作系统之上的应用程序的I/O流程。大致的I/O流程如下图:

    如上图显示,往硬盘写数据需要经过四个过程:

    Step1: 应用程序发起IO调用(如fwrite/fread),要写入的数据被复制到应用层缓冲区中,在应用层缓冲区中数据会被重新组合,使得传递给下一步的数据是对齐的。(这一步的操作时非必需的,当调用库函数fwrite/fread时,会有这一层的缓冲,但如果是直接调用的系统函数write/read,将直接跳到下一步)

    Step2:系统由用户态切换到系统态,数据被写入到系统缓冲区,这一步完成后write函数就认为写操作已经完成了,返回写数据结果(选择同步IO除外,选择同步IO的话会等到step4完成后才会返回)。

    Step3:系统缓冲区内的数据被复制到操作系统page里面进行进一步的组合

    Step4:数据被写入到硬盘的cache中,等待合适的时机将数据写到硬盘的指定位置;

    Step5:硬盘的磁头到达指定的位置,将数据写入到硬盘中;只有当这一步完成时,数据才是真正的被保存下去了,如果在这一步完成之前出现异常,数据将会丢失。

    在整个写数据流程中,之所以需要这么多的缓冲和数据复制,其原因是1.硬盘的读写速度相较于cpu的计算速度来说太慢了,需要缓冲区来缓冲数据,保证数据不会丢失;2.加快硬盘写入速度,通过这些缓冲,可以对应用层的读写操作进行优化合并,以减少不必要的读写操作,从而加快速度;

    三、I/O优化:

    I/O的最大瓶颈在于往存储设备里真正写数据的时候(上面I/O流程中的step4),这也是为什么应用数据进行I/O要经过多重缓冲的原因;

    回顾下存储设备IO的流程:

    1.      将磁头移到到指定的磁道(寻道);

    2.      等待指定的扇区转到磁头下方(等待);

    3.      真实IO操作;

    (注:这是机械硬盘的流程,SSD和U盘没有寻道操作)

    实际IO的速度有存储设备本身物理特性决定的,无法优化;但我们可以优化寻道和等待的时间(最理想的情况下是硬盘不需要寻道,也不需要等待),我们可以通过以下操作降低寻道和等待消耗的时间:

    1.      按顺序读取数据;这样就不需要寻道和等待了。

    2.      数据尽可能一次性读写,减少文件碎片;文件碎片就是一个文件在存储设备上存放时不是连续存放的,假设一个文件2K大小,可能它的前512字节存放在扇区1,第二个512字节存放在扇区10,最后1K存放在扇区5。这样虽然在逻辑上读取文件时没有seek操作,但实际上IO时还是需要等待的。

    大部分的文件系统是先到先得的原则,因此一次性写入可以极大的降低文件碎片的情况。

    3.      字节对齐;硬盘IO的最小单位为扇区,即使需要IO的数据小于一个扇区,实际IO时也是对一个扇区进行操作的。eg:假设一个扇区为512字节,现在需要分两次一共写入1K的数据,这1K数据存放在两个连续的扇区,扇区1和扇区2中。

    (1)如果每次写512字节,整个IO消耗的时间就是两次真实IO的时间;

    (2)而如果不对齐写,第一次写511个字节,第二次写513个字节;由于硬盘一次必须写入512字节,因此第一次IO时扇区1中写入了512字节,最后一个字节是无效数据,第二次IO时磁头已经偏移到扇区2了,但控制器发现第512个字节是在扇区1里面的,所以控制器会等到扇区1再次出现在磁头下面后才开始第二次IO,并且这次IO实际上只有一个字节是有效的;写完后还需要对扇区2再进行一次IO操作。因此整个IO消耗的时间等于三次真实IO的时间加上等待盘片转完一遍的时间(U盘的话还存在一个写惩罚,即每次IO时都要先将原来的数据读取出来,然后擦除数据,最后再将新数据写入进去,这也需要时间)

    IO过程中,数据在这些缓冲区内进行复制也要消耗时间和性能的,字节对齐后,这些复制操作可视情况去掉(通过open时传入的参数进行修改),这里又可以节省点时间

    4.      减少不必要的IO同步操作;一般IO时,操作系统、磁盘控制器等都会对传下来的数据进行优化,比如将多次write合并成一次,调整IO顺序,数据对齐等操作;而IO同步时不经过这些优化操作,一旦传下来的数据诸如字节未对齐,多次IO等会极大影响IO的性能。

    5.      减少不必要的文件开关操作;大部分的文件系统中,文件的属性数据和文件的真实数据是分开存放的,open一个文件时,操作系统要先找到文件的属性数据将这些数据载入到内存,然后根据属性数据再偏移到文件真实数据存放的扇区对数据进行操作;close时一般操作系统会强制刷新数据到存储设备,然后再偏移到文件的属性数据存放的扇区更新属性信息,同样也会强制刷新数据;

    6.      减少调用IO的频率,能一次写完的数据不要分成多次;这一点其实跟IO关系不大,主要是linux的关系;linux系统分为用户态和系统态(或叫内核态);一般的操作都是在用户态下进行的,而一些系统调用需要先切换到系统态才能执行,切换时需要将当前环境压入到寄存器,操作完成后再从寄存器中读取环境信息来进行恢复;减少IO的调用次数可以减少系统状态切换的开销。

  • 相关阅读:
    169. Majority Element
    283. Move Zeroes
    1331. Rank Transform of an Array
    566. Reshape the Matrix
    985. Sum of Even Numbers After Queries
    1185. Day of the Week
    867. Transpose Matrix
    1217. Play with Chips
    766. Toeplitz Matrix
    1413. Minimum Value to Get Positive Step by Step Sum
  • 原文地址:https://www.cnblogs.com/tcicy/p/8454718.html
Copyright © 2011-2022 走看看