目次
序文
こんにちは、皆さん初めまして!今日は IO について紹介します。IO は、周辺機器にデータを表示したり、ホスト A からホスト B にデータを送信したりするなど、コンピューターで非常に一般的な操作です。パフォーマンスを向上させるためには、IO 時間を短縮することが重要になります。人々の関心が高まっているトピックですが、今日は IO 効率を向上させる方法を紹介します。
1. 効率的な IO を理解する方法
IO: 本質的には、あるパーティから別のパーティにデータをコピーすることです。たとえば、read/recv を呼び出してデータを読み取ることは、本質的にはバッファ内のデータをコピーすることです! しかし、データをコピーするための前提条件はデータがあることであるため、IOこのプロセスは 2 つの部分で構成されており、1 つの部分はデータの準備ができるまで待機し、もう 1 つの部分はデータをコピーします。データのコピー効率はハードウェア自体によって決まるため、効率的な IO を実現するには待ち時間を短縮する必要があります。
2. 5つのIOモデル
イラスト: 釣りの例:
張三は川のほとりで動かずに座って、魚が餌を食べるのを待ちました。
李斯は釣り竿を川のほとりに置き、他のことをして、しばらくして魚がいないか確認しました。呉在宇、
竿に鈴を付けたり、釣り竿を川辺に置いたり、その他のことをして、鈴が鳴ったら魚が餌を取ったという意味なので、魚を釣り上げます。趙劉さんは一群の釣り竿を持ってきて、それを
川のほとりに置き、魚が餌を食べたかどうかを調べました
。魚が釣れたので、Xiao LiuはTian Qiに通知し、Tian Qiに魚を持ち帰るように頼みました。
このうち、Zhang San はブロッキング IO、
Li Si はノンブロッキング IO、
Wang Wu はシグナルドリブンIO
、Zhao Liu はマルチチャネル スイッチング/多重化IO
、Tian Qi は非同期 IO に対応します。 、Zhang San、Li Si、および Wang Wu がいます 効率に違いはありません!きちんと
した観点から見ると、Li Si と Wang Wu は釣り中に他のことを行うことができます。
データをコピーする前に送信されると、基本的に待機状態になります。
4 つのメソッド、誰もが釣りを待っている -> 同期 IO に属する
5 つ目のメソッドは、IO ステージのどのステージにも参加しない -> 非同期 IO に属する
ブロッキング IO とノンブロッキング IO の違い:
類似点: 両方プロセスデータ コピーの違い
: 待機方法の違い
ブロッキング IO モデル:
システム コールは、カーネルがデータの準備ができるまで待機します。デフォルトでは、すべてのソケットがブロックされています。
ノンブロッキング IO モデル:
カーネルがデータを準備していない場合でも、システム コールは直接戻り、EWOULDBLOCK エラー コードを返します。
ノンブロッキング IO では、多くの場合、プログラマがループ内でファイル記述子の読み取りと書き込みを繰り返し試行する必要があります。このプロセスはポーリングと呼ばれます。これは CPU にとって非常に重要ですが、非常に無駄なため、通常は特定のシナリオでのみ使用されます。
信号駆動型 IO モデル:
カーネルはデータを準備するときに、SIGIO 信号を使用してアプリケーションに IO 操作を実行するように通知します。
IO多重化モデル:
フローチャートから見ると IO をブロックすることに似ていますが、実際の核心は、IO 多重化が
複数のファイル記述子の準備完了ステータスを同時に待機できることです。
非同期 IO:
カーネルは、データのコピーが完了するとアプリケーションに通知します (また、シグナル ドライバーは、データのコピーを開始できる時期をアプリケーションに通知します)。
要約:
どの IO プロセスにも 2 つのステップが含まれています。最初のステップは待機であり、2 番目のステップはコピーです。そして、実際のアプリケーション シナリオでは、待機に費やされる時間は、コピーに費やされる時間よりもはるかに長いことがよくあります。IO をより効率的にする、最高のコア待ち時間をできるだけ少なくする方法です。
3. ノンブロッキング IO
ファイル記述子はデフォルトで IO をブロックします。
fcntl() 関数のプロトタイプは次のとおりです。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
渡される cmd の値が異なり、後から追加されるパラメーターも異なります
。fcntl 関数には 5 つの関数があります。
既存の記述子のコピー (cmd=F_DUPFD)
ファイル記述子フラグの取得/設定 (cmd=F_GETFD または F_SETFD)
ファイル状態フラグの取得/設定 (cmd=F_GETFL または F_SETFL)
非同期 I/O 所有権の取得/設定 (cmd =F_GETOWN または F_SETOWN)
レコード ロックの取得/設定 (cmd=F_GETLK、F_SETLK または F_SETLKW)
ここでは、ファイル ステータス マークを取得/設定する 3 番目の関数のみを使用し、ファイル記述子を非ブロック
SetNoBlock 実装関数は
fcntl をベースとしており、ファイル記述子をノンブロッキングに設定する SetNoBlock 関数を実装しています。
void SetNoBlock(int fd) {
int fl = fcntl(fd, F_GETFL);
if (fl < 0) {
perror("fcntl");
return;
}
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
F_GETFL を使用して、現在のファイル記述子の属性 (これはビットマップです) を取得します。
次に、F_SETFL を使用してファイル記述子を元に戻します。元に戻す場合は、O_NONBLOCK パラメータを追加します。
4. ノンブロッキングコードの作成
説明: ユーザーはデータをバッファに入力し、データを読み取ってモニターに出力します。モニターは本質的にファイルであり、対応するファイル記述子は 0 です。このとき、ファイル記述子番号に対応するファイルは 0 です。 0を指定するとノンブロッキングとなり、データがある場合はデータを読み込み、データがない場合はブロック待ちせずに他の業務を処理できます。
main.cc
#include"util.hpp"
#include<functional>
#include<vector>
using func_t = std::function<void()>;
#define INIT(v) do {\
v.push_back(PrintLog);\
v.push_back(Download);\
}while(0)
#define callback(cal) do{\
for(auto& e: cal) e();\
}while(0);
int main()
{
std::vector<func_t> cbs;
INIT(cbs);
setNoBlock(0);
while(true) {
char buf[1024];
printf(">>> ");
fflush(stdout);
int ret = read(0,buf,sizeof(buf)-1);
if(ret == 0) {
std::cout<< "read end" << std::endl;
break;
}
else if(ret > 0){
buf[ret-1] = 0;
std::cout << "echo# " << buf << std::endl;
}
else {
//不输入的时候,底层没有数据,不算错误,只不过是以错误的形式返回了
//如何区分是真的错了还是没有数据
//EAGAIN 和 EWOULDBLOCK 都表示没有数据
if(errno == EAGAIN || errno == EWOULDBLOCK) {
std::cout << "没有数据" << std::endl;
callback(cbs);
}
else if(errno == EINTR) continue;
else {
//真的错了
std::cout << ret << "errno: "<< strerror(errno) << std::endl;
break;
}
}
sleep(1);
}
return 0;
}
util.hpp:
#include<iostream>
#include<cstring>
#include<errno.h>
#include<unistd.h>
#include<fcntl.h>
void setNoBlock(int fd) {
int f1 = fcntl(fd,F_GETFL);
if(f1 < 0) std::cerr<< "fcntl fail: " << strerror(errno) << std::endl;
fcntl(fd,F_SETFL,f1 | O_NONBLOCK);
}
void PrintLog() {
std::cout << "this is a LOG" << std::endl;
}
void Download() {
std::cout << "this is a Download" << std::endl;
}
実行中のスクリーンショット:
要約する
この記事を読むと、効率的な IO を実現するには待ち時間を短縮する必要があることが必ず理解できると思います。この点については、IO マルチチャネル スイッチング方式が一般的であり、IO マルチチャネル スイッチングには、select モデル、poll モデル、epoll モデルがあり、これら 3 つのモデルについては、次の記事で 1 つずつ紹介します。以上、今日ご紹介した内容です。