访客模式序列化内存膨胀

Visitor pattern serialization memory bloat

提问人:klonyyy 提问时间:10/17/2023 最后编辑:klonyyy 更新时间:10/17/2023 访问量:61

问:

我正在开发一个用于嵌入式目标的序列化库。该库基于经典的访问者模式,在我要序列化的每个结构中都实现了模板化方法。例:pack()

struct softwareInfo
{
    uint32_t version;
    std::array<uint8_t, 40> commitHash;
    uint32_t buildDate;

    template <class T>
    void pack(T& archive)
    {
        archive.process(HVP(version));
        archive.process(HVP(commitHash));
        archive.process(HVP(buildDate));
    }
};

HVP 宏将具有哈希名称的字段打包到稍后使用的 hashValuePair 类中。

constexpr uint32_t compileTimeHash(const char* str, size_t index = 0, uint32_t hash = 0)
{
    return (str[index] == '\0') ? hash : compileTimeHash(str, index + 1, hash * 31 + str[index]);
}

template <typename T>
class hashValuePair
{
   public:
    hashValuePair(uint32_t hash, T* value) : hash(hash), value(value) {}
    uint32_t getHash() const { return hash; }
    T& getValueRef() { return *value; }

   private:
    uint32_t hash = 0;
    T* const value = nullptr;
};

#define HVP(var) hashValuePair(compileTimeHash(#var), &var)

还有两个模块:

  1. PersistentStorage 类,用于注册可序列化的对象。它包含以下缓冲区:
std::array<StorageEntry, maxEntries> entries

哪里:

struct StorageEntry
{
    std::aligned_storage_t<sizeof(Entry<void>), alignof(Entry<void>)> buffer;
    EntryBase* object;
    struct Header
    {
        uint32_t hashName;
        uint32_t size;
    } header;
    uint32_t datacrc32;
    Status packStatus;
    Status unpackStatus;
};

class EntryBase
{
   public:
    virtual ~EntryBase() {}
    virtual void pack(Packer::Packer& packer) = 0;
    virtual void pack(Packer::Unpacker& unpacker) = 0;
};

template <typename T>
class Entry : public EntryBase
{
   public:
    explicit Entry(T* item) : item(item) {}
    void pack(Packer::Packer& packer) override { item->pack(std::forward<Packer::Packer&>(packer)); }
    void pack(Packer::Unpacker& unpacker) override { item->pack(std::forward<Packer::Unpacker&>(unpacker)); }

   private:
    T* const item;
};

它有两个主要方法 - 并且采用所有已注册的结构并使用下面所示的 和 方法一一打包/解压缩它们。doRead()doWrite()packunpack

  1. 第二个模块是一对 Packer/Unpacker 类,用于通过访问使用 process 方法列出的每个成员来序列化和反序列化结构。
namespace Packer
{

class Packer
{
   public:

    template <class T>
    void process(hashValuePair<T>&& hashValuePair)
    {
        packType(hashValuePair.getValueRef());
    }

   private:

    template <class T>
    void packType(T& value)
    { 
        (...)
        copyToBuf(value);
    }

    template <class T>
    void copyToBuf(T value)
    {
        it += serialize<T>(value, it);
    }
};

class Unpacker
{
   public:

    template <class T>
    void process(hashValuePair<T>&& hashValuePair)
    {
        //search for value hash
        unpackType(hashValuePair.getValueRef());
    }

   private:

    template <class T>
    void unpackType(T& value)
    {
        (...)
        tryUnpack(*it++, value);
    }

    template <typename T>
    void tryUnpack(uint8_t maybeType, T& value)
    {
        if (maybeType != getType(value))
            //error
        copyToValue(value);
    }

    template <typename T>
    void copyToValue(T& value)
    {
        value = deserialize<T>(it);
        it += sizeof(value);
    }
};

template <class PackableObject>
Status pack(PackableObject& obj, std::span<uint8_t>& buf, size_t& packedSize)
{
    Status status{};
    auto packer = Packer(buf, packedSize, &status);
    obj.pack(packer);
    return status;
}

template <class PackableObject>
Status unpack(PackableObject& obj, std::span<uint8_t>& buf)
{
    Status status{};
    auto unpacker = Unpacker(buf, &status);
    obj.pack(unpacker);
    return status;
}

} 

我故意省略了实现的其余部分(错误处理、搜索字段哈希),只留下最基本的流程。

现在,我可以看到,与上面提供的实现相关的内存占用量相对较大。我可以想象模板化方法必须使用使用的类型进行实例化,但是我不确定我是否以最佳方式执行所有操作。例如,序列化结构使用 464B 的 FLASH 存储器 Packer FLASH 占用空间,划分为函数。当我添加另一个 uint32_t 字段时,我的大小增加了 44B,这感觉相当大,特别是考虑到我已经将这种类型与以前的uint32_t场 Packer FLASH 占用空间划分为具有附加uint32_t场的功能。生成类型为 release with -Os。softwareInfo

您能看到当前设计中的任何明显缺陷吗?在这种情况下,像 std::variant 这样的东西会有所帮助吗,因为它的运行时多态性?

C++ 优化 序列化 访客模式

评论

0赞 Botje 10/17/2023
如果大小很重要,可以考虑编写一个通用解释器来序列化/反序列化对象。描述符字符串(如)包含 softwareInfo 所需的所有信息。\x00\x00U \x00\x04C40 \x00\x2CU
0赞 kiner_shah 10/17/2023
您是否尝试过使用像 Cereal 这样的现有库?
0赞 klonyyy 10/17/2023
@Botje存储大小本身并不高,但我相当担心单个方法在 .text 部分占用多少内存。我想为引导加载程序和主应用程序重用 PersistentStorage 类。@kiner_shah AFAIK Cereal 使用动态内存分配和 RTTI,这在我的情况下是不行的。process
0赞 Botje 10/17/2023
是的,解释器会将代码内存要求降低到仅构成应用程序和某些构造函数调用的基元类型。
0赞 Jarod42 10/17/2023
您是否仅限于 C++11(根据实现)?另请注意,调用不是在 constexpr 表达式中完成的,而是可能在运行时完成的。 可能在这方面有所帮助。compileTimeHashconsteval

答: 暂无答案