Skip to content

Sending an ArraySegment object - #314

Open
kuzux wants to merge 2 commits into
statianzo:masterfrom
kuzux:master
Open

Sending an ArraySegment object#314
kuzux wants to merge 2 commits into
statianzo:masterfrom
kuzux:master

Conversation

@kuzux

@kuzux kuzux commented Jun 6, 2021

Copy link
Copy Markdown

If we are populating a very large buffer and want to send that via a websocket;
we need that buffer to be of "right" size. To doi that, we need to either create a new byte[] for each message, or truncate the large buffer to the message size. If we do the truncration thing for a byte[] object, what we get is an ArraySegment<byte> object, not a new byte[] object. So, it should be possible to pass an ArraySegment instead of just a byte array for binary data.

@AppVeyorBot

Copy link
Copy Markdown

Build Fleck 0.0.61-ci completed (commit ba7ddb68ff by @kuzux)

1 similar comment
@AppVeyorBot

Copy link
Copy Markdown

Build Fleck 0.0.61-ci completed (commit ba7ddb68ff by @kuzux)

@statianzo

Copy link
Copy Markdown
Owner

Could you provide context beyond the new code?

@kuzux

kuzux commented Jun 7, 2021

Copy link
Copy Markdown
Author

Here's the case where that would be useful:

In a codebase using Fleck, I was creating a buffer, filling the buffer in native code and then sending that buffer. Something like

byte[] buf  = new byte[MaxSize];
while(some_condition) {
    // ...
    int bufsize = fillBuffer(buf); // native method called with p/invoke
    byte[] sendBuf = new byte[bufsize];
    buf.CopyTo(sendBuf, 0);
    conn.Send(sendBuf);
    // ...
}

So, I want to send just a portion of that buffer (the part filled by the native code). To extract that part as a new byte array, the only "solution" I was able to find (to creating a smaller byte array from a larger one) was allocating a new buffer and copying the data to that (Causes latency spikes when working with buffers of large data).

The way to do that is, I believe, using an ArraySegment<T> object, which is just a separate "view" to the existing underlying array. So instead of creating a new byte array and copying existing data to it, I can create an ArraySegment object and pass that to the socket

@statianzo

Copy link
Copy Markdown
Owner

And you’ve found a measurable improvement with this code?

I ask because it still writes to a memory stream that gets written to an array.
To avoid allocation of the intermediate array, I believe you’d need to write directly to the socket.

@kuzux

kuzux commented Jun 8, 2021

Copy link
Copy Markdown
Author

About measurable improvement: Well there was measurable (in fact noticeable) improvement when I eliminated other allocate & copy operations elsewhere in the code. So I decided to go on an allocation-hunting spree, essentially. This was the low-hanging fruit.

I know about the memory stream allocation, but eliminating that as well would probably take way too much modification in the source, which I wasn't comfortable doing. After all, having 2 allocation & copy operations made a significant improvement over having 3. Having 1 would still be better than having 2 of them (Even if it's not as good as having 0 :D)

Comment thread src/Fleck/Handlers/Hybi13Handler.cs Outdated
memoryStream.WriteByte((byte)payload.Count);
}

memoryStream.Write(payload.Array, 0, payload.Count);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should 0 be payload.Offset so it uses the correct range from the Segment's array?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, fixed that


public static byte[] FrameData(ArraySegment<byte> payload, FrameType frameType)
{
var memoryStream = new MemoryStream();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another allocation that could be skipped is if the MemoryStream is initialized with a starting buffer then memoryStream.toArray will just return the underlying array. To determine the buffer length, we'd just need to defer creating it until after calculating lengthBytes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with that is, the returned array from MemoryStream.ToArray() still has the length of the length of the underlying buffer (See https://replit.com/@kuzux/memorystreamarray#main.cs). The length of that array is then used by the SendBytes function. If we want it to function correctly, we'll need to initialize the underlying buffer for each message.

Unless you were talking about the extra allocation that happens after writing a large amount of bytes to the stream.

@AppVeyorBot

Copy link
Copy Markdown

Build Fleck 0.0.62-ci completed (commit 4d644b9f28 by @kuzux)

1 similar comment
@AppVeyorBot

Copy link
Copy Markdown

Build Fleck 0.0.62-ci completed (commit 4d644b9f28 by @kuzux)

@dougbenham

Copy link
Copy Markdown

+1 for this, definitely a needed feature

@dougbenham

Copy link
Copy Markdown

@kuzux Don't forget to add the Send method to the IWebSocketConnection!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants