首页 文章

Protobuf-net v2和大型词典

提问于
浏览
1

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

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

每个电流'sensorstate' .

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

这些传感器通常具有超过50k的数据点(相当于15分钟的数据读数),所以我想找到一些比默认的.NET BinaryFormatter 更快的序列化的东西,因此设置Protobuf-net将会非常快速地序列化 .

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

任何帮助解释这一点将非常感激 .

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

编辑:

我刚刚将我的源码放在GitHub上,这是有问题的类

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

这是一个例子raw data file

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

2 回答

  • 2

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

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

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

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

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

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

    • 要少得多 DateTime 值存储

    • 你可以在浮点数上使用"packed"编码,这意味着它不需要添加所有中间标记;你通过将数组/列表标记为( [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

    这是我道歉的地方,因为它曾暗示它与我自己没有做的代码有任何关系 . 虽然我在这里疯狂支持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

相关问题