Linuxファイルの読み書きメカニズムと最適化方式

6452 ワード

本文はLinuxの下のファイルの読み書きのメカニズムだけを討論して、read、fread、cinなどの異なる読取方式の対比に関与しないで、これらの読取方式は本質的にすべてシステムapi readを呼び出して、ただ異なるパッケージをしただけです.以下のすべてのテストはopen,read,writeというシステムapiを使用します.

キャッシュ


キャッシュは、高速デバイスが低速デバイスにアクセスするのに要する平均時間を短縮するためのコンポーネントであり、ファイルの読み書きはコンピュータのメモリとディスクに関連し、メモリの操作速度はディスクよりはるかに大きい.readを呼び出すたびにwriteがディスクを直接操作すると、速度が制限される一方で、ディスクの使用寿命が低下するため、ディスクの読み書き操作にかかわらず、オペレーティングシステムはデータをキャッシュする

Page Cache


Page Cache(Page Cache)はメモリとファイルの間にあるバッファであり、実際にはメモリ領域でもあり、すべてのファイルIO(ネットワークファイルを含む)は直接ページキャッシュとインタラクティブであり、オペレーティングシステムはinode、address_などの一連のデータ構造を通じてspace,struct pageは,1つのファイルをページにマッピングするレベルを実現し,これらの具体的なデータ構造との関係はしばらく議論しないが,ページキャッシュの存在とファイルIOにおいて重要な役割を果たしていることを知るだけで,ファイルの読み書きの最適化はページキャッシュの使用に対する最適化である.

Dirty Page


ページキャッシュは、対応するファイル内の領域をキャッシュします.ページキャッシュと対応するファイル領域の内容が一致しない場合は、そのページキャッシュをダーティページ(Dirty Page)と呼びます.ページキャッシュを変更するか、新しいページキャッシュを作成します.ディスクをブラシしない限り、汚れたページが発生します.

ページ・キャッシュ・サイズの表示


linuxでは、freeコマンドであるページキャッシュサイズを表示する方法が2つあります.
 $ free
             total       used       free     shared    buffers     cached
Mem:      20470840    1973416   18497424        164     270208    1202864
-/+ buffers/cache:     500344   19970496
Swap:            0          0          0

cachedの列はページキャッシュサイズ、単位Byteです
もう1つは/proc/meminfoを直接表示することです.ここでは2つのフィールドに注目します.
Cached:          1202872 kB
Dirty:                52 kB

Cachedはページキャッシュサイズ、Dirtyはダーティページサイズ

ダーティページの書き込みパラメータ


Linuxには、オペレーティングシステムの汚れたページの書き込み動作を変更するパラメータがあります.
 $ sysctl -a 2>/dev/null | grep dirty
vm.dirty_background_ratio = 10
vm.dirty_background_bytes = 0
vm.dirty_ratio = 20
vm.dirty_bytes = 0
vm.dirty_writeback_centisecs = 500
vm.dirty_expire_centisecs = 3000

vm.dirty_background_ratioはメモリが汚れたページを埋める割合であり、汚れたページの合計サイズがこの割合に達すると、システムのバックグラウンドプロセスは汚れたページブラシディスク(vm.dirty_background_bytesと類似しており、バイト数で設定されているにすぎない)を開始します.
vm.dirty_ratioは絶対的な汚いデータ制限であり、メモリ内の汚いデータの割合はこの値を超えてはいけない.汚れたデータがこの数を超えると、新しいIOリクエストは、汚れたデータがディスクに書き込まれるまでブロックされます.
vm.dirty_writeback_centisecsはどれくらいの時間に汚いデータを書き戻す操作をするかを指定します.単位は1秒です.
vm.dirty_expire_centisecsは、汚れたデータが生存できる時間を1秒単位で指定します.たとえば、ここでは30秒に設定し、オペレーティングシステムが書き込みを行う場合、汚れたデータがメモリに30秒を超えるとディスクに書き込まれます.
これらのパラメータは、sudo sysctl -w vm.dirty_background_ratio=5のようなコマンドで変更することができ、root権限が必要であり、rootユーザの下でecho 5 > /proc/sys/vm/dirty_background_ratioを実行して変更することもできる.

ファイル読み書きプロセス


ページキャッシュと汚いページの概念ができたら、ファイルの読み書きの流れを見てみましょう.

ファイルを読む

  • ユーザはread操作
  • を開始する.
  • オペレーティングシステムページキャッシュの検索
  • ヒットしていない場合、ページ欠落異常が発生し、ページキャッシュを作成し、対応するページ埋め込みページキャッシュ
  • をディスクから読み出す.
  • がヒットすると、ページキャッシュから直接読み出すコンテンツ
  • が戻る.
  • ユーザread呼び出し完了
  • ファイルを書く

  • ユーザがwrite操作
  • を開始する.
  • オペレーティングシステムページキャッシュの検索
  • ヒットしていないと、ページ欠落異常が発生し、ページキャッシュが作成され、ユーザから入力コンテンツがページキャッシュ
  • に書き込まれる.
  • ヒットすると、ユーザからのコンテンツがページキャッシュ
  • に直接書き込まれる.
  • ユーザwrite呼び出し完了
  • ページが変更されて汚れたページになり、オペレーティングシステムは汚れたページをディスクに書き込む2つのメカニズムがあります.
  • ユーザ手動でfsync()
  • を呼び出す
  • pdflushプロセスのタイミングで汚れたページをディスク
  • に書き込む.

    ページキャッシュとディスクファイルには対応関係があります.この関係はオペレーティングシステムによって維持され、ページキャッシュの読み書き操作はカーネル状態で完了し、ユーザーにとって透明です.

    ファイルの読み書きの最適化の考え方


    異なる最適化スキームは、ファイルサイズ、読み書き頻度など、異なる使用シーンに適応しています.ここでは、システムパラメータを変更するスキームを考慮しません.システムパラメータを変更するには、常に損失があり、バランスポイントを選択する必要があります.これは、データの強い一貫性が要求されているかどうか、データの損失を許容しているかどうかなど、ビジネス関連度が高すぎます.最適化の考え方には以下の2つの考慮点がある.
  • 最大利用ページキャッシュ
  • システムapi呼び出し回数
  • を減少する.
    1つ目は、IO操作のたびにページキャッシュにヒットさせることが理解しやすいことです.これは操作ディスクよりもずっと速くなります.2つ目のシステムapiは主にreadとwriteです.システム呼び出しはユーザー状態からカーネル状態に入り、メモリデータのコピーも伴うため、システム呼び出しを減らすとパフォーマンスが向上する場合があります.

    readahead


    readaheadは、オペレーティングシステムがファイル内容をページキャッシュにプリリードし、すぐに戻るようにトリガーする非ブロックシステム呼び出しです.関数のプロトタイプは次のとおりです.
    ssize_t readahead(int fd, off64_t offset, size_t count);
    

    通常、readaheadを呼び出すとすぐにreadを呼び出すのは読み取り速度を向上させることはありません.私たちは通常、一括読み取りまたは読み取りの前に一定時間readaheadを呼び出します.次のようなシーンを仮定すると、1000個の1 Mのファイルを連続的に読み取る必要があります.次の2つのスキームがあります.擬似コードは次のようになります.
    read関数を直接呼び出す
    char* buf = (char*)malloc(10*1024*1024);
    for (int i = 0; i < 1000; ++i)
    {
        int fd = open_file();
        int size = stat_file_size();
        read(fd, buf, size);
        // do something with buf
        close(fd);
    }
    

    readaheadを一括呼び出してからreadを呼び出す
    int* fds = (int*)malloc(sizeof(int)*1000);
    int* fd_size = (int*)malloc(sizeof(int)*1000);
    for (int i = 0; i < 1000; ++i)
    {
        int fd = open_file();
        int size = stat_file_size();
        readahead(fd, 0, size);
        fds[i] = fd;
        fd_size[i] = size;
    }
    char* buf = (char*)malloc(10*1024*1024);
    for (int i = 0; i < 1000; ++i)
    {
        read(fds[i], buf, fd_size[i]);
        // do something with buf
        close(fds[i]);
    }
    

    興味のあるのはコードを書いて実際にテストすることができて、注意しなければならないのはテストする前にまず汚いページと空のページのキャッシュを書いて、以下のコマンドを実行しなければなりません
    sync && sudo sysctl -w vm.drop_caches=3
    

    /proc/meminfoのCachedおよびDirtyアイテムを確認して有効かどうかを確認できます
    テストにより、第2の方法は第1の読取速度より約10%-20%向上し、このシーンでは大量にreadaheadを実行した後、すぐにreadを実行し、最適化空間は限られており、readの前にreadaheadを呼び出すシーンがあれば、read自体の読取速度を大幅に向上させることが分かった.
    このスキームは実際にはオペレーティングシステムのページキャッシュを利用している.すなわち、オペレーティングシステムがファイルをページキャッシュに読み込むことを事前にトリガし、オペレーティングシステムが欠ページ処理、キャッシュヒット、キャッシュ淘汰に対して完備したメカニズムを利用している.ユーザーも自分のデータに対してキャッシュ管理を行うことができるが、ページキャッシュを直接使用するよりも大きな違いはなく、メンテナンスコストを増加させる.

    mmap


    mmapはメモリマッピングファイルの方法であり、1つのファイルまたは他のオブジェクトをプロセスのアドレス空間にマッピングし、ファイルディスクアドレスとプロセス仮想アドレス空間の仮想アドレスの1対1のマッピング関係を実現する.関数の原型は以下の通りである.
    void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    

    このようなマッピング関係を実現すると、プロセスはポインタでこのメモリを読み書き操作することができ、システムは自動的に汚れたページを対応するファイルディスクに書き戻すことができ、read、writeなどのシステム呼び出し関数を呼び出す必要がなく、ファイルに対する操作を完了する.下図のように
    mmapはread、writeなどのシステム呼び出しを減らすほか、メモリのコピー回数を減らすことができます.例えば、read呼び出しの場合、オペレーティングシステムがディスクファイルをページキャッシュに読み込み、ページキャッシュからreadが伝達するbufferにデータをコピーします.mmapを使用すると、オペレーティングシステムはディスクをページキャッシュに読み込むだけで、ユーザーはポインタでmmapマッピングのメモリを直接操作することができます.カーネル状態からユーザー状態へのデータコピーを削減
    mmapは、64 Mのファイルにインデックス情報が格納されているなど、同じ領域を頻繁に読み書きする場合に適しています.頻繁に変更してディスクに永続化する必要があります.これにより、mmapを介してユーザーの仮想メモリにファイルをマッピングし、ポインタでメモリ領域を変更し、オペレーティングシステムによって自動的に変更された部分をディスクに戻すことができます.また、msync手動でディスクをブラシすることもできます.