Protocol buffers TakeLock中的protobuf网络并发性能问题

Protocol buffers TakeLock中的protobuf网络并发性能问题,protocol-buffers,protobuf-net,Protocol Buffers,Protobuf Net,我们正在使用protobuf net在服务之间发送日志消息。在分析压力测试时,在高并发性下,我们看到非常高的CPU使用率,而RuntimeTypeModel中的TakeLock就是罪魁祸首。热调用堆栈类似于: *Our code...* ProtoBuf.Serializer.SerializeWithLengthPrefix(class System.IO.Stream,!!0,valuetype ProtoBuf.PrefixStyle) ProtoBuf.Serializer.Serial

我们正在使用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
}
[协议]
公共类消息
{
[原成员(1)]
公共SomeEnumType SomeEnum{get;set;}
[原成员(2)]
公共长SomeId{get;set;}
[原成员(3)]
公共字符串SomeString{get;set;}
[原成员(4)]
公共日期时间SomeDate{get;set;}
[原型成员(5,DynamicType=true,OverwriteList=true)]
公共集合参数
}
谢谢你的帮助

更新9/17

谢谢你的回复!我们将尝试您建议的解决方法,看看能否解决问题

这段代码存在于我们的日志系统中,因此,在SomeMessage示例中,SomeString实际上是一个格式字符串(例如,“Hello{0}”),SomeArguments集合是一个用于填充格式字符串的对象列表,就像string.format一样。在序列化之前,我们先查看每个参数并调用
DynamicSerializer.IsKnownType(argument.GetType())
,如果未知,则首先将其转换为字符串。我没有研究数据的比率,但是我很确定我们有很多不同的字符串作为参数


让我知道这是否有帮助。如果需要,我将尝试获取更多详细信息。

TakeLock
仅在更改模型时使用,例如,因为它第一次看到类型。在第一次使用特定类型后,通常不应看到
TakeLock
。在大多数情况下,使用
serializer.PrepareSerializer()
应该执行所有必要的初始化(以及您正在使用的任何其他合同的类似操作)

然而!我想知道这是否也与您使用的
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
上的静态方法),那么它实际上将执行与“预编译器”非常类似的操作,但不需要实际预编译它(显然,这将只在“full.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

中的对象是什么?

感谢您的回答!我在上面添加了详细信息。我们还遇到了严重的TakeLock争用——似乎只发生了n当执行大量值类型(int、string等)的并发序列化时@arathorn
string
不是值类型,但是:我看看能找到什么,thanks@arathornk;我正在为即将到来的测试添加一条快速失败路径version@MarcGravell我面临着同样的问题,很多TakeLock都处于加载状态。我有静态模型instanc,我正在从数据契约的汇编和调用编译中加载类型。不过,findoradau方法被多次调用,与线程所有者的问题相同。知道吗?