再利用

スケジューラーだけでなく、タスクの割り当てと解除もバイパスすることができます。このような機会は、スケジューラーのバイパスを行う再帰タスクで頻繁に発生します。「スケジューラーのバイパス」セクションの例について考えてみます。継続タスク "c" を作成した後、次のステップを行います。

  1. 子タスク "a" を作成します。

  2. 子タスク "b" を作成します。

  3. execute() メソッドからタスク "a" にポインターを返します。

  4. 親タスクを破棄します。

親をタスク "a" として再利用することで、ステップ 1 と 4 で行われるタスクの作成と破棄を回避できます。さらに、多くの場合、ステップ 1 で親から状態をコピーします。親をタスク "a" として再利用することで、コピーのオーバーヘッドもなくすことができます。

次のコードは、スケジューラーのバイパスの例で、再利用を実装するために必要な変更を示しています。

struct FibTask: public task {
    long n;
    long* sum;
    ...
    task* execute() {
        if( n<CutOff ) {
            *sum = SerialFib(n);
            return NULL;
        } else {
            FibContinuation& c = 
                *new( allocate_continuation() ) FibContinuation(sum);
            // 変更前:	FibTask& a = *new( c.allocate_child() ) FibTask(n-2,&c.x);
            FibTask& b = *new( c.allocate_child() ) FibTask(n-1,&c.y);
            recycle_as_child_of(c);
            n -= 2;
            sum = &c.x;
            // ref_count を "子タスクの数 2" に設定
            c.set_ref_count(2);
            spawn( b );
            // 変更前:	return &a;
            return this;
        }
    }
};

変更前に a として定義されていた子は、現在は再利用された this です。recycle_as_child_of(c) を呼び出すと、次のような処理が行われます。

参照カウントにおける問題を防ぐため、recycle_as_child_of には this が NULL サクセサーを含むという前提条件があります。これは、allocate_continuation が発生した後の場合です。次の図は、allocate_continuationrecycle_as_child_of がタスクグラフをどのように変換するかを示しています。

allocate_continuationrecycle_as_child_of の動作

再利用時には、オリジナルのタスクフィールドがタスクの実行を開始した後に使用されないようにする必要があります。この例では、スケジューラーのバイパスを使用してこの処理を行っています。生成した後、どのフィールドも使用されない場合に限り、再利用されたタスクを代わりに生成できます。タスクが生成された後、タスクは親の処理が進む前に実行され、破棄される可能性があるため、この制限は、const フィールドにも適用されます。

同様のメソッド、task::recycle_as_continuation() は、タスクを子としてではなく継続として再利用します。

関連情報