Skip to content

Latest commit

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ByteStream

A blazing fast byte (de)serializer for C#.

Available on NuGet via Install-Package ByteStream

What is ByteStream?

Bytestream is a small library that enables blazing fast serialization of a collection of types to raw bytes. Either in the form of a byte array byte[] or unmanaged memory IntPtr. The library performs no memory allocation on its own, so you are free to use your own memory allocator!

ByteStream is written in NetStandard2.0 making it compatible with .Net, .Net Core and Unity3D among things!

Carefully read the Usage section for a brief introduction on how to use the library.

What does ByteStream offer?

Bytestream offers 2 sets of serializers and deserializers, called writers and readers. They are solely used for manual serialization and deserialization of the supported types.

  • Easy to understand, simple syntax.
  • Lightweight, no memory allocation.
  • Extremely fast, using pointer conversion to read and write data.
  • Uses memory copy to quickly copy large blocks of memory to and from the buffers.
  • Comes in a managed and unmanaged variant.
  • Auto-resizable, managed buffer (optional).
  • Automatically keeps track of offsets and buffer boundaries.
  • Prefixes strings and memory blocks with a 2-byte length (optional).

Supported types:

  • All types that adhere to the unmanaged constraint can be written to, and read from the buffers.
  • Byte arrays
  • ANSI strings (1-byte per character)
  • UTF16 strings (2-byte per character, default in C#)
  • Any string that's supported by .NET's Encoding class.

User-defined structs may change their layout when used on another system. Don't serialize user-defined structs unless you are absolutely certain the layout will be the same everywhere it is read back.

Drawbacks

  • The ByteWriter and ByteReader makes heavy use of memory pinning. This can make the unmanaged variant preferable when possible.
  • The writers and readers always make use of pointer conversion. In the case of writing single-byte values to a byte[] buffer, consider directly writing these values to the buffer instead.

Technical specifications:

Comparison to other methods:

Writing of 1024 integer (4-byte) values to a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteWrite2.199 us0.0048 us0.0043 us0.57--
PtrWrite1.651 us0.0074 us0.0069 us0.43--
ManagedStream1.705 us0.0012 us0.0010 us0.44--
UnmanagedStream1.351 us0.0058 us0.0055 us0.35--
ArraySegment3.875 us0.0050 us0.0042 us1.00--
BitConvert10.762 us0.0227 us0.0201 us2.785.188032768 B
Union9.001 us0.0438 us0.0410 us2.32--

Reading of 1024 integer (4-byte) values from a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteRead1.311 us0.0045 us0.0042 us0.35--
PtrRead1.239 us0.0055 us0.0052 us0.33--
ManagedStream1.172 us0.0012 us0.0012 us0.32--
UnmanagedStream1.192 us0.0048 us0.0045 us0.32--
ArraySegment3.712 us0.0085 us0.0080 us1.00--
BitConvert1.542 us0.0046 us0.0043 us0.42--
Union9.421 us0.0336 us0.0314 us2.54--

When using ArraySegment, BitConverter and Unioning structs the user needs to keep track of the offsets themselves. Manual bitshifting may also be required to read and write values.

Usage

Because the API is almost identical, the managed and unmanaged readers and writers function nearly identical. Be warned that when using the unmanaged reader or writer, the provided length must be no longer than the actual length of the memory block provided.

It is important that you write and read back the values in the same order to keep data consistent (as shown in the write and read example).

Because the readers and writers are value types, they must be passed along using the ref keyword.

The readers and writers are not thread safe! Beware of accessing buffers concurrently from multiple threads and modifying the supplied buffers during operation!

Writing

Writing of values is really easy. Just wrap a buffer and you can start writing!

//Creating a simple buffer that resizes when the limit it met.ByteWriterwriter=newByteWriter(64,false);//Wrapping a pre-existing bufferbyte[]buffer=newbyte[128];ByteWriterwriter=newByteWriter(buffer);//Writing an ANSI string with a prefixed length.writer.WriteANSI("Writing an integer value...",true);//Writing an integer value.writer.Write(591823);//Copying the internal buffer to a new byte array.byte[]copy=newbyte[32];writer.CopyTo(copy);//Gets the original buffer (is equal to the above defined "buffer")byte[]originalBuffer=writer.Buffer;

Reading

Reading values is just as easy as writing them! Again, just wrap a buffer and you can start reading.

byte[]buffer;//Some earlier defined buffer that holds data.ByteReaderreader=newByteReader(buffer);//Read an ANSI string (automatically grabs the prefixed size).stringstringValue=reader.ReadANSI();//Read an integer value.intintValue=reader.Read<int>();

Defining custom types

We can create extension methods to allow serializing and deserializing of user-defined types (even classes!). Be sure to add the ref keyword to ensure the offset gets incremented!

publicstaticvoidWritePoint(refthisByteWriterwriter,Pointpoint){writer.Write(point.X);writer.Write(point.Y);}publicstaticPointReadPoint(refthisByteReaderreader){returnnewPoint(reader.Read<int>(),reader.Read<int>());}//We can then use these extension methods from anywhere elseByteWriterwriter=newByteWriter(buffer);writer.WritePoint(newPoint(1,2));ByteReaderreader=newByteReader(buffer);varpoint=reader.ReadPoint();

Streaming classes

The ManagedSteam and the UnmanagedStream make serialization of objects even easier. Below is an example of how to serialize a class using the IByteStream interface that comes with both of the aforementioned Streams.

The Stream can simply be passed to the Serialize method. Depending if the Stream is in Write or Read mode, the object is serialized or deserialized automatically.

publicclassPlayerData{publicstringName;publicintHealth;publicfloatSpeed;publicPlayerInventoryInventory;publicvoidSerialize(IByteStreamstream){stream.SerializeString(refName,Encoding.ASCII);stream.Serialize(refHealth);stream.Serialize(refSpeed);Inventory.Serialize(stream);}}

Extending either of the Stream classes (or the IByteStream Interface) is equally as easy as using them to serialize. Below is an example of a Serialize method for a Vector3 by extending IByteStream.

internalstaticclassExtension{publicstaticvoidSerialize(thisIByteStreamstream,refVector3vector){stream.Serialize(refvector.X);stream.Serialize(refvector.Y);stream.Serialize(refvector.Z);}}

About

A blazing fast byte (de)serializer

Topics

Resources

Stars

23 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - DennisCorvers/ByteStream: A blazing fast byte (de)serializer · GitHub
Skip to content

Latest commit

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ByteStream

A blazing fast byte (de)serializer for C#.

Available on NuGet via Install-Package ByteStream

What is ByteStream?

Bytestream is a small library that enables blazing fast serialization of a collection of types to raw bytes. Either in the form of a byte array byte[] or unmanaged memory IntPtr. The library performs no memory allocation on its own, so you are free to use your own memory allocator!

ByteStream is written in NetStandard2.0 making it compatible with .Net, .Net Core and Unity3D among things!

Carefully read the Usage section for a brief introduction on how to use the library.

What does ByteStream offer?

Bytestream offers 2 sets of serializers and deserializers, called writers and readers. They are solely used for manual serialization and deserialization of the supported types.

  • Easy to understand, simple syntax.
  • Lightweight, no memory allocation.
  • Extremely fast, using pointer conversion to read and write data.
  • Uses memory copy to quickly copy large blocks of memory to and from the buffers.
  • Comes in a managed and unmanaged variant.
  • Auto-resizable, managed buffer (optional).
  • Automatically keeps track of offsets and buffer boundaries.
  • Prefixes strings and memory blocks with a 2-byte length (optional).

Supported types:

  • All types that adhere to the unmanaged constraint can be written to, and read from the buffers.
  • Byte arrays
  • ANSI strings (1-byte per character)
  • UTF16 strings (2-byte per character, default in C#)
  • Any string that's supported by .NET's Encoding class.

User-defined structs may change their layout when used on another system. Don't serialize user-defined structs unless you are absolutely certain the layout will be the same everywhere it is read back.

Drawbacks

  • The ByteWriter and ByteReader makes heavy use of memory pinning. This can make the unmanaged variant preferable when possible.
  • The writers and readers always make use of pointer conversion. In the case of writing single-byte values to a byte[] buffer, consider directly writing these values to the buffer instead.

Technical specifications:

Comparison to other methods:

Writing of 1024 integer (4-byte) values to a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteWrite2.199 us0.0048 us0.0043 us0.57--
PtrWrite1.651 us0.0074 us0.0069 us0.43--
ManagedStream1.705 us0.0012 us0.0010 us0.44--
UnmanagedStream1.351 us0.0058 us0.0055 us0.35--
ArraySegment3.875 us0.0050 us0.0042 us1.00--
BitConvert10.762 us0.0227 us0.0201 us2.785.188032768 B
Union9.001 us0.0438 us0.0410 us2.32--

Reading of 1024 integer (4-byte) values from a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteRead1.311 us0.0045 us0.0042 us0.35--
PtrRead1.239 us0.0055 us0.0052 us0.33--
ManagedStream1.172 us0.0012 us0.0012 us0.32--
UnmanagedStream1.192 us0.0048 us0.0045 us0.32--
ArraySegment3.712 us0.0085 us0.0080 us1.00--
BitConvert1.542 us0.0046 us0.0043 us0.42--
Union9.421 us0.0336 us0.0314 us2.54--

When using ArraySegment, BitConverter and Unioning structs the user needs to keep track of the offsets themselves. Manual bitshifting may also be required to read and write values.

Usage

Because the API is almost identical, the managed and unmanaged readers and writers function nearly identical. Be warned that when using the unmanaged reader or writer, the provided length must be no longer than the actual length of the memory block provided.

It is important that you write and read back the values in the same order to keep data consistent (as shown in the write and read example).

Because the readers and writers are value types, they must be passed along using the ref keyword.

The readers and writers are not thread safe! Beware of accessing buffers concurrently from multiple threads and modifying the supplied buffers during operation!

Writing

Writing of values is really easy. Just wrap a buffer and you can start writing!

//Creating a simple buffer that resizes when the limit it met.ByteWriterwriter=newByteWriter(64,false);//Wrapping a pre-existing bufferbyte[]buffer=newbyte[128];ByteWriterwriter=newByteWriter(buffer);//Writing an ANSI string with a prefixed length.writer.WriteANSI("Writing an integer value...",true);//Writing an integer value.writer.Write(591823);//Copying the internal buffer to a new byte array.byte[]copy=newbyte[32];writer.CopyTo(copy);//Gets the original buffer (is equal to the above defined "buffer")byte[]originalBuffer=writer.Buffer;

Reading

Reading values is just as easy as writing them! Again, just wrap a buffer and you can start reading.

byte[]buffer;//Some earlier defined buffer that holds data.ByteReaderreader=newByteReader(buffer);//Read an ANSI string (automatically grabs the prefixed size).stringstringValue=reader.ReadANSI();//Read an integer value.intintValue=reader.Read<int>();

Defining custom types

We can create extension methods to allow serializing and deserializing of user-defined types (even classes!). Be sure to add the ref keyword to ensure the offset gets incremented!

publicstaticvoidWritePoint(refthisByteWriterwriter,Pointpoint){writer.Write(point.X);writer.Write(point.Y);}publicstaticPointReadPoint(refthisByteReaderreader){returnnewPoint(reader.Read<int>(),reader.Read<int>());}//We can then use these extension methods from anywhere elseByteWriterwriter=newByteWriter(buffer);writer.WritePoint(newPoint(1,2));ByteReaderreader=newByteReader(buffer);varpoint=reader.ReadPoint();

Streaming classes

The ManagedSteam and the UnmanagedStream make serialization of objects even easier. Below is an example of how to serialize a class using the IByteStream interface that comes with both of the aforementioned Streams.

The Stream can simply be passed to the Serialize method. Depending if the Stream is in Write or Read mode, the object is serialized or deserialized automatically.

publicclassPlayerData{publicstringName;publicintHealth;publicfloatSpeed;publicPlayerInventoryInventory;publicvoidSerialize(IByteStreamstream){stream.SerializeString(refName,Encoding.ASCII);stream.Serialize(refHealth);stream.Serialize(refSpeed);Inventory.Serialize(stream);}}

Extending either of the Stream classes (or the IByteStream Interface) is equally as easy as using them to serialize. Below is an example of a Serialize method for a Vector3 by extending IByteStream.

internalstaticclassExtension{publicstaticvoidSerialize(thisIByteStreamstream,refVector3vector){stream.Serialize(refvector.X);stream.Serialize(refvector.Y);stream.Serialize(refvector.Z);}}

About

A blazing fast byte (de)serializer

Topics

Resources

Stars

23 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DennisCorvers/ByteStream: A blazing fast byte (de)serializer · GitHub
Skip to content

Latest commit

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ByteStream

A blazing fast byte (de)serializer for C#.

Available on NuGet via Install-Package ByteStream

What is ByteStream?

Bytestream is a small library that enables blazing fast serialization of a collection of types to raw bytes. Either in the form of a byte array byte[] or unmanaged memory IntPtr. The library performs no memory allocation on its own, so you are free to use your own memory allocator!

ByteStream is written in NetStandard2.0 making it compatible with .Net, .Net Core and Unity3D among things!

Carefully read the Usage section for a brief introduction on how to use the library.

What does ByteStream offer?

Bytestream offers 2 sets of serializers and deserializers, called writers and readers. They are solely used for manual serialization and deserialization of the supported types.

  • Easy to understand, simple syntax.
  • Lightweight, no memory allocation.
  • Extremely fast, using pointer conversion to read and write data.
  • Uses memory copy to quickly copy large blocks of memory to and from the buffers.
  • Comes in a managed and unmanaged variant.
  • Auto-resizable, managed buffer (optional).
  • Automatically keeps track of offsets and buffer boundaries.
  • Prefixes strings and memory blocks with a 2-byte length (optional).

Supported types:

  • All types that adhere to the unmanaged constraint can be written to, and read from the buffers.
  • Byte arrays
  • ANSI strings (1-byte per character)
  • UTF16 strings (2-byte per character, default in C#)
  • Any string that's supported by .NET's Encoding class.

User-defined structs may change their layout when used on another system. Don't serialize user-defined structs unless you are absolutely certain the layout will be the same everywhere it is read back.

Drawbacks

  • The ByteWriter and ByteReader makes heavy use of memory pinning. This can make the unmanaged variant preferable when possible.
  • The writers and readers always make use of pointer conversion. In the case of writing single-byte values to a byte[] buffer, consider directly writing these values to the buffer instead.

Technical specifications:

Comparison to other methods:

Writing of 1024 integer (4-byte) values to a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteWrite2.199 us0.0048 us0.0043 us0.57--
PtrWrite1.651 us0.0074 us0.0069 us0.43--
ManagedStream1.705 us0.0012 us0.0010 us0.44--
UnmanagedStream1.351 us0.0058 us0.0055 us0.35--
ArraySegment3.875 us0.0050 us0.0042 us1.00--
BitConvert10.762 us0.0227 us0.0201 us2.785.188032768 B
Union9.001 us0.0438 us0.0410 us2.32--

Reading of 1024 integer (4-byte) values from a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteRead1.311 us0.0045 us0.0042 us0.35--
PtrRead1.239 us0.0055 us0.0052 us0.33--
ManagedStream1.172 us0.0012 us0.0012 us0.32--
UnmanagedStream1.192 us0.0048 us0.0045 us0.32--
ArraySegment3.712 us0.0085 us0.0080 us1.00--
BitConvert1.542 us0.0046 us0.0043 us0.42--
Union9.421 us0.0336 us0.0314 us2.54--

When using ArraySegment, BitConverter and Unioning structs the user needs to keep track of the offsets themselves. Manual bitshifting may also be required to read and write values.

Usage

Because the API is almost identical, the managed and unmanaged readers and writers function nearly identical. Be warned that when using the unmanaged reader or writer, the provided length must be no longer than the actual length of the memory block provided.

It is important that you write and read back the values in the same order to keep data consistent (as shown in the write and read example).

Because the readers and writers are value types, they must be passed along using the ref keyword.

The readers and writers are not thread safe! Beware of accessing buffers concurrently from multiple threads and modifying the supplied buffers during operation!

Writing

Writing of values is really easy. Just wrap a buffer and you can start writing!

//Creating a simple buffer that resizes when the limit it met.ByteWriterwriter=newByteWriter(64,false);//Wrapping a pre-existing bufferbyte[]buffer=newbyte[128];ByteWriterwriter=newByteWriter(buffer);//Writing an ANSI string with a prefixed length.writer.WriteANSI("Writing an integer value...",true);//Writing an integer value.writer.Write(591823);//Copying the internal buffer to a new byte array.byte[]copy=newbyte[32];writer.CopyTo(copy);//Gets the original buffer (is equal to the above defined "buffer")byte[]originalBuffer=writer.Buffer;

Reading

Reading values is just as easy as writing them! Again, just wrap a buffer and you can start reading.

byte[]buffer;//Some earlier defined buffer that holds data.ByteReaderreader=newByteReader(buffer);//Read an ANSI string (automatically grabs the prefixed size).stringstringValue=reader.ReadANSI();//Read an integer value.intintValue=reader.Read<int>();

Defining custom types

We can create extension methods to allow serializing and deserializing of user-defined types (even classes!). Be sure to add the ref keyword to ensure the offset gets incremented!

publicstaticvoidWritePoint(refthisByteWriterwriter,Pointpoint){writer.Write(point.X);writer.Write(point.Y);}publicstaticPointReadPoint(refthisByteReaderreader){returnnewPoint(reader.Read<int>(),reader.Read<int>());}//We can then use these extension methods from anywhere elseByteWriterwriter=newByteWriter(buffer);writer.WritePoint(newPoint(1,2));ByteReaderreader=newByteReader(buffer);varpoint=reader.ReadPoint();

Streaming classes

The ManagedSteam and the UnmanagedStream make serialization of objects even easier. Below is an example of how to serialize a class using the IByteStream interface that comes with both of the aforementioned Streams.

The Stream can simply be passed to the Serialize method. Depending if the Stream is in Write or Read mode, the object is serialized or deserialized automatically.

publicclassPlayerData{publicstringName;publicintHealth;publicfloatSpeed;publicPlayerInventoryInventory;publicvoidSerialize(IByteStreamstream){stream.SerializeString(refName,Encoding.ASCII);stream.Serialize(refHealth);stream.Serialize(refSpeed);Inventory.Serialize(stream);}}

Extending either of the Stream classes (or the IByteStream Interface) is equally as easy as using them to serialize. Below is an example of a Serialize method for a Vector3 by extending IByteStream.

internalstaticclassExtension{publicstaticvoidSerialize(thisIByteStreamstream,refVector3vector){stream.Serialize(refvector.X);stream.Serialize(refvector.Y);stream.Serialize(refvector.Z);}}

About

A blazing fast byte (de)serializer

Topics

Resources

Stars

23 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DennisCorvers/ByteStream: A blazing fast byte (de)serializer · GitHub
Skip to content

Latest commit

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ByteStream

A blazing fast byte (de)serializer for C#.

Available on NuGet via Install-Package ByteStream

What is ByteStream?

Bytestream is a small library that enables blazing fast serialization of a collection of types to raw bytes. Either in the form of a byte array byte[] or unmanaged memory IntPtr. The library performs no memory allocation on its own, so you are free to use your own memory allocator!

ByteStream is written in NetStandard2.0 making it compatible with .Net, .Net Core and Unity3D among things!

Carefully read the Usage section for a brief introduction on how to use the library.

What does ByteStream offer?

Bytestream offers 2 sets of serializers and deserializers, called writers and readers. They are solely used for manual serialization and deserialization of the supported types.

  • Easy to understand, simple syntax.
  • Lightweight, no memory allocation.
  • Extremely fast, using pointer conversion to read and write data.
  • Uses memory copy to quickly copy large blocks of memory to and from the buffers.
  • Comes in a managed and unmanaged variant.
  • Auto-resizable, managed buffer (optional).
  • Automatically keeps track of offsets and buffer boundaries.
  • Prefixes strings and memory blocks with a 2-byte length (optional).

Supported types:

  • All types that adhere to the unmanaged constraint can be written to, and read from the buffers.
  • Byte arrays
  • ANSI strings (1-byte per character)
  • UTF16 strings (2-byte per character, default in C#)
  • Any string that's supported by .NET's Encoding class.

User-defined structs may change their layout when used on another system. Don't serialize user-defined structs unless you are absolutely certain the layout will be the same everywhere it is read back.

Drawbacks

  • The ByteWriter and ByteReader makes heavy use of memory pinning. This can make the unmanaged variant preferable when possible.
  • The writers and readers always make use of pointer conversion. In the case of writing single-byte values to a byte[] buffer, consider directly writing these values to the buffer instead.

Technical specifications:

Comparison to other methods:

Writing of 1024 integer (4-byte) values to a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteWrite2.199 us0.0048 us0.0043 us0.57--
PtrWrite1.651 us0.0074 us0.0069 us0.43--
ManagedStream1.705 us0.0012 us0.0010 us0.44--
UnmanagedStream1.351 us0.0058 us0.0055 us0.35--
ArraySegment3.875 us0.0050 us0.0042 us1.00--
BitConvert10.762 us0.0227 us0.0201 us2.785.188032768 B
Union9.001 us0.0438 us0.0410 us2.32--

Reading of 1024 integer (4-byte) values from a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteRead1.311 us0.0045 us0.0042 us0.35--
PtrRead1.239 us0.0055 us0.0052 us0.33--
ManagedStream1.172 us0.0012 us0.0012 us0.32--
UnmanagedStream1.192 us0.0048 us0.0045 us0.32--
ArraySegment3.712 us0.0085 us0.0080 us1.00--
BitConvert1.542 us0.0046 us0.0043 us0.42--
Union9.421 us0.0336 us0.0314 us2.54--

When using ArraySegment, BitConverter and Unioning structs the user needs to keep track of the offsets themselves. Manual bitshifting may also be required to read and write values.

Usage

Because the API is almost identical, the managed and unmanaged readers and writers function nearly identical. Be warned that when using the unmanaged reader or writer, the provided length must be no longer than the actual length of the memory block provided.

It is important that you write and read back the values in the same order to keep data consistent (as shown in the write and read example).

Because the readers and writers are value types, they must be passed along using the ref keyword.

The readers and writers are not thread safe! Beware of accessing buffers concurrently from multiple threads and modifying the supplied buffers during operation!

Writing

Writing of values is really easy. Just wrap a buffer and you can start writing!

//Creating a simple buffer that resizes when the limit it met.ByteWriterwriter=newByteWriter(64,false);//Wrapping a pre-existing bufferbyte[]buffer=newbyte[128];ByteWriterwriter=newByteWriter(buffer);//Writing an ANSI string with a prefixed length.writer.WriteANSI("Writing an integer value...",true);//Writing an integer value.writer.Write(591823);//Copying the internal buffer to a new byte array.byte[]copy=newbyte[32];writer.CopyTo(copy);//Gets the original buffer (is equal to the above defined "buffer")byte[]originalBuffer=writer.Buffer;

Reading

Reading values is just as easy as writing them! Again, just wrap a buffer and you can start reading.

byte[]buffer;//Some earlier defined buffer that holds data.ByteReaderreader=newByteReader(buffer);//Read an ANSI string (automatically grabs the prefixed size).stringstringValue=reader.ReadANSI();//Read an integer value.intintValue=reader.Read<int>();

Defining custom types

We can create extension methods to allow serializing and deserializing of user-defined types (even classes!). Be sure to add the ref keyword to ensure the offset gets incremented!

publicstaticvoidWritePoint(refthisByteWriterwriter,Pointpoint){writer.Write(point.X);writer.Write(point.Y);}publicstaticPointReadPoint(refthisByteReaderreader){returnnewPoint(reader.Read<int>(),reader.Read<int>());}//We can then use these extension methods from anywhere elseByteWriterwriter=newByteWriter(buffer);writer.WritePoint(newPoint(1,2));ByteReaderreader=newByteReader(buffer);varpoint=reader.ReadPoint();

Streaming classes

The ManagedSteam and the UnmanagedStream make serialization of objects even easier. Below is an example of how to serialize a class using the IByteStream interface that comes with both of the aforementioned Streams.

The Stream can simply be passed to the Serialize method. Depending if the Stream is in Write or Read mode, the object is serialized or deserialized automatically.

publicclassPlayerData{publicstringName;publicintHealth;publicfloatSpeed;publicPlayerInventoryInventory;publicvoidSerialize(IByteStreamstream){stream.SerializeString(refName,Encoding.ASCII);stream.Serialize(refHealth);stream.Serialize(refSpeed);Inventory.Serialize(stream);}}

Extending either of the Stream classes (or the IByteStream Interface) is equally as easy as using them to serialize. Below is an example of a Serialize method for a Vector3 by extending IByteStream.

internalstaticclassExtension{publicstaticvoidSerialize(thisIByteStreamstream,refVector3vector){stream.Serialize(refvector.X);stream.Serialize(refvector.Y);stream.Serialize(refvector.Z);}}

About

A blazing fast byte (de)serializer

Topics

Resources

Stars

23 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - DennisCorvers/ByteStream: A blazing fast byte (de)serializer · GitHub
Skip to content

Latest commit

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ByteStream

A blazing fast byte (de)serializer for C#.

Available on NuGet via Install-Package ByteStream

What is ByteStream?

Bytestream is a small library that enables blazing fast serialization of a collection of types to raw bytes. Either in the form of a byte array byte[] or unmanaged memory IntPtr. The library performs no memory allocation on its own, so you are free to use your own memory allocator!

ByteStream is written in NetStandard2.0 making it compatible with .Net, .Net Core and Unity3D among things!

Carefully read the Usage section for a brief introduction on how to use the library.

What does ByteStream offer?

Bytestream offers 2 sets of serializers and deserializers, called writers and readers. They are solely used for manual serialization and deserialization of the supported types.

  • Easy to understand, simple syntax.
  • Lightweight, no memory allocation.
  • Extremely fast, using pointer conversion to read and write data.
  • Uses memory copy to quickly copy large blocks of memory to and from the buffers.
  • Comes in a managed and unmanaged variant.
  • Auto-resizable, managed buffer (optional).
  • Automatically keeps track of offsets and buffer boundaries.
  • Prefixes strings and memory blocks with a 2-byte length (optional).

Supported types:

  • All types that adhere to the unmanaged constraint can be written to, and read from the buffers.
  • Byte arrays
  • ANSI strings (1-byte per character)
  • UTF16 strings (2-byte per character, default in C#)
  • Any string that's supported by .NET's Encoding class.

User-defined structs may change their layout when used on another system. Don't serialize user-defined structs unless you are absolutely certain the layout will be the same everywhere it is read back.

Drawbacks

  • The ByteWriter and ByteReader makes heavy use of memory pinning. This can make the unmanaged variant preferable when possible.
  • The writers and readers always make use of pointer conversion. In the case of writing single-byte values to a byte[] buffer, consider directly writing these values to the buffer instead.

Technical specifications:

Comparison to other methods:

Writing of 1024 integer (4-byte) values to a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteWrite2.199 us0.0048 us0.0043 us0.57--
PtrWrite1.651 us0.0074 us0.0069 us0.43--
ManagedStream1.705 us0.0012 us0.0010 us0.44--
UnmanagedStream1.351 us0.0058 us0.0055 us0.35--
ArraySegment3.875 us0.0050 us0.0042 us1.00--
BitConvert10.762 us0.0227 us0.0201 us2.785.188032768 B
Union9.001 us0.0438 us0.0410 us2.32--

Reading of 1024 integer (4-byte) values from a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteRead1.311 us0.0045 us0.0042 us0.35--
PtrRead1.239 us0.0055 us0.0052 us0.33--
ManagedStream1.172 us0.0012 us0.0012 us0.32--
UnmanagedStream1.192 us0.0048 us0.0045 us0.32--
ArraySegment3.712 us0.0085 us0.0080 us1.00--
BitConvert1.542 us0.0046 us0.0043 us0.42--
Union9.421 us0.0336 us0.0314 us2.54--

When using ArraySegment, BitConverter and Unioning structs the user needs to keep track of the offsets themselves. Manual bitshifting may also be required to read and write values.

Usage

Because the API is almost identical, the managed and unmanaged readers and writers function nearly identical. Be warned that when using the unmanaged reader or writer, the provided length must be no longer than the actual length of the memory block provided.

It is important that you write and read back the values in the same order to keep data consistent (as shown in the write and read example).

Because the readers and writers are value types, they must be passed along using the ref keyword.

The readers and writers are not thread safe! Beware of accessing buffers concurrently from multiple threads and modifying the supplied buffers during operation!

Writing

Writing of values is really easy. Just wrap a buffer and you can start writing!

//Creating a simple buffer that resizes when the limit it met.ByteWriterwriter=newByteWriter(64,false);//Wrapping a pre-existing bufferbyte[]buffer=newbyte[128];ByteWriterwriter=newByteWriter(buffer);//Writing an ANSI string with a prefixed length.writer.WriteANSI("Writing an integer value...",true);//Writing an integer value.writer.Write(591823);//Copying the internal buffer to a new byte array.byte[]copy=newbyte[32];writer.CopyTo(copy);//Gets the original buffer (is equal to the above defined "buffer")byte[]originalBuffer=writer.Buffer;

Reading

Reading values is just as easy as writing them! Again, just wrap a buffer and you can start reading.

byte[]buffer;//Some earlier defined buffer that holds data.ByteReaderreader=newByteReader(buffer);//Read an ANSI string (automatically grabs the prefixed size).stringstringValue=reader.ReadANSI();//Read an integer value.intintValue=reader.Read<int>();

Defining custom types

We can create extension methods to allow serializing and deserializing of user-defined types (even classes!). Be sure to add the ref keyword to ensure the offset gets incremented!

publicstaticvoidWritePoint(refthisByteWriterwriter,Pointpoint){writer.Write(point.X);writer.Write(point.Y);}publicstaticPointReadPoint(refthisByteReaderreader){returnnewPoint(reader.Read<int>(),reader.Read<int>());}//We can then use these extension methods from anywhere elseByteWriterwriter=newByteWriter(buffer);writer.WritePoint(newPoint(1,2));ByteReaderreader=newByteReader(buffer);varpoint=reader.ReadPoint();

Streaming classes

The ManagedSteam and the UnmanagedStream make serialization of objects even easier. Below is an example of how to serialize a class using the IByteStream interface that comes with both of the aforementioned Streams.

The Stream can simply be passed to the Serialize method. Depending if the Stream is in Write or Read mode, the object is serialized or deserialized automatically.

publicclassPlayerData{publicstringName;publicintHealth;publicfloatSpeed;publicPlayerInventoryInventory;publicvoidSerialize(IByteStreamstream){stream.SerializeString(refName,Encoding.ASCII);stream.Serialize(refHealth);stream.Serialize(refSpeed);Inventory.Serialize(stream);}}

Extending either of the Stream classes (or the IByteStream Interface) is equally as easy as using them to serialize. Below is an example of a Serialize method for a Vector3 by extending IByteStream.

internalstaticclassExtension{publicstaticvoidSerialize(thisIByteStreamstream,refVector3vector){stream.Serialize(refvector.X);stream.Serialize(refvector.Y);stream.Serialize(refvector.Z);}}

About

A blazing fast byte (de)serializer

Topics

Resources

Stars

23 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DennisCorvers/ByteStream: A blazing fast byte (de)serializer · GitHub
Skip to content

Latest commit

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ByteStream

A blazing fast byte (de)serializer for C#.

Available on NuGet via Install-Package ByteStream

What is ByteStream?

Bytestream is a small library that enables blazing fast serialization of a collection of types to raw bytes. Either in the form of a byte array byte[] or unmanaged memory IntPtr. The library performs no memory allocation on its own, so you are free to use your own memory allocator!

ByteStream is written in NetStandard2.0 making it compatible with .Net, .Net Core and Unity3D among things!

Carefully read the Usage section for a brief introduction on how to use the library.

What does ByteStream offer?

Bytestream offers 2 sets of serializers and deserializers, called writers and readers. They are solely used for manual serialization and deserialization of the supported types.

  • Easy to understand, simple syntax.
  • Lightweight, no memory allocation.
  • Extremely fast, using pointer conversion to read and write data.
  • Uses memory copy to quickly copy large blocks of memory to and from the buffers.
  • Comes in a managed and unmanaged variant.
  • Auto-resizable, managed buffer (optional).
  • Automatically keeps track of offsets and buffer boundaries.
  • Prefixes strings and memory blocks with a 2-byte length (optional).

Supported types:

  • All types that adhere to the unmanaged constraint can be written to, and read from the buffers.
  • Byte arrays
  • ANSI strings (1-byte per character)
  • UTF16 strings (2-byte per character, default in C#)
  • Any string that's supported by .NET's Encoding class.

User-defined structs may change their layout when used on another system. Don't serialize user-defined structs unless you are absolutely certain the layout will be the same everywhere it is read back.

Drawbacks

  • The ByteWriter and ByteReader makes heavy use of memory pinning. This can make the unmanaged variant preferable when possible.
  • The writers and readers always make use of pointer conversion. In the case of writing single-byte values to a byte[] buffer, consider directly writing these values to the buffer instead.

Technical specifications:

Comparison to other methods:

Writing of 1024 integer (4-byte) values to a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteWrite2.199 us0.0048 us0.0043 us0.57--
PtrWrite1.651 us0.0074 us0.0069 us0.43--
ManagedStream1.705 us0.0012 us0.0010 us0.44--
UnmanagedStream1.351 us0.0058 us0.0055 us0.35--
ArraySegment3.875 us0.0050 us0.0042 us1.00--
BitConvert10.762 us0.0227 us0.0201 us2.785.188032768 B
Union9.001 us0.0438 us0.0410 us2.32--

Reading of 1024 integer (4-byte) values from a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteRead1.311 us0.0045 us0.0042 us0.35--
PtrRead1.239 us0.0055 us0.0052 us0.33--
ManagedStream1.172 us0.0012 us0.0012 us0.32--
UnmanagedStream1.192 us0.0048 us0.0045 us0.32--
ArraySegment3.712 us0.0085 us0.0080 us1.00--
BitConvert1.542 us0.0046 us0.0043 us0.42--
Union9.421 us0.0336 us0.0314 us2.54--

When using ArraySegment, BitConverter and Unioning structs the user needs to keep track of the offsets themselves. Manual bitshifting may also be required to read and write values.

Usage

Because the API is almost identical, the managed and unmanaged readers and writers function nearly identical. Be warned that when using the unmanaged reader or writer, the provided length must be no longer than the actual length of the memory block provided.

It is important that you write and read back the values in the same order to keep data consistent (as shown in the write and read example).

Because the readers and writers are value types, they must be passed along using the ref keyword.

The readers and writers are not thread safe! Beware of accessing buffers concurrently from multiple threads and modifying the supplied buffers during operation!

Writing

Writing of values is really easy. Just wrap a buffer and you can start writing!

//Creating a simple buffer that resizes when the limit it met.ByteWriterwriter=newByteWriter(64,false);//Wrapping a pre-existing bufferbyte[]buffer=newbyte[128];ByteWriterwriter=newByteWriter(buffer);//Writing an ANSI string with a prefixed length.writer.WriteANSI("Writing an integer value...",true);//Writing an integer value.writer.Write(591823);//Copying the internal buffer to a new byte array.byte[]copy=newbyte[32];writer.CopyTo(copy);//Gets the original buffer (is equal to the above defined "buffer")byte[]originalBuffer=writer.Buffer;

Reading

Reading values is just as easy as writing them! Again, just wrap a buffer and you can start reading.

byte[]buffer;//Some earlier defined buffer that holds data.ByteReaderreader=newByteReader(buffer);//Read an ANSI string (automatically grabs the prefixed size).stringstringValue=reader.ReadANSI();//Read an integer value.intintValue=reader.Read<int>();

Defining custom types

We can create extension methods to allow serializing and deserializing of user-defined types (even classes!). Be sure to add the ref keyword to ensure the offset gets incremented!

publicstaticvoidWritePoint(refthisByteWriterwriter,Pointpoint){writer.Write(point.X);writer.Write(point.Y);}publicstaticPointReadPoint(refthisByteReaderreader){returnnewPoint(reader.Read<int>(),reader.Read<int>());}//We can then use these extension methods from anywhere elseByteWriterwriter=newByteWriter(buffer);writer.WritePoint(newPoint(1,2));ByteReaderreader=newByteReader(buffer);varpoint=reader.ReadPoint();

Streaming classes

The ManagedSteam and the UnmanagedStream make serialization of objects even easier. Below is an example of how to serialize a class using the IByteStream interface that comes with both of the aforementioned Streams.

The Stream can simply be passed to the Serialize method. Depending if the Stream is in Write or Read mode, the object is serialized or deserialized automatically.

publicclassPlayerData{publicstringName;publicintHealth;publicfloatSpeed;publicPlayerInventoryInventory;publicvoidSerialize(IByteStreamstream){stream.SerializeString(refName,Encoding.ASCII);stream.Serialize(refHealth);stream.Serialize(refSpeed);Inventory.Serialize(stream);}}

Extending either of the Stream classes (or the IByteStream Interface) is equally as easy as using them to serialize. Below is an example of a Serialize method for a Vector3 by extending IByteStream.

internalstaticclassExtension{publicstaticvoidSerialize(thisIByteStreamstream,refVector3vector){stream.Serialize(refvector.X);stream.Serialize(refvector.Y);stream.Serialize(refvector.Z);}}

About

A blazing fast byte (de)serializer

Topics

Resources

Stars

23 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DennisCorvers/ByteStream: A blazing fast byte (de)serializer · GitHub
Skip to content

Latest commit

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ByteStream

A blazing fast byte (de)serializer for C#.

Available on NuGet via Install-Package ByteStream

What is ByteStream?

Bytestream is a small library that enables blazing fast serialization of a collection of types to raw bytes. Either in the form of a byte array byte[] or unmanaged memory IntPtr. The library performs no memory allocation on its own, so you are free to use your own memory allocator!

ByteStream is written in NetStandard2.0 making it compatible with .Net, .Net Core and Unity3D among things!

Carefully read the Usage section for a brief introduction on how to use the library.

What does ByteStream offer?

Bytestream offers 2 sets of serializers and deserializers, called writers and readers. They are solely used for manual serialization and deserialization of the supported types.

  • Easy to understand, simple syntax.
  • Lightweight, no memory allocation.
  • Extremely fast, using pointer conversion to read and write data.
  • Uses memory copy to quickly copy large blocks of memory to and from the buffers.
  • Comes in a managed and unmanaged variant.
  • Auto-resizable, managed buffer (optional).
  • Automatically keeps track of offsets and buffer boundaries.
  • Prefixes strings and memory blocks with a 2-byte length (optional).

Supported types:

  • All types that adhere to the unmanaged constraint can be written to, and read from the buffers.
  • Byte arrays
  • ANSI strings (1-byte per character)
  • UTF16 strings (2-byte per character, default in C#)
  • Any string that's supported by .NET's Encoding class.

User-defined structs may change their layout when used on another system. Don't serialize user-defined structs unless you are absolutely certain the layout will be the same everywhere it is read back.

Drawbacks

  • The ByteWriter and ByteReader makes heavy use of memory pinning. This can make the unmanaged variant preferable when possible.
  • The writers and readers always make use of pointer conversion. In the case of writing single-byte values to a byte[] buffer, consider directly writing these values to the buffer instead.

Technical specifications:

Comparison to other methods:

Writing of 1024 integer (4-byte) values to a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteWrite2.199 us0.0048 us0.0043 us0.57--
PtrWrite1.651 us0.0074 us0.0069 us0.43--
ManagedStream1.705 us0.0012 us0.0010 us0.44--
UnmanagedStream1.351 us0.0058 us0.0055 us0.35--
ArraySegment3.875 us0.0050 us0.0042 us1.00--
BitConvert10.762 us0.0227 us0.0201 us2.785.188032768 B
Union9.001 us0.0438 us0.0410 us2.32--

Reading of 1024 integer (4-byte) values from a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteRead1.311 us0.0045 us0.0042 us0.35--
PtrRead1.239 us0.0055 us0.0052 us0.33--
ManagedStream1.172 us0.0012 us0.0012 us0.32--
UnmanagedStream1.192 us0.0048 us0.0045 us0.32--
ArraySegment3.712 us0.0085 us0.0080 us1.00--
BitConvert1.542 us0.0046 us0.0043 us0.42--
Union9.421 us0.0336 us0.0314 us2.54--

When using ArraySegment, BitConverter and Unioning structs the user needs to keep track of the offsets themselves. Manual bitshifting may also be required to read and write values.

Usage

Because the API is almost identical, the managed and unmanaged readers and writers function nearly identical. Be warned that when using the unmanaged reader or writer, the provided length must be no longer than the actual length of the memory block provided.

It is important that you write and read back the values in the same order to keep data consistent (as shown in the write and read example).

Because the readers and writers are value types, they must be passed along using the ref keyword.

The readers and writers are not thread safe! Beware of accessing buffers concurrently from multiple threads and modifying the supplied buffers during operation!

Writing

Writing of values is really easy. Just wrap a buffer and you can start writing!

//Creating a simple buffer that resizes when the limit it met.ByteWriterwriter=newByteWriter(64,false);//Wrapping a pre-existing bufferbyte[]buffer=newbyte[128];ByteWriterwriter=newByteWriter(buffer);//Writing an ANSI string with a prefixed length.writer.WriteANSI("Writing an integer value...",true);//Writing an integer value.writer.Write(591823);//Copying the internal buffer to a new byte array.byte[]copy=newbyte[32];writer.CopyTo(copy);//Gets the original buffer (is equal to the above defined "buffer")byte[]originalBuffer=writer.Buffer;

Reading

Reading values is just as easy as writing them! Again, just wrap a buffer and you can start reading.

byte[]buffer;//Some earlier defined buffer that holds data.ByteReaderreader=newByteReader(buffer);//Read an ANSI string (automatically grabs the prefixed size).stringstringValue=reader.ReadANSI();//Read an integer value.intintValue=reader.Read<int>();

Defining custom types

We can create extension methods to allow serializing and deserializing of user-defined types (even classes!). Be sure to add the ref keyword to ensure the offset gets incremented!

publicstaticvoidWritePoint(refthisByteWriterwriter,Pointpoint){writer.Write(point.X);writer.Write(point.Y);}publicstaticPointReadPoint(refthisByteReaderreader){returnnewPoint(reader.Read<int>(),reader.Read<int>());}//We can then use these extension methods from anywhere elseByteWriterwriter=newByteWriter(buffer);writer.WritePoint(newPoint(1,2));ByteReaderreader=newByteReader(buffer);varpoint=reader.ReadPoint();

Streaming classes

The ManagedSteam and the UnmanagedStream make serialization of objects even easier. Below is an example of how to serialize a class using the IByteStream interface that comes with both of the aforementioned Streams.

The Stream can simply be passed to the Serialize method. Depending if the Stream is in Write or Read mode, the object is serialized or deserialized automatically.

publicclassPlayerData{publicstringName;publicintHealth;publicfloatSpeed;publicPlayerInventoryInventory;publicvoidSerialize(IByteStreamstream){stream.SerializeString(refName,Encoding.ASCII);stream.Serialize(refHealth);stream.Serialize(refSpeed);Inventory.Serialize(stream);}}

Extending either of the Stream classes (or the IByteStream Interface) is equally as easy as using them to serialize. Below is an example of a Serialize method for a Vector3 by extending IByteStream.

internalstaticclassExtension{publicstaticvoidSerialize(thisIByteStreamstream,refVector3vector){stream.Serialize(refvector.X);stream.Serialize(refvector.Y);stream.Serialize(refvector.Z);}}

About

A blazing fast byte (de)serializer

Topics

Resources

Stars

23 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - DennisCorvers/ByteStream: A blazing fast byte (de)serializer · GitHub
Skip to content

Latest commit

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ByteStream

A blazing fast byte (de)serializer for C#.

Available on NuGet via Install-Package ByteStream

What is ByteStream?

Bytestream is a small library that enables blazing fast serialization of a collection of types to raw bytes. Either in the form of a byte array byte[] or unmanaged memory IntPtr. The library performs no memory allocation on its own, so you are free to use your own memory allocator!

ByteStream is written in NetStandard2.0 making it compatible with .Net, .Net Core and Unity3D among things!

Carefully read the Usage section for a brief introduction on how to use the library.

What does ByteStream offer?

Bytestream offers 2 sets of serializers and deserializers, called writers and readers. They are solely used for manual serialization and deserialization of the supported types.

  • Easy to understand, simple syntax.
  • Lightweight, no memory allocation.
  • Extremely fast, using pointer conversion to read and write data.
  • Uses memory copy to quickly copy large blocks of memory to and from the buffers.
  • Comes in a managed and unmanaged variant.
  • Auto-resizable, managed buffer (optional).
  • Automatically keeps track of offsets and buffer boundaries.
  • Prefixes strings and memory blocks with a 2-byte length (optional).

Supported types:

  • All types that adhere to the unmanaged constraint can be written to, and read from the buffers.
  • Byte arrays
  • ANSI strings (1-byte per character)
  • UTF16 strings (2-byte per character, default in C#)
  • Any string that's supported by .NET's Encoding class.

User-defined structs may change their layout when used on another system. Don't serialize user-defined structs unless you are absolutely certain the layout will be the same everywhere it is read back.

Drawbacks

  • The ByteWriter and ByteReader makes heavy use of memory pinning. This can make the unmanaged variant preferable when possible.
  • The writers and readers always make use of pointer conversion. In the case of writing single-byte values to a byte[] buffer, consider directly writing these values to the buffer instead.

Technical specifications:

Comparison to other methods:

Writing of 1024 integer (4-byte) values to a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteWrite2.199 us0.0048 us0.0043 us0.57--
PtrWrite1.651 us0.0074 us0.0069 us0.43--
ManagedStream1.705 us0.0012 us0.0010 us0.44--
UnmanagedStream1.351 us0.0058 us0.0055 us0.35--
ArraySegment3.875 us0.0050 us0.0042 us1.00--
BitConvert10.762 us0.0227 us0.0201 us2.785.188032768 B
Union9.001 us0.0438 us0.0410 us2.32--

Reading of 1024 integer (4-byte) values from a buffer.

MethodMeanErrorStdDevRatioGen 0Allocated
ByteRead1.311 us0.0045 us0.0042 us0.35--
PtrRead1.239 us0.0055 us0.0052 us0.33--
ManagedStream1.172 us0.0012 us0.0012 us0.32--
UnmanagedStream1.192 us0.0048 us0.0045 us0.32--
ArraySegment3.712 us0.0085 us0.0080 us1.00--
BitConvert1.542 us0.0046 us0.0043 us0.42--
Union9.421 us0.0336 us0.0314 us2.54--

When using ArraySegment, BitConverter and Unioning structs the user needs to keep track of the offsets themselves. Manual bitshifting may also be required to read and write values.

Usage

Because the API is almost identical, the managed and unmanaged readers and writers function nearly identical. Be warned that when using the unmanaged reader or writer, the provided length must be no longer than the actual length of the memory block provided.

It is important that you write and read back the values in the same order to keep data consistent (as shown in the write and read example).

Because the readers and writers are value types, they must be passed along using the ref keyword.

The readers and writers are not thread safe! Beware of accessing buffers concurrently from multiple threads and modifying the supplied buffers during operation!

Writing

Writing of values is really easy. Just wrap a buffer and you can start writing!

//Creating a simple buffer that resizes when the limit it met.ByteWriterwriter=newByteWriter(64,false);//Wrapping a pre-existing bufferbyte[]buffer=newbyte[128];ByteWriterwriter=newByteWriter(buffer);//Writing an ANSI string with a prefixed length.writer.WriteANSI("Writing an integer value...",true);//Writing an integer value.writer.Write(591823);//Copying the internal buffer to a new byte array.byte[]copy=newbyte[32];writer.CopyTo(copy);//Gets the original buffer (is equal to the above defined "buffer")byte[]originalBuffer=writer.Buffer;

Reading

Reading values is just as easy as writing them! Again, just wrap a buffer and you can start reading.

byte[]buffer;//Some earlier defined buffer that holds data.ByteReaderreader=newByteReader(buffer);//Read an ANSI string (automatically grabs the prefixed size).stringstringValue=reader.ReadANSI();//Read an integer value.intintValue=reader.Read<int>();

Defining custom types

We can create extension methods to allow serializing and deserializing of user-defined types (even classes!). Be sure to add the ref keyword to ensure the offset gets incremented!

publicstaticvoidWritePoint(refthisByteWriterwriter,Pointpoint){writer.Write(point.X);writer.Write(point.Y);}publicstaticPointReadPoint(refthisByteReaderreader){returnnewPoint(reader.Read<int>(),reader.Read<int>());}//We can then use these extension methods from anywhere elseByteWriterwriter=newByteWriter(buffer);writer.WritePoint(newPoint(1,2));ByteReaderreader=newByteReader(buffer);varpoint=reader.ReadPoint();

Streaming classes

The ManagedSteam and the UnmanagedStream make serialization of objects even easier. Below is an example of how to serialize a class using the IByteStream interface that comes with both of the aforementioned Streams.

The Stream can simply be passed to the Serialize method. Depending if the Stream is in Write or Read mode, the object is serialized or deserialized automatically.

publicclassPlayerData{publicstringName;publicintHealth;publicfloatSpeed;publicPlayerInventoryInventory;publicvoidSerialize(IByteStreamstream){stream.SerializeString(refName,Encoding.ASCII);stream.Serialize(refHealth);stream.Serialize(refSpeed);Inventory.Serialize(stream);}}

Extending either of the Stream classes (or the IByteStream Interface) is equally as easy as using them to serialize. Below is an example of a Serialize method for a Vector3 by extending IByteStream.

internalstaticclassExtension{publicstaticvoidSerialize(thisIByteStreamstream,refVector3vector){stream.Serialize(refvector.X);stream.Serialize(refvector.Y);stream.Serialize(refvector.Z);}}

About

A blazing fast byte (de)serializer

Topics

Resources

Stars

23 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages