7

私はこの質問hereからフォローアップしています

私が抱えている問題は、主に文字列である MSMQ からの大きなオブジェクトがいくつかあることです。これらのオブジェクトがラージ オブジェクト ヒープ (LOH) で作成され、したがって断片化されていることにメモリの問題を絞り込みました (プロファイラーの助けを借りて確認しました)。

上に投稿した質問では、主に文字列を文字配列に分割するという形でいくつかの回避策を得ました。

私が直面している問題は、文字列処理の最後に (どのような形式であっても)、その文字列を制御できない別のシステムに送信する必要があることです。そこで、この文字列を LOH に配置する次の解決策を考えていました。

  1. それぞれ 85k 未満の char 配列の配列として表します (LOH に配置されるオブジェクトのしきい値)
  2. 送信側で圧縮し(つまり、ここで説明している受信側のシステムで受信する前に)、サードパーティシステムに渡す前にのみ解凍します。

私が何をするにしても - 何らかの方法で - 文字列は完全でなければなりません (char 配列や圧縮はありません)。

私はここで立ち往生していますか?管理された環境を使用することがここで間違いだったのではないかと考えており、弾丸をかじって C++ のような環境に移行するべきかどうかを考えています。

ありがとう、ヤニス

編集:ここに投稿されたコードに問題を正確に絞り込みました

通過する大きな文字列は LOH に配置されます。メッセージを受け取った時点からすべての処理モジュールを削除しましたが、メモリ消費の傾向は変わりません。

したがって、この WorkContext がシステム間で受け渡される方法を変更する必要があると思います。

4

3 に答える 3

1

StringBuilder(ロープのような実装を使用する 4.0 バージョン) を使用して、値のストリーミングを試すことができます。

この例は、モードで、アタッチされた (CTRL-F5)で実行する必要があります。モードと混乱の両方が GC を使いすぎています。ReleaseStart Without DebuggingDebugStart Debugging

public class SerializableWork
{
    // This is very often between 100-120k bytes. This is actually a String - not just for the purposes of this example
    public String WorkContext { get; set; }

    // This is quite large as well but usually less than 85k bytes. This is actually a String - not just for the purposes of this example
    public String ContextResult { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Initial memory: {0}", GC.GetTotalMemory(true));
        var sw = new SerializableWork { WorkContext = new string(' ', 1000000), ContextResult = new string(' ', 1000000) };
        Console.WriteLine("Memory with objects: {0}", GC.GetTotalMemory(true));

        using (var mq = new MessageQueue(@".\Private$\Test1"))
        {
            mq.Send(sw);
        }

        sw = null;

        Console.WriteLine("Memory after collect: {0}", GC.GetTotalMemory(true));

        using (var mq = new MessageQueue(@".\Private$\Test1"))
        {
            StringBuilder sb1, sb2;

            using (var msg = mq.Receive())
            {
                Console.WriteLine("Memory after receive: {0}", GC.GetTotalMemory(true));

                using (var reader = XmlTextReader.Create(msg.BodyStream))
                {
                    reader.ReadToDescendant("WorkContext");
                    reader.Read();

                    sb1 = ReadContentAsStringBuilder(reader);

                    reader.ReadToFollowing("ContextResult");
                    reader.Read();

                    sb2 = ReadContentAsStringBuilder(reader);

                    Console.WriteLine("Memory after creating sb: {0}", GC.GetTotalMemory(true));
                }
            }

            Console.WriteLine("Memory after freeing mq: {0}", GC.GetTotalMemory(true));

            GC.KeepAlive(sb1);
            GC.KeepAlive(sb2);
        }

        Console.WriteLine("Memory after final collect: {0}", GC.GetTotalMemory(true));
    }

    private static StringBuilder ReadContentAsStringBuilder(XmlReader reader)
    {
        var sb = new StringBuilder();
        char[] buffer = new char[4096];

        int read;

        while ((read = reader.ReadValueChunk(buffer, 0, buffer.Length)) != 0)
        {
            sb.Append(buffer, 0, read);
        }

        return sb;
    }
}

Message.BodyStreamでメッセージを直接読んでXmlReaderから、必要な要素に移動し、次を使用してチャンクでデータを読み取りますXmlReader.ReadValueChunk

最後に、stringオブジェクトを使用する場所はありません。メモリの唯一の大きなブロックはMessage.

于 2011-10-21T08:53:39.653 に答える
1

オプションは、サードパーティのシステムがデータを受信する方法によって異なります。何らかの方法でストリーミングできる場合は、すべてを一度にメモリに入れる必要はありません。その場合、ストリームを解凍してチャンクでサードパーティシステムにパントできるため、圧縮(簡単に圧縮できるデータであれば、ネットワークの負荷におそらく本当に役立つでしょう)は優れています。

もちろん、弦を分割して LoH しきい値を下回る場合も同様です。

そうでない場合でも、MSMQ メッセージのペイロードを分割し、クライアントに送信する前に、事前に割り当てられ再利用されたバイト配列のメモリ プールを使用して再構築することをお勧めします。Microsoft には、使用できる実装がありますhttp://msdn.microsoft.com/en-us/library/system.servicemodel.channels.buffermanager.aspx

私が考えることができる最後のオプションは、C++ のアンマネージ コードで msmq デシリアライズを処理し、placement new を使用して独自のカスタムの大きなブロック メモリ プールを作成し、文字列をデシリアライズすることです。巧妙で動的にしようとするのは難しいのではなく、プールバッファーが可能な限り最長のメッセージに十分であることを確認することで、比較的単純に保つことができます。

于 2011-10-21T00:29:11.360 に答える
0

LargeString以前に割り当てられた文字列を再利用し、それらの小さなコレクションを保持するクラスを実装することもできます (それを と呼びます)。

通常、文字列は不変であるため、すべての変更と新しい代入を、安全でないポインター ジャグリングによって行う必要があります。文字列をレシーバーに渡した後、再利用できるように手動でマークする必要があります。受信者が長すぎるメッセージに対処できない場合や、さまざまな長さの文字列のコレクションがある場合を除き、メッセージの長さが異なることも問題になる可能性があります。

おそらく素晴らしいアイデアではありませんが、C++ ですべてを書き直すよりはましかもしれません。

于 2011-10-17T07:57:06.823 に答える