4

私はストリーミング twitter クライアントで作業しています。1 ~ 2 日間連続して実行した後、メモリ使用量が 1.4 ギガ (32 ビット プロセス) を超えており、その量に達するとすぐにメモリ不足になります。基本的にこれであるコードの例外 (このコードは、私のマシンでは 30 秒以内にエラーになります):

while (true)
{
  Task.Factory.StartNew(() =>
  {
    dynamic dyn2 = new ExpandoObject();

    //get a ton of text, make the string random 
    //enough to be be interned, for the most part
    dyn2.text = Get500kOfText() + Get500kOfText() + DateTime.Now.ToString() + 
      DateTime.Now.Millisecond.ToString(); 
  });
}

私はそれをプロファイリングしましたが、それは間違いなく DLR のクラスがかなり下にあるためです (メモリから - ここには詳細な情報はありません) xxRuntimeBinderxx と xxAggregatexx です。

Eric Lippert (microsoft) からのこの回答は、コード内の何も参照されていないにもかかわらず、GC が実行されない舞台裏でオブジェクトを解析する式を作成していることを示しているようです。

その場合、上記のコードにそれを防止または軽減する方法はありますか?

私のフォールバックは、動的な使用法を排除することですが、そうしないことを好みます。

ありがとう

アップデート:

12/14/12:

答え:

この特定の例でタスクを解放する方法は、yield (Thread.Sleep(0)) でした。これにより、解放されたタスクを GC で処理できるようになります。この特定のケースでは、メッセージ/イベント ループの処理が許可されていなかったと思います。

私が使用していた実際のコード(TPL Dataflow) では、ブロックでComplete()を呼び出していませんでした。これは、ブロックが終わりのないデータフローであることを意図していたためです。タスクは、Twitter が送信する限り Twitter メッセージを受け取ります。このモデルでは、アプリが実行されている限りブロックは決して完了しないため、ブロックのいずれかが完了したことを通知する理由はまったくありませんでした

残念ながら、Dataflow ブロックは、送信されたすべてのものへの参照を実際に保持しているため、非常に長時間実行したり、膨大な数のアイテムを処理したりするように設計されていないようです。私が間違っている場合は、お知らせください。

したがって、回避策は定期的に(メモリ使用量に基づいて-私の場合は100kのTwitterメッセージごとでした)ブロックを解放し、再度設定することです.

このスキームでは、メモリ消費量が 80 メガを超えることはなく、ブロックをリサイクルして適切な測定のために GC を強制した後、gen2 ヒープは 6 メガに戻り、すべてが正常に戻ります。

10/17/12:

  • 「これは何の役にも立たない」 : この例は、単に問題を迅速に生成できるようにするためのものです。問題とは関係のない数百行のコードから要約されています。
  • タスクを作成し、次にオブジェクトを作成する無限ループ」: 覚えておいてください-これは問題を簡単に示しているだけです-実際のコードはそこに座って、さらにストリーミングデータを待っています. また、コードを見ると、すべてのオブジェクトがタスクの Action<> ラムダ内に作成されています。範囲外になった後、(最終的には) クリーンアップされないのはなぜですか? この問題は、実行が速すぎることによるものでもありません。実際のコードでは、メモリ不足の例外に到達するのに 1 日以上かかります。これにより、物事を試すのに十分な速さになります。
  • 「タスクは解放されることが保証されていますか?」オブジェクトはオブジェクトですね。私の理解では、スケジューラはプール内のスレッドを使用しているだけであり、実行中のラムダは、実行が完了した後に破棄されます。
4

2 に答える 2

3

これは、DLR よりも、プロデューサーがコンシューマーよりもはるかに先を行っていることに関係しています。ループはできるだけ早くタスクを作成しますが、タスクは「すぐに」開始されません。どれだけ遅れるかは簡単にわかります。

        int count = 0;

        new Timer(_ => Console.WriteLine(count), 0, 0, 500);

        while (true)
        {
            Interlocked.Increment(ref count);

            Task.Factory.StartNew(() =>
            {
                dynamic dyn2 = new ExpandoObject();
                dyn2.text = Get500kOfText() + Get500kOfText() + DateTime.Now.ToString() +
                  DateTime.Now.Millisecond.ToString();

                Interlocked.Decrement(ref count);
            });
        }

出力:

324080
751802
1074713
1620403
1997559
2431238

これは、3 秒分のスケジューリングに相当します。Task.Factory.StartNew(シングルスレッド実行)を削除すると、安定したメモリが得られます。

ただし、あなたが与えた再現は少し不自然に思えます。同時タスクが多すぎることが実際に問題である場合は、同時スケジューリングを制限するカスタム タスク スケジューラを試すことができます。

于 2012-10-17T18:54:58.880 に答える
1

ここでの問題は、作成しているタスクがクリーンアップされていないことではありません。 Astiは、コードがタスクを処理できるよりも速く作成していることを実証しました。そのため、完了したタスクのメモリをクリアしている間に、最終的には不足してしまいます。

あなたは言った:

この例で戦略的なスリープを設定しても、メモリ不足の例外が生成されます。時間がかかるだけです。

このコード、または同時実行タスクの数を制限する他の例を示していません。私の推測では、あなたは創造をある程度制限していますが、それでも創造の速度は消費の速度よりも速いと思います。これが私自身の制限された例です:

int numConcurrentActions = 100000;
BlockingCollection<Task> tasks = new BlockingCollection<Task>();

Action someAction = () =>
{
    dynamic dyn = new System.Dynamic.ExpandoObject();

    dyn.text = Get500kOfText() + Get500kOfText() 
        + DateTime.Now.ToString() + DateTime.Now.Millisecond.ToString();
};

//add a fixed number of tasks
for (int i = 0; i < numConcurrentActions; i++)
{
    tasks.Add(new Task(someAction));
}

//take a task out, set a continuation to add a new one when it finishes, 
//and then start the task.
foreach (Task t in tasks.GetConsumingEnumerable())
{
    t.ContinueWith(_ =>
    {
        tasks.Add(new Task(someAction));
    });
    t.Start();
}

このコードは、一度に 100,000 を超えるタスクが実行されないようにします。これを実行すると、メモリは安定します(数秒間平均すると)。固定数を作成し、継続を設定して、既存のタスクが終了するたびに新しいタスクをスケジュールすることで、タスクを制限します。

つまり、実際のデータは外部ソースからのフィードに基づいているため、そのフィードからデータを取得する速度が、処理よりもわずかに速いということです。ここにはいくつかのオプションがあります。アイテムが入ってきたらキューに入れ、限られた数だけが現在実行できるようにし、容量を超えた場合はリクエストを破棄します (または、入力をフィルタリングしてすべてを処理しないようにする他の方法を見つけます)。 、または、より優れたハードウェアを入手する (または使用している処理方法を最適化する) ことで、要求を作成するよりも速く処理できるようにすることができます。

通常、人々は既に「十分に高速」に実行されているコードを最適化しようとする傾向があると思いますが、これは明らかに当てはまりません。達成する必要があるかなり難しいベンチマークがあります。アイテムが入ってくるよりも速く処理する必要があります。現在、そのベンチマークを満たしていません (ただし、失敗する前にしばらく実行されるため、それほど遠くないはずです)

于 2012-10-17T20:28:24.417 に答える