Protobuf-net v2和大型词典

时间:2011-12-05 21:00:51

标签: c# .net-4.0 protobuf-net

我有一种奇怪的情况发生,我不太了解。

我有一个'数据集'类,其中包含有关监视浮标的各种元数据,包括“传感器”列表。

每个电流'sensorstate'。

每个'sensorstate'都有一些关于它的元数据(时间戳,更改原因等),但最重要的是它有Dictionary<DateTime,float>个值。

这些传感器通常有超过50k的数据点(相当于15分钟的数据读数),所以我想找到一些比默认的.NET BinaryFormatter更快的序列化速度,因此设置{{} 3}}会快速地序列化。

不幸的是,我的问题发生在反序列化时,我的值字典引发了一个异常,因为已经有一个项目添加了相同的键,并且我可以让它反序列化的唯一方法是启用'OverwriteList'但我有点不确定为什么在序列化时没有任何重复的键(它是字典),为什么在反序列化时有重复的键?这也会带来数据完整性问题。

任何帮助解释这一点都将受到高度赞赏。

(另一方面,在给出ProtoMember属性id时,它们是否需要对类或整个项目是唯一的?我正在寻找与protobuf-net一起使用的无损压缩建议,因为文件是变得非常大)

编辑:

我刚刚将我的资源放在GitHub上,这是有问题的课程

Protobuf-net(注意:它目前有OverwriteList = true以使其适用于其他开发)

以下是SensorState

的示例

我已经尝试过使用SkipContructor标志,但即使设置为true,它也会获得异常,除非对于值字典,OverwriteList也是如此。

2 个答案:

答案 0 :(得分:2)

如果OverwriteList修复它,那么它建议给我默认情况下字典中有一些数据,可能是通过构造函数或类似的。如果它确实来自构造函数,则可以使用[ProtoContract(SkipConstructor=true)]禁用它。

如果我误解了上述内容,如果可能的话,可以通过可重复的示例进行说明。

关于id,它们只需要在每种类型中都是唯一的,并且建议保持它们小(由于标签的“varint”编码,小键比大键“更便宜”)。

如果你想真正减小尺寸,我实际上也会建议查看数据的内容。例如,你说这是15分钟的读数...好吧,我猜有偶尔的差距,但你可以做,例如:

Block (class)
    Start Time (DateTime)
    Values (float[])

并且每个连续的15分钟值都有一个Block(这里的假设是每个值在最后一个之后是15,否则启动一个新块)。因此,您要存储多个Block实例来代替单个字典。这有以下优点:

  • 存储
  • DateTime值要少得多
  • 您可以在浮点数上使用“压缩”编码,这意味着它不需要添加所有中间标记;你通过将数组/列表标记为([ProtoMember({key}, IsPacked = true)])来做到这一点 - 注意它只适用于几种基本数据类型(不是子对象)
总而言之,这两项调整可以带来显着的节省

如果数据有很多字符串,您可以尝试GZIP / DEFLATE。你当然可以尝试这两种方式,但是如果没有大量的字符串数据,我会谨慎地期望从压缩中获得额外的额外费用。


作为基于提供的(CSV)数据文件的更新,处理字典没有固有的问题 - 如图所示:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ProtoBuf;
class Program
{
    static void Main()
    {
        var data = new Data
        {
            Points =
            {
                {new DateTime(2009,09,1,0,0,0), 11.04F},
                {new DateTime(2009,09,1,0,15,0), 11.04F},
                {new DateTime(2009,09,1,0,30,0), 11.01F},
                {new DateTime(2009,09,1,0,45,0), 11.01F},
                {new DateTime(2009,09,1,1,0,0), 11F},
                {new DateTime(2009,09,1,1,15,0), 10.98F},
                {new DateTime(2009,09,1,1,30,0), 10.98F},
                {new DateTime(2009,09,1,1,45,0), 10.92F},
                {new DateTime(2009,09,1,2,00,0), 10.09F},
            }
        };

        var ms = new MemoryStream();
        Serializer.Serialize(ms, data);
        ms.Position = 0;
        var clone =Serializer.Deserialize<Data>(ms);
        Console.WriteLine("{0} points:", clone.Points.Count);
        foreach(var pair in clone.Points.OrderBy(x => x.Key))
        {
            float orig;
            data.Points.TryGetValue(pair.Key, out orig);
            Console.WriteLine("{0}: {1}", pair.Key, pair.Value == orig ? "correct" : "FAIL");
        }
    }
}
[ProtoContract]
class Data
{
    private readonly Dictionary<DateTime, float> points = new Dictionary<DateTime, float>();
    [ProtoMember(1)]
    public Dictionary<DateTime, float> Points { get { return points; } } 
}

答案 1 :(得分:1)

这是我道歉的地方,因为它曾暗示它与我自己所做的代码有任何关系。虽然我在这里疯狂支持protobuf和Marc Gravell支持protobuf-net的团队但它的速度非常快。

发生的事情发生在Sensor类我有一些逻辑,永远不会让一些属性永远不为空。

[ProtoMember(12)]
public SensorState CurrentState
{
    get { return (_currentState == null) ? RawData : _currentState; }
    set { _currentState = value; }
}

Link

[ProtoMember(16)]
public SensorState RawData
{
    get { return _rawData ?? (_rawData =  new SensorState(this, DateTime.Now, new Dictionary<DateTime, float>(), "", true, null)); }
    private set { _rawData = value; }
}

Link

虽然这很有效,因为当我使用这些属性时,它会扰乱序列化过程。

简单的解决方法是改为标记基础对象以进行序列化。

[ProtoMember(16)]
private SensorState _rawData;
[ProtoMember(12)]
private SensorState _currentState;

Link