nfs: allow serializing/deserializing IOBuf

Summary:
For the READ/WRITE RPC calls, copying data in and out of an IOBuf chain can be
fairly expensive, to avoid this overhead, we can simply clone the data out of
the IOBuf chain directly, saving on the cost of copy.

Since the code uses a folly::io::Appender that doesn't support adding an IOBuf
to it, we still pay the cost of copying data to it, switching to
folly::io::QueueAppender may solve this.

Reviewed By: chadaustin

Differential Revision: D26671896

fbshipit-source-id: 0161f04cb820bf27ef66fdef6b4a1ce4eb778b96
This commit is contained in:
Xavier Deguillard 2021-03-04 09:57:01 -08:00 committed by Facebook GitHub Bot
parent b46a7b2a11
commit 0841d553fd
3 changed files with 82 additions and 0 deletions

View File

@ -31,6 +31,17 @@ void serialize_variable(folly::io::Appender& appender, folly::ByteRange value) {
serialize_fixed(appender, value);
}
void serialize_iobuf(folly::io::Appender& appender, const folly::IOBuf& buf) {
auto len = buf.computeChainDataLength();
if (len > std::numeric_limits<uint32_t>::max()) {
throw std::length_error(
"XDR cannot encode variable sized array bigger than 4GB");
}
XdrTrait<uint32_t>::serialize(appender, folly::to_narrow(len));
appender.push(folly::io::Cursor(&buf), len);
addPadding(appender, len);
}
} // namespace detail
} // namespace facebook::eden

View File

@ -105,6 +105,12 @@ void serialize_fixed(folly::io::Appender& appender, folly::ByteRange value);
*/
void serialize_variable(folly::io::Appender& appender, folly::ByteRange value);
/**
* Serialize an IOBuf chain. This is serialized like a variable sized array,
* ie: size first, followed by the content and aligned on a 4-byte boundary.
*/
void serialize_iobuf(folly::io::Appender& appender, const folly::IOBuf& buf);
/**
* Skip the padding bytes that were written during serialization.
*/
@ -175,6 +181,33 @@ struct XdrTrait<std::vector<uint8_t>> {
}
};
/**
* IOBuf are encoded as a variable sized array, similarly to a vector. IOBuf
* should be preferred to a vector when the data to serialize/deserialize is
* potentially large, a vector would copy all the data, while an IOBuf would
* clone the existing cursor.
*
* TODO(xavierd): folly::io::Appender doesn't have a way to zero-copy append to
* it, maybe a folly::io::QueueAppender would be better fit than
* folly::io::Appender?
*/
template <>
struct XdrTrait<std::unique_ptr<folly::IOBuf>> {
static void serialize(
folly::io::Appender& appender,
const std::unique_ptr<folly::IOBuf>& buf) {
detail::serialize_iobuf(appender, *buf);
}
static std::unique_ptr<folly::IOBuf> deserialize(folly::io::Cursor& cursor) {
auto len = XdrTrait<uint32_t>::deserialize(cursor);
auto ret = std::make_unique<folly::IOBuf>();
cursor.clone(ret, len);
detail::skipPadding(cursor, len);
return ret;
}
};
template <typename T>
struct XdrTrait<
std::vector<T>,

View File

@ -151,6 +151,44 @@ TEST(XdrSerialize, optionalVariant) {
roundtrip(opt2, sizeof(uint32_t));
}
struct IOBufStruct {
uint32_t before;
std::unique_ptr<folly::IOBuf> buf;
uint32_t after;
bool operator==(const IOBufStruct& other) const {
return before == other.before && after == other.after &&
folly::IOBufEqualTo()(buf, other.buf);
}
};
template <>
struct XdrTrait<IOBufStruct> {
static void serialize(
folly::io::Appender& appender,
const IOBufStruct& value) {
XdrTrait<uint32_t>::serialize(appender, value.before);
XdrTrait<std::unique_ptr<folly::IOBuf>>::serialize(appender, value.buf);
XdrTrait<uint32_t>::serialize(appender, value.after);
}
static IOBufStruct deserialize(folly::io::Cursor& cursor) {
IOBufStruct ret;
ret.before = XdrTrait<uint32_t>::deserialize(cursor);
ret.buf = XdrTrait<std::unique_ptr<folly::IOBuf>>::deserialize(cursor);
ret.after = XdrTrait<uint32_t>::deserialize(cursor);
return ret;
}
};
TEST(XdrSerialize, iobuf) {
struct IOBufStruct buf {
42, folly::IOBuf::copyBuffer("This is a test"), 10
};
auto bufLen = buf.buf->computeChainDataLength();
roundtrip(std::move(buf), 3 * sizeof(uint32_t) + bufLen + 2 /*padding*/);
}
} // namespace facebook::eden
#endif