外部イベントに応答して共有メモリ内の多くのリンクリストを更新する「サーバー」プログラムがあります。クライアントプログラムがリストの更新にできるだけ早く気付くようにしたい(待ち時間が最も短い)。サーバーは、データが入力され、次のポインターが有効な場所に設定されると、リンクリストのノードをマークしstate_
ます。FILLED
それまでstate_
はですNOT_FILLED_YET
。私はメモリバリアを使用して、内部のデータが実際に準備ができている前のようにクライアントに表示state_
されないようにしています(そして、動作しているように見えますが、破損したデータは表示されません)。FILLED
また、state_
コンパイラがクライアントによるループからのチェックを解除しないようにするために、揮発性です。
サーバーコードをまったく同じに保ちながら、クライアントがリンクリストをスキャンして変更を確認するための3つの異なる方法を考え出しました。問題は、なぜ3番目の方法が最速なのかということです。
方法1:すべてのリンクリスト(「チャネル」と呼ばれる)を継続的にラウンドロビンし、ノードが「FILLED」に変更されているかどうかを確認します。
void method_one()
{
std::vector<Data*> channel_cursors;
for(ChannelList::iterator i = channel_list.begin(); i != channel_list.end(); ++i)
{
Data* current_item = static_cast<Data*>(i->get(segment)->tail_.get(segment));
channel_cursors.push_back(current_item);
}
while(true)
{
for(std::size_t i = 0; i < channel_list.size(); ++i)
{
Data* current_item = channel_cursors[i];
ACQUIRE_MEMORY_BARRIER;
if(current_item->state_ == NOT_FILLED_YET) {
continue;
}
log_latency(current_item->tv_sec_, current_item->tv_usec_);
channel_cursors[i] = static_cast<Data*>(current_item->next_.get(segment));
}
}
}
方法1では、チャネル数が少ない場合に遅延が非常に小さくなりました。しかし、チャネル数が増えると(250K以上)、すべてのチャネルをループするため、非常に遅くなりました。だから私は試しました...
方法2:各リンクリストにIDを付けます。別の「更新リスト」を横に置いておきます。リンクリストの1つが更新されるたびに、そのIDを更新リストにプッシュします。ここで、単一の更新リストを監視し、そこから取得したIDを確認する必要があります。
void method_two()
{
std::vector<Data*> channel_cursors;
for(ChannelList::iterator i = channel_list.begin(); i != channel_list.end(); ++i)
{
Data* current_item = static_cast<Data*>(i->get(segment)->tail_.get(segment));
channel_cursors.push_back(current_item);
}
UpdateID* update_cursor = static_cast<UpdateID*>(update_channel.tail_.get(segment));
while(true)
{
ACQUIRE_MEMORY_BARRIER;
if(update_cursor->state_ == NOT_FILLED_YET) {
continue;
}
::uint32_t update_id = update_cursor->list_id_;
Data* current_item = channel_cursors[update_id];
if(current_item->state_ == NOT_FILLED_YET) {
std::cerr << "This should never print." << std::endl; // it doesn't
continue;
}
log_latency(current_item->tv_sec_, current_item->tv_usec_);
channel_cursors[update_id] = static_cast<Data*>(current_item->next_.get(segment));
update_cursor = static_cast<UpdateID*>(update_cursor->next_.get(segment));
}
}
方法2はひどい待ち時間を与えました。方法1では10us未満の遅延が発生する可能性がありますが、方法2では不可解なことに8msの遅延が発生することがよくあります。gettimeofdayを使用すると、update_cursor-> state_の変更が、サーバーのビューからクライアントのビューに伝達するのに非常に時間がかかったようです(私はマルチコアボックスを使用しているため、遅延はキャッシュによるものと思われます)。だから私はハイブリッドアプローチを試しました...
方法3:更新リストを保持します。ただし、すべてのチャネルを継続的にループし、各反復内で更新リストが更新されているかどうかを確認します。ある場合は、番号を押し込んでください。そうでない場合は、現在繰り返しているチャネルを確認してください。
void method_three()
{
std::vector<Data*> channel_cursors;
for(ChannelList::iterator i = channel_list.begin(); i != channel_list.end(); ++i)
{
Data* current_item = static_cast<Data*>(i->get(segment)->tail_.get(segment));
channel_cursors.push_back(current_item);
}
UpdateID* update_cursor = static_cast<UpdateID*>(update_channel.tail_.get(segment));
while(true)
{
for(std::size_t i = 0; i < channel_list.size(); ++i)
{
std::size_t idx = i;
ACQUIRE_MEMORY_BARRIER;
if(update_cursor->state_ != NOT_FILLED_YET) {
//std::cerr << "Found via update" << std::endl;
i--;
idx = update_cursor->list_id_;
update_cursor = static_cast<UpdateID*>(update_cursor->next_.get(segment));
}
Data* current_item = channel_cursors[idx];
ACQUIRE_MEMORY_BARRIER;
if(current_item->state_ == NOT_FILLED_YET) {
continue;
}
found_an_update = true;
log_latency(current_item->tv_sec_, current_item->tv_usec_);
channel_cursors[idx] = static_cast<Data*>(current_item->next_.get(segment));
}
}
}
この方法の遅延は方法1と同じくらい良好でしたが、多数のチャネルにスケーリングされました。問題は、理由がわからないことです。物事にレンチを投げるだけです。「更新で見つかりました」の部分のコメントを外すと、すべてのレイテンシログメッセージの間に出力されます。つまり、更新リストでしか見つからないということです。したがって、この方法が方法2よりもどのように高速になるかわかりません。
テストデータとしてランダムな文字列を生成する完全なコンパイル可能なコード(GCCとboost-1.41が必要)は次の場所にあります:http://pastebin.com/0kuzm3Uf
更新:更新が発生するまで、3つの方法すべてが効果的にスピンロックされます。違いは、更新が行われたことに気付くのにかかる時間です。それらはすべて継続的にプロセッサに負担をかけるため、速度の違いを説明することはできません。私は他に何も実行されていない4コアのマシンでテストしているので、サーバーとクライアントは競合するものがありません。更新が条件を通知し、クライアントに条件を待機させるバージョンのコードを作成しました。これは、どのメソッドのレイテンシーにも役立ちませんでした。
Update2:3つの方法がありますが、一度に試したのは1つだけなので、state_メンバーをめぐって競合しているのは1つのサーバーと1つのクライアントだけです。