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(即存储上的实例方法TypeModel而不是静态方法上Serializer),那么它本质上将执行与“预编译器”非常相似的事情,但不需要实际预编译它(显然这只可用在“完整”.NET 上)。这将永远不会调用TakeLock,因为它运行的是固定模型,而不是灵活模型。但是,它确实要求您知道您使用的合同类型。您可以使用反射来查找这些,通过查找具有给定属性的所有这些类型:

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 回答