This is the Nimlang code I am using which is a very simple TCP forwarder but it consumes about 6MB of memory to accomplish this in Golang I achieve this same task under 1.5MB of memory and has a very efficient and reliable function io.CopyBuffer() which can be used to connect two connections that reuse its buffer. I could not find any similar function in Nimlang but my problem is I want to lower my memory consumption and achieve equal to go or if possible lower is there a buffer pool concept or buffer reuse concept in Nimlang or any other way I can achieve my goal.
Thank You In Advance.
import strformat, strutils, net, asyncdispatch, asyncnet, strutils
type ForwardOptions = object
listenAddr*: string
listenPort*: Port
toAddr*: string
toPort*: Port
type Forwarder = object of RootObj
options*: ForwardOptions
proc processClient(this: ref Forwarder, client: AsyncSocket) {.async.} =
let remote = newAsyncSocket(buffered=false)
await remote.connect(this.options.toAddr, this.options.toPort)
proc clientHasData() {.async.} =
while not client.isClosed and not remote.isClosed:
let data = await client.recv(1024)
await remote.send(data)
client.close()
remote.close()
proc remoteHasData() {.async.} =
while not remote.isClosed and not client.isClosed:
let data = await remote.recv(1024)
await client.send(data)
client.close()
remote.close()
try:
asyncCheck clientHasData()
asyncCheck remoteHasData()
except:
echo getCurrentExceptionMsg()
proc serve(this: ref Forwarder) {.async.} =
var server = newAsyncSocket(buffered=false)
server.setSockOpt(OptReuseAddr, true)
server.bindAddr(this.options.listenPort, this.options.listenAddr)
echo fmt"Started tcp server... {this.options.listenAddr}:{this.options.listenPort} "
echo fmt"Forwarding To... {this.options.toAddr}:{this.options.toPort}"
server.listen()
while true:
let client = await server.accept()
echo "..Got connection "
asyncCheck this.processClient(client)
proc newForwarder(opts: ForwardOptions): ref Forwarder =
result = new(Forwarder)
result.options = opts
let opts = ForwardOptions(listenAddr:"127.0.0.1", listenPort:11000.Port, toAddr:"127.0.0.1", toPort:80.Port)
var f = newForwarder(opts)
asyncCheck f.serve()
runForever()
You can do something like:
proc clientHasData() {.async.} =
var data = newString(1024)
while not client.isClosed and not remote.isClosed:
let n = await client.recv(data, 1024)
await remote.send(pointer(data.cstring), len(data))
client.close()
remote.close()
proc remoteHasData() {.async.} =
var data = newString(1024)
while not remote.isClosed and not client.isClosed:
let n = await remote.recv(data, 1024)
await client.send(pointer(data.cstring), len(data))
client.close()
remote.close()
I believe in Go the data stays in kernel space and it must use specific syscalls to move the data around with zero copy
http://avtok.com/2014/11/05/interface-upgrades.html
and also
https://www.sobyte.net/post/2022-03/golang-zero-copy/
This is really nice and having a way to tap to these advanced kernel features easily is a strong point for Go. It would be great to have the ability to do the same transparently in Nim, but I still don't know what would be the best way.
The stdlib does provide methods to read data into existing buffer's. I've used it a fair bit on embedded devices, though mostly non-async.
The easiest way to structure it in Nim to prevent copying I've found is to pass the buffer as an openArray parameter. Unfortunately the net functions don't take an openArray so you need to use unsafeAddr buffer[0] to get the data pointer.
You can pass a var string parameter around as well.
See:
I think the non-async model provides the best way to avoid excessive memory. Mummy uses a thread model and has a good example of "low level" network buffer usage:
https://github.com/guzba/mummy/blob/5969a731d8a932ddc809b60d09b864cbd492b5b9/src/mummy.nim#L1308
Also StatusIM's Chronos library focuses on reducing async memory usage too. Also make sure you're using ARC/ORC as well.
@elcritch Tried doing this writes malformed data to remote connection can you provide me little snipet kindly.
thank you
proc clientHasData() {.async.} =
var buf: cstring
while not client.isClosed and not remote.isClosed:
let n = await client.recvInto(addr(buf),1024)
buf.echo
await remote.send(addr(buf), n)
client.close()
remote.close()
This works and reduced to half 3MB but still not near my goal and also slowly memory increases when running for long.
proc remoteHasData() {.async.} =
var buf = newString(1024)
while not remote.isClosed and not remote.isClosed:
let n = await remote.recvInto(unsafeAddr buf[0],1024)
await client.send(unsafeAddr buf[0], n)
client.close()
remote.close()
Perhaps ... ?
proc remoteHasData() {.async.} =
var buf : array[1024 , char]
while not remote.isClosed and not remote.isClosed:
let n = await remote.recvInto(unsafeAddr buf[0],1024)
await client.send(unsafeAddr buf[0], n)
client.close()
remote.close()
Your first attempt with cstring didn't allocate memory for the cstring, hence the data corruption. I'm not sure why the memory usage would increase over time.
It might be async related. You could try Chronos async as they've done a lot of work on memory usage with their async impl. Otherwise you could try non-async if you don't have lots of connections or setup a connection pool.
Also, you could try -d:useMalloc as well. Nim's allocator may just be keeping more pages in memory.
One last bit would be that a buffer size of 1024kb is fairly small. Ethernet packets can be from ~1500kb to 9000kb. The memory usage increases might be due to async buffering. I'd recommend a larger buffer.
Nothing actually work for me seems like can't do it out of the box and need's more then i thought but enjoyed so far. Maybe i will try again when i have more time.
Thank you for your time.