This is an automated email from the ASF dual-hosted git repository.

numinnex pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new 90ce2da8e perf(common): write message header directly into target 
buffer (#3707)
90ce2da8e is described below

commit 90ce2da8e5a7c89e9b6a5a8eb2303cc089019bd0
Author: Ryan Huang <[email protected]>
AuthorDate: Tue Jul 21 01:07:51 2026 +0800

    perf(common): write message header directly into target buffer (#3707)
---
 core/common/src/types/message/iggy_message.rs   | 46 +++++++++++++-
 core/common/src/types/message/message_header.rs | 80 ++++++++++++++++++++++---
 2 files changed, 115 insertions(+), 11 deletions(-)

diff --git a/core/common/src/types/message/iggy_message.rs 
b/core/common/src/types/message/iggy_message.rs
index 645f6ec79..7fe3a3a7c 100644
--- a/core/common/src/types/message/iggy_message.rs
+++ b/core/common/src/types/message/iggy_message.rs
@@ -359,8 +359,7 @@ impl IggyMessage {
 
     pub fn to_bytes(&self) -> Bytes {
         let mut bytes = 
BytesMut::with_capacity(self.get_size_bytes().as_bytes_usize());
-        let message_header = self.header.to_bytes();
-        bytes.put_slice(&message_header);
+        self.header.write_to(&mut bytes);
         bytes.put_slice(&self.payload);
         if let Some(user_headers) = &self.user_headers {
             bytes.put_slice(user_headers);
@@ -418,8 +417,13 @@ impl IggyMessage {
         })
     }
 
+    /// Serializes the message into `buf`.
+    ///
+    /// `buf` need not be preallocated: the whole message is reserved up front 
so
+    /// an empty buffer grows once rather than once per appended field.
     pub fn write_to_buffer(&self, buf: &mut BytesMut) {
-        buf.put_slice(&self.header.to_bytes());
+        buf.reserve(self.get_size_bytes().as_bytes_usize());
+        self.header.write_to(buf);
         buf.put_slice(&self.payload);
         if let Some(user_headers) = &self.user_headers {
             buf.put_slice(user_headers);
@@ -844,4 +848,40 @@ mod tests {
             
deserialized_map.get(&HeaderKey::try_from("correlation-id").unwrap())
         );
     }
+
+    #[test]
+    fn given_empty_destination_buffer_when_written_should_grow_exactly_once() {
+        let message = IggyMessage::builder()
+            .payload(Bytes::from(vec![b'x'; 1024]))
+            .build()
+            .unwrap();
+        let size = message.get_size_bytes().as_bytes_usize();
+
+        let mut buf = BytesMut::new();
+        message.write_to_buffer(&mut buf);
+
+        assert_eq!(buf.len(), size);
+        // A single reservation covers the whole message, so the buffer never
+        // reallocates part-way through the individually appended header 
fields.
+        assert!(buf.capacity() >= size);
+
+        let mut second = BytesMut::new();
+        message.write_to_buffer(&mut second);
+        assert_eq!(&buf[..], &second[..]);
+    }
+
+    #[test]
+    fn 
given_buffer_with_existing_content_when_written_should_append_without_disturbing_it()
 {
+        let message = IggyMessage::builder()
+            .payload(Bytes::from_static(b"payload"))
+            .build()
+            .unwrap();
+
+        let mut buf = BytesMut::new();
+        buf.put_slice(b"existing");
+        message.write_to_buffer(&mut buf);
+
+        assert_eq!(&buf[..8], b"existing");
+        assert_eq!(&buf[8..], &message.to_bytes()[..]);
+    }
 }
diff --git a/core/common/src/types/message/message_header.rs 
b/core/common/src/types/message/message_header.rs
index cf2a66829..fa776bbb7 100644
--- a/core/common/src/types/message/message_header.rs
+++ b/core/common/src/types/message/message_header.rs
@@ -107,16 +107,25 @@ impl IggyMessageHeader {
         })
     }
 
+    /// Serializes the header directly into `buf`, without an intermediate 
allocation.
+    ///
+    /// Reserving up front keeps an unsized `buf` to a single growth, since the
+    /// fields are appended individually.
+    pub fn write_to(&self, buf: &mut BytesMut) {
+        buf.reserve(IGGY_MESSAGE_HEADER_SIZE);
+        buf.put_u64_le(self.checksum);
+        buf.put_u128_le(self.id);
+        buf.put_u64_le(self.offset);
+        buf.put_u64_le(self.timestamp);
+        buf.put_u64_le(self.origin_timestamp);
+        buf.put_u32_le(self.user_headers_length);
+        buf.put_u32_le(self.payload_length);
+        buf.put_u64_le(self.reserved);
+    }
+
     pub fn to_bytes(&self) -> Bytes {
         let mut bytes = 
BytesMut::with_capacity(self.get_size_bytes().as_bytes_usize());
-        bytes.put_u64_le(self.checksum);
-        bytes.put_u128_le(self.id);
-        bytes.put_u64_le(self.offset);
-        bytes.put_u64_le(self.timestamp);
-        bytes.put_u64_le(self.origin_timestamp);
-        bytes.put_u32_le(self.user_headers_length);
-        bytes.put_u32_le(self.payload_length);
-        bytes.put_u64_le(self.reserved);
+        self.write_to(&mut bytes);
         bytes.freeze()
     }
 
@@ -214,6 +223,61 @@ mod tests {
         assert_eq!(header, deserialized);
     }
 
+    #[test]
+    fn 
given_populated_header_when_written_into_shared_buffer_should_append_exact_layout()
 {
+        let header = IggyMessageHeader {
+            checksum: u64::MAX,
+            id: u128::MAX,
+            offset: 42,
+            timestamp: 7,
+            origin_timestamp: 9,
+            user_headers_length: u32::MAX,
+            payload_length: 13,
+            reserved: 0,
+        };
+
+        // Built independently of `to_bytes`, which now shares `write_to`'s
+        // implementation and so cannot serve as an oracle for it.
+        let mut expected = Vec::new();
+        expected.extend_from_slice(&header.checksum.to_le_bytes());
+        expected.extend_from_slice(&header.id.to_le_bytes());
+        expected.extend_from_slice(&header.offset.to_le_bytes());
+        expected.extend_from_slice(&header.timestamp.to_le_bytes());
+        expected.extend_from_slice(&header.origin_timestamp.to_le_bytes());
+        expected.extend_from_slice(&header.user_headers_length.to_le_bytes());
+        expected.extend_from_slice(&header.payload_length.to_le_bytes());
+        expected.extend_from_slice(&header.reserved.to_le_bytes());
+
+        let mut buf = BytesMut::new();
+        buf.put_slice(b"prefix");
+        header.write_to(&mut buf);
+
+        assert_eq!(&buf[..6], b"prefix");
+        assert_eq!(&buf[6..], &expected[..]);
+        assert_eq!(expected.len(), IGGY_MESSAGE_HEADER_SIZE);
+    }
+
+    #[test]
+    fn given_header_written_at_offset_when_parsed_back_should_round_trip() {
+        let header = IggyMessageHeader {
+            checksum: 1,
+            id: 2,
+            offset: 3,
+            timestamp: 4,
+            origin_timestamp: 5,
+            user_headers_length: 6,
+            payload_length: 7,
+            reserved: 0,
+        };
+
+        let mut buf = BytesMut::new();
+        header.write_to(&mut buf);
+        header.write_to(&mut buf);
+
+        let second = Bytes::copy_from_slice(&buf[IGGY_MESSAGE_HEADER_SIZE..]);
+        assert_eq!(IggyMessageHeader::from_bytes(second).unwrap(), header);
+    }
+
     #[test]
     fn should_serialize_header_to_correct_size() {
         let header = IggyMessageHeader::default();

Reply via email to