I have a singleton object that process requests. Each request takes around one millisecond to be completed, usually less. This object is not thread-safe and it expects requests in a particular format, encapsulated in the Request
class, and returns the result as Response
. This processor has another producer/consumer that sends/receives through a socket.
I implemented the producer/consumer approach to work fast:
- Client prepares a
RequestCommand
command object, that contains aTaskCompletionSource<Response>
and the intendedRequest
. - Client add the command to the "request queue" (
Queue<>
) and awaitscommand.Completion.Task
. - A different thread (and actual background
Thread
) pulls the command from the "request queue", process thecommand.Request
, generatesResponse
and signals the command as done usingcommand.Completion.SetResult(response)
. - Client continues working.
But when doing a small memory benchmark I see LOTS of these objects being created and topping the list of most common object in memory. Note that there is no memory leak, the GC can clean everything up nicely each time triggers, but obviously so many objects being created fast, makes Gen 0 very big. I wonder if a better memory usage may yield better performance.
I was considering convert some of these objects to structs to avoid allocations, specially now that there are some new features to work with them C# 7.1. But I do not see a way of doing it.
- Value types can be instantiated in the stack, but if they pass from thread to thread, they must be copied to the stackA->heap and heap->stackB I guess. Also when enqueuing in the queue, it goes from stack to heap.
- The singleton object is truly asynchronous. There is some in-memory processing, but 90% of the time it needs to call outside and going through the internal producer/consumer.
ValueTask<>
does not seem to fit here, because things are asynchronous.TaskCompletionSource<>
has a state, but it isobject
, so it would be boxed.- The command also jumps from thread to thread.
- Reciclying objects only works for the command itself, its content cannot be recycled (
TaskCompletionSource<>
and astring
).
Is there any way I could leverage structs to reduce the memory usage or/and improve the performance? Any other option?