2

NVIDIA の CUDA を使用して特定の作業タスクを GPU にオフロードする C# ライブラリに取り組んでいます。この例は、拡張メソッドを使用して 2 つの配列を一緒に追加することです。

float[] a = new float[]{ ... }
float[] b = new float[]{ ... }
float[] c = a.Add(b);

このコードの作業は GPU 上で行われます。ただし、結果が必要な場合にのみCPUブロックでコードが実行されるように、非同期で実行したいと思います(結果がまだGPUで終了していない場合)。これを行うために、非同期実行を隠す ExecutionResult クラスを作成しました。使用中、これは次のようになります。

float[] a = new float[]{ ... }
float[] b = new float[]{ ... }
ExecutionResult res = a.Add(b);
float[] c = res; //Implicit converter

最後の行で、データの準備がまだ完了していない場合、プログラムはブロックされます。スレッドの同期などの経験があまりないため、ExecutionResult クラス内でこのブロッキング動作を実装する最善の方法については確信が持てません。

public class ExecutionResult<T>
{
    private T[] result;
    private long computed = 0;

    internal ExecutionResult(T[] a, T[] b, Action<T[], T[], Action<T[]>> f)
    {
        f(a, b, UpdateData); //Asych call - 'UpdateData' is the callback method
    }

    internal void UpdateData(T[] data)
    {
        if (Interlocked.Read(ref computed) == 0)
        {
            result = data;
            Interlocked.Exchange(ref computed, 1);
        }
    }

    public static implicit operator T[](ExecutionResult<T> r)
    {
        //This is obviously a stupid way to do it
        while (Interlocked.Read(ref r.computed) == 0)
        {
            Thread.Sleep(1);
        }

        return result;
    }
}

コンストラクターに渡されるアクションは、GPU で実際の作業を実行する非同期メソッドです。ネストされた Action は、非同期コールバック メソッドです。

私の主な関心事は、コンバーターで行われる待機を最適/最もエレガントに処理する方法ですが、問題全体を攻撃するためのより適切な方法があるかどうかです。さらに詳しく説明したり説明したりする必要がある場合は、コメントを残してください。

4

4 に答える 4

6

これがあなたが実装しているフレームワークであり、他のコードをどれだけ呼び出しているかは明確ではありませんが、可能な限り.NETの「通常の」非同期パターンに従います。

于 2008-10-31T11:30:54.173 に答える
3

私が見つけたこの問題の解決策は、2 つのことを行う ExecutionResult コンストラクターに関数を渡すことです。実行すると、非同期作業が開始され、さらに、目的の結果を返す別の関数が返されます。

private Func<T[]> getResult;

internal ExecutionResult(T[] a, T[] b, Func<T[], T[], Func<T[]>> asynchBinaryFunction)
{
   getResult = asynchUnaryFunction(a);
}

public static implicit operator T[](ExecutionResult<T> r)
{
    return r.getResult();
}

「getResult」関数は、データが計算されて GPU から取得されるまでブロックされます。これは、CUDA ドライバー API の構造とうまく連携します。

これは非常にクリーンでシンプルなソリューションです。C# では、ローカル スコープにアクセスして匿名関数を作成できるため、ExecutionResult コンストラクターに渡されるメソッドのブロック部分を次のように置き換えるだけで済みます。

    ...

    status = LaunchGrid(func, length);

    //Fetch result
    float[] c = new float[length];
    status = CUDADriver.cuMemcpyDtoH(c, ptrA, byteSize);
    status = Free(ptrA, ptrB);

    return c;
}

なる...

    ...

    status = LaunchGrid(func, length);

    return delegate
    {
        float[] c = new float[length];
        CUDADriver.cuMemcpyDtoH(c, ptrA, byteSize); //Blocks until work is done
        Free(ptrA, ptrB);
        return c;
    };
}
于 2008-11-03T10:25:16.623 に答える
1

Delegate.BeginInvoke通常の/ Delegate.EndInvokehereを使用できなかったのだろうか?そうでない場合は、待機ハンドル ( などManualResetEvent) がオプションになる可能性があります。

using System.Threading;
static class Program {
    static void Main()
    {
        ThreadPool.QueueUserWorkItem(DoWork);

        System.Console.WriteLine("Main: waiting");
        wait.WaitOne();
        System.Console.WriteLine("Main: done");
    }
    static void DoWork(object state)
    {
        System.Console.WriteLine("DoWork: working");
        Thread.Sleep(5000); // simulate work
        System.Console.WriteLine("DoWork: done");
        wait.Set();
    }
    static readonly ManualResetEvent wait = new ManualResetEvent(false);

}

本当に必要な場合は、オブジェクトを使用してこれを行うことができることに注意してください。

using System.Threading;
static class Program {
    static void Main()
    {
        object syncObj = new object();
        lock (syncObj)
        {
            ThreadPool.QueueUserWorkItem(DoWork, syncObj);

            System.Console.WriteLine("Main: waiting");
            Monitor.Wait(syncObj);
            System.Console.WriteLine("Main: done");
        }
    }
    static void DoWork(object syncObj)
    {

        System.Console.WriteLine("DoWork: working");
        Thread.Sleep(5000); // simulate work
        System.Console.WriteLine("DoWork: done");
        lock (syncObj)
        {
            Monitor.Pulse(syncObj);
        }
    }

}
于 2008-10-31T11:30:23.597 に答える
0

cudaThreadSyncronize() または memcpy() を使用すると、Invoke() に適した同期操作を実行できます。

CUDA では、callAsync() / sync() を使用して非同期メモリ転送を要求することもできます。これは、callAsync() を使用した Begin/EndInvoke() に適しています。

于 2009-09-19T23:44:44.130 に答える