トップ > サンプル解説 > 課題3

課題3: sample3.cpp — 5サーバ直列

3週目。5 台のルータを直列に並べ、限られたバッファ総数 (= 20) をどう配分するかでスループットを最大化する。

構成

source
server0
t_ave=25
server1
t=6, buf=4
server2
t=10, buf=4
server3
t=15, buf=4
server4
t=20, buf=4
server5
t=5, buf=4
受信者

server0 は送信源(率 \(1/25\) でパケットを生成)。server1..5 がルータで、それぞれ固有の平均サービス時間 t_ave とバッファサイズ q_max を持つ。

課題のポイント

ソースコード(配布版)

下のテキストエリアには 配布/sample3.cpp の中身がそのまま貼り付けてある。// *** の箇所を埋めて、 「sample3.cpp をダウンロード」で自分の環境に保存できる。

実験手順

  1. 準備 上のテキストエリア(または 配布/sample3.cpp)の // *** を埋める:
    • server::servicedist==3(指数): sample2 と同じロジック
    • main()server1..53 つ目の引数(平均サービス時間 t_ave4 つ目の引数(バッファ数 q_maxを決める
    制約: 使える t_ave は 5, 6, 10, 15, 18, 30(小さいほど高性能)、バッファ総数 \(= 20\)。 順序(どの性能を直列のどこに置くか)も自由。
  2. 実行
    g++ sample3.cpp && ./a.out
    最終行の average rate がスループット。配置を変えて複数回実行し、最大値を探す。
    ヒント: 全体のスループットはボトルネック(一番遅いサーバ)でほぼ決まる。 ただしバッファ不足だとその前段も連鎖的に詰まる。
  3. 確認 ソース先頭の #define TRACE_QUEUE 1 にして 1 回実行すると queue_trace3.csv が出力される。 専用ツール (sample3_viewer.html) で読み込むと:
    • 各サーバの Q_i(t) 時系列(色分け)
    • 累計離脱数(スループットの傾き)
    • 5 サーバのバッファを並べた パイプラインアニメーション(パケットが流れて詰まる様子)
    が見られる。学習ポイント: 数値だけでは分からない「どこで詰まっているか」をアニメで確認し、 次のバッファ配分に反映させる反復プロセスを体験する。

出力形式と HTML との連携

TRIAL:1
440129
400220
TRIAL:2
...
average rate = 0.0400502

各試行ごとに total_packet(全期間)と target_packetBEGIN_TIME 以降のみ)を表示し、最後に平均スループット。

パイプライン可視化(任意)
#define TRACE_QUEUE 1 にすると queue_trace3.csvt, q1..q5, departed 列)が出力される。 sample3_viewer.html で読み込むと: が確認でき、どのルータがボトルネックか視覚的に分かる。

主要な定数

BEGIN_TIME測定開始時刻(例 1,000,000)。これ以前は過渡状態(空から満たされていく途中)なので統計対象外。
END_TIME測定終了時刻(例 11,000,000)。
TRIAL独立試行回数(例 5)。結果は平均される。
DIST_TYPEサービス時間分布。3:指数(sample2 と同じ)。

ブロッキング機構

各ルータにはバッファサイズ q_max があり、キュー長がそれを超えるパケットは入れない。 server::departure では next->arrive() を呼んで次のサーバに入れようとする:

void server::departure(double t_c){
  t_current = t_c;
  if(order == 5){          // 最終サーバ
    total_packet++;
    if(t_current > begin_time) target_packet++;
    queue--;
    ...
  }
  else{
    int result = next->arrive(t_current);   // 次のルータに入れる試み
    if(result == 1){
      queue--;             // 成功: 自分から 1 個減らす
      ...
    }
    else{                  // 失敗(次が満杯): ブロック
      t_finish = next->get_t_finish();
      (*top)->add(this, t_finish);
    }
  }
}

次のサーバが満杯(arrive が 0 を返す)だと、自分のパケットは 動けない。 次のサーバの離脱時刻 t_finish を待って再試行するように、スケジューラに登録し直す。

結果として起きること: 遅いサーバ(t_ave が大きい)の手前が連鎖的に詰まる。 バッファが足りないと、さらに前のサーバもブロックされて系全体が停滞する。

スループットの計算

server5 だけが総離脱数をカウントする。begin_time 以降にカウントされた target_packet を観測時間で割る:

\[ \text{rate} = \frac{\texttt{target\_packet}}{\texttt{END\_TIME} - \texttt{BEGIN\_TIME}} \]

TRIAL 回繰り返して平均を取り、average rate として出力する。

過渡状態を除外する理由

シミュレーション開始直後は全バッファが空で「まだ詰まっていない」状態なので、スループットが一時的に高く見える。 定常状態(各バッファが平均的にある程度埋まった状態)に達してから測定するため、BEGIN_TIME までは捨てる。

埋める箇所 ***

class server server1(1, dist,  6, 4, idum, &top, t_b);
class server server2(2, dist, 10, 4, idum, &top, t_b);
class server server3(3, dist, 15, 4, idum, &top, t_b);
class server server4(4, dist, 20, 4, idum, &top, t_b);
class server server5(5, dist,  5, 4, idum, &top, t_b);

上は既定の例(合計バッファ 20)。配置と値を変えて average rate が最大になる組み合わせを探す。

共通仕様

イベントドリブンと ran2() の仕組みは課題2と共通。詳しい解説は 課題2のページ 参照。

考察のヒント