未经编码的编码 avro 消息的大小

Size of encoded avro message without encoding it

有没有办法在不实际编码的情况下获取编码后的 avro 消息的大小?

我正在为 C++ 使用 Avro 1.8.1。

我习惯了 google 协议缓冲区,您可以在其中调用 ByteSize() protobuf 以获得编码大小,所以它与我正在寻找的类似。

由于消息本质上是一个原始结构,我知道无法从消息本身检索大小,但也许有一个我不知道的辅助方法?

不幸的是,没有办法解决...

下面是一个示例,展示了如何通过对对象进行编码来计算大小:

MyAvroStruct obj;

avro::EncoderPtr encoder = avro::binaryEncoder();
std::auto_ptr<avro::OutputStream> out = avro::memoryOutputStream(1);
encoder->init(*out);
avro::encode(*encoder, obj);
out->flush();
uint32_t bufferSize = out->byteCount();

(下面的编辑显示了一种在用 BinaryEncoder 写入后 shrink-to-fit 一个 OutputStream 的 hacky 方法)

遗憾的是 avro::encode() 没有在 OutputStream 上使用 backup 来释放编码后未使用的内存。 仅使用 avro 提供的工具给出了最佳解决方案,但如果序列化对象的大小为 N 字节,它会发出 N 次内存分配,每次分配 1 字节。

您可以实现一个自定义 avro::OutputStream 来简单地计算并丢弃所有写入的字节。这将摆脱内存分配。这仍然不是一个好方法,因为实际的编码器将不得不“询问”每个字节:

(代码未经测试,仅供演示)

#include <avro/Encoder.hh>
#include <cstdint>

class ByteCountOutputStream : public avro::OutputStream {
public:
    size_t byteCount_ = 0;
    uint8_t dummyWriteLocation_;

    explicit ByteCountOutputStream() {};

    bool next(uint8_t **data, size_t *len) final {
        byteCount_ += 1;
        *data = &dummyWriteLocation_;
        *len = 1;
        return true;
    }

    void backup(size_t len) final {
        byteCount_ -= len;
    }

    uint64_t byteCount() const final {
        return byteCount_;
    }

    void flush() final {}
};

这可以用作:

MyAvroStruct obj;

avro::EncoderPtr encoder = avro::binaryEncoder();
ByteCountOutputStream out();
encoder->init(out);
avro::encode(*encoder, obj);
size_t bufferSize = out.byteCount();

编辑: 遇到这个问题时,我最初的问题是:我如何知道 OutputStream 需要多少字节(用于存储/传输)?或者,等效地,如果 OutputStream.byteCount() returns 到目前为止编码器分配的字节数,我怎样才能让编码器“备份”/释放它没有使用的字节?好吧,有一个 hacky 方法:

Encoder 摘要 class 提供了一个 init method. For the BinaryEncoder, this is currently implemented as:

void BinaryEncoder::init(OutputStream &os) {
    out_.reset(os);
}

其中 out_internal StreamWriter of the Encoder

现在,StreamWriter implements reset as:

    void reset(OutputStream &os) {
        if (out_ != nullptr && end_ != next_) {
            out_->backup(end_ - next_);
        }
        out_ = &os;
        next_ = end_;
    }

这将 return 未使用的内存在切换到新的之前返回到“旧的”OutputStream。

因此,您可以像这样滥用编码器的 init 方法:

// setup as always
MyAvroStruct obj;
avro::EncoderPtr encoder = avro::binaryEncoder();
std::auto_ptr<avro::OutputStream> out = avro::memoryOutputStream();

// actual serialization
encoder->init(*out);
avro::encode(*encoder, obj);

// re-init on the same OutputStream. Happens to shrink the stream to fit
encoder->init(*out);
size_t bufferSize = out->byteCount();

但是,这种行为是 not documented,因此它可能会在未来崩溃。