2

サービス間でログ メッセージを送信するために protobuf-net を使用しています。ストレス テストをプロファイリングすると、高い同時実行性の下で CPU 使用率が非常に高くなり、RuntimeTypeModel の TakeLock が原因であることがわかります。ホット コール スタックは次のようになります。

*Our code...*
ProtoBuf.Serializer.SerializeWithLengthPrefix(class System.IO.Stream,!!0,valuetype ProtoBuf.PrefixStyle)
ProtoBuf.Serializer.SerializeWithLengthPrefix(class System.IO.Stream,!!0,valuetype ProtoBuf.PrefixStyle,int32)
ProtoBuf.Meta.TypeModel.SerializeWithLengthPrefix(class System.IO.Stream,object,class System.Type,valuetype ProtoBuf.PrefixStyle,int32)
ProtoBuf.Meta.TypeModel.SerializeWithLengthPrefix(class System.IO.Stream,object,class System.Type,valuetype ProtoBuf.PrefixStyle,int32,class ProtoBuf.SerializationContext)
ProtoBuf.ProtoWriter.WriteObject(object,int32,class ProtoBuf.ProtoWriter,valuetype  ProtoBuf.PrefixStyle,int32)
ProtoBuf.BclHelpers.WriteNetObject(object,class ProtoBuf.ProtoWriter,int32,valuetype 
ProtoBuf.BclHelpers/NetObjectOptions)
ProtoBuf.Meta.TypeModel.GetKey(class System.Type&)
ProtoBuf.Meta.RuntimeTypeModel.GetKey(class System.Type,bool,bool)
ProtoBuf.Meta.RuntimeTypeModel.FindOrAddAuto(class System.Type,bool,bool,bool)
ProtoBuf.Meta.RuntimeTypeModel.TakeLock(int32&)
[clr.dll]

新しいプリコンパイラを使用して速度を向上できることがわかりましたが、それで問題が解決するかどうか疑問に思っています (リフレクションを使用していないように聞こえます)。これを統合するのは少し面倒なので、まだテストしていません。Serializer.PrepareSerializer を呼び出すオプションも表示されます。私の最初の(小規模な)テストでは、準備が有望に見えませんでした。

シリアライズしている型に関するもう少しの情報:

[ProtoContract]
public class SomeMessage
{
    [ProtoMember(1)]
    public SomeEnumType SomeEnum { get; set; }

    [ProtoMember(2)]
    public long SomeId{ get; set; }

    [ProtoMember(3)]
    public string SomeString{ get; set; }

    [ProtoMember(4)]
    public DateTime SomeDate { get; set; }

    [ProtoMember(5, DynamicType = true, OverwriteList = true)]
    public Collection<object> SomeArguments
}

ご協力いただきありがとうございます!

9/17更新

ご返信ありがとうございます!ご提案の回避策を試して、問題が解決するかどうかを確認します。

このコードはロギング システムに存在するため、SomeMessage の例では、SomeString は実際にはフォーマット文字列 (例: "Hello {0}") であり、SomeArguments コレクションは、String と同様に、フォーマット文字列を埋めるために使用されるオブジェクトのリストです。フォーマット。シリアル化する前に、各引数を調べて を呼び出しますDynamicSerializer.IsKnownType(argument.GetType())。不明な場合は、最初に文字列に変換します。データの比率は見ていませんが、引数としてさまざまな文字列が多数入っていることは確かです。

これが役立つかどうか教えてください。必要があれば、詳細を調べてみます。

4

1 に答える 1

3

TakeLockモデルを変更する場合にのみ使用されます。たとえば、タイプが初めて表示されるためです。TakeLock通常、特定のタイプが最初に使用された後は表示されません。ほとんどの場合、usingSerializaer.PrepareSerializer<SomeMessage>()は必要なすべての初期化を実行する必要があります(使用している他のコントラクトについても同様です)。

でも!DynamicTypeおそらくこれはあなたの;の使用にも関係しているのだろうか。ここで使用されている実際のオブジェクトは何ですか?ここでロジックを微調整して、そのステップに時間を費やさないようにする必要があるかもしれません。実際のオブジェクトを教えていただければ(再現できるように)、いくつかのテストを実行してみます。

プリコンパイラがこれを変更するかどうかについては、はい、そうです。完全にコンパイルされた静的モデルは、ProtoBuf.Meta.TypeModel.GetKeyメソッドの実装が完全に異なるため、呼び出されることはTakeLockありません(変更できないモデルを保護する必要はありません!)。ただし、プリコンパイルを使用しなくても、実際には非常によく似た処理を実行できます。次のことを考慮して、アプリの初期化の一部として実行します。

static readonly TypeModel serializer;
...
var model = TypeModel.Create();
model.Add(typeof(SomeMessage), true);
// TODO add other contracts you use here
serializer = model.Compile();

これにより、(個々の操作がコンパイルされた可変モデルの代わりに)メモリ内に完全に静的にコンパイルされたシリアライザーアセンブリが作成されます。serializer.Serialize(...)代わりに(つまり、上の静的メソッドではなくSerializer.Serialize、保存されたインスタンスメソッド)を使用すると、基本的に「プリコンパイラ」と非常によく似た動作をしますが、実際にプリコンパイルする必要はありません(明らかにこれは利用可能です) 「フル」.NET)。柔軟なモデルではなく固定モデルを実行しているため、これは決して呼び出されません。ただし、使用する契約タイプを知っている必要があります。特定の属性を持つすべてのタイプを検索することにより、リフレクションを使用してこれらを見つけることができます。TypeModelSerializerTakeLock

static readonly TypeModel serializer;
...
var model = TypeModel.Create();
Type attributeType = typeof(ProtoContractAttribute);
foreach (var type in typeof(SomeMessage).Assembly.GetTypes()) {
    if (Attribute.IsDefined(type, attributeType)) {
        model.Add(type, true);
    }
}
serializer = model.Compile();

ただし、強調:上記は回避策です。グリッチがあるようですが、実際に発生する例を確認できるかどうかを喜んで調査します。最も重要なのは、オブジェクトはSomeArguments何ですか?

于 2012-09-13T06:10:13.310 に答える