修改数组 vs seq 的数据并将数组 vs seq 的地址传递给 asyncnet proc `send`

Modifying the data of an array vs seq and passing the address of an array vs seq to asyncnet proc `send`

我一直在研究希望通过缓冲区接收数据的服务器。我有一个这样定义的对象和一些修改其中缓冲区的过程:

Packet* = ref object
  buf*: seq[int8]
  #buf*: array[0..4096, int8]
  pos*: int

proc newPacket*(size: int): Packet =
  result = Packet(buf: newSeq[int8](size))
  #result = Packet()

proc sendPacket*(s: AsyncSocket, p: Packet) =
  aSyncCheck s.send(addr(p.buf), p.pos)

现在我有两行评论的原因是因为那是我最初使用的代码,但是创建一个每次都初始化一个包含 4096 个元素的数组的对象可能对性能来说不是很好。但是,它有效,而 seq[int8] 版本无效。

奇怪的是,如果我使用旧的静态缓冲区 buf*: array[0..4096, int8],我当前的代码将工作得很好。在 sendPacket 中,我确保检查缓冲区中包含的数据以确保数组和 seq[int8] 版本相等,而且它们是相等的。 (或者至少看起来是)。换句话说,如果我要执行 var p = createPacket(17) 并用恰好 17 个字节写入 p.buf,则元素的值在两个版本中看起来是相同的。

所以尽管两个版本中的数据看起来相同,但在传递缓冲区地址时调用 send 时我得到了不同的结果。

以防万一,数据会这样读:

result = p.buf[p.pos]
inc(p.pos)

并这样写:

p.buf[p.pos] = cast[int8](value)
inc(p.pos)

我调查过的一些事情可能与我的问题无关:我查看了 GC_refGC_unref,它们对我的问题没有影响,并且还查看了 maybe尝试使用 alloc0,其中 buf 被定义为 pointer,但我似乎无法访问该指针的数据,这可能不是我首先应该做的。此外,如果我执行 var data = p.buf 并传递 data 的地址,我会得到不同的结果,但仍然不是预期的结果。

所以我想我想弄清楚的是:

  1. 为什么当我使用 array[0..4096, int8] 而不是使用 newSeq 初始化的 seq[int8]send 工作得很好,即使它们看起来包含相同的数据?
  2. 我当前用于接收和写入数据的布局在像 Nim 这样的语言(或与此相关的任何语言)中是否有意义?有没有更好的办法?

为了不初始化数组,您可以像这样使用 noinit pragma:

buf* {.noinit.}: array[0..4096, int8]

您可能正在使用指向 seq 的指针,而不是指向 seq 中数据的指针,因此请尝试使用 addr(p.buf[0])

如果您使用的是 seq 版本,pos 字段是无用的,因为您已经有 p.buf.len,但您可能已经知道并只是将其留在数组中。如果你想使用 seq 并期望大数据包,请确保使用 newSeqOfCap 只分配一次内存。

此外,您的数组太大了 1 个字节,它从 0 到 4096(含)!相反,您可以使用 [0..4095, int8] 或仅使用 [4096, int8].

我个人更喜欢在 buf 中使用 uint8 类型,这样你就可以输入 0 到 255 之间的值,而不是 -128 到 127

在 ref 对象内部使用 seq 意味着在访问 buf 时有两层间接访问,以及 GC 必须清理的两个对象。您可以将 Packet 作为 seq[uint8] 的别名(不带 ref):type Packet* = seq[uint8]。或者,如果您想稍后在 Packet 中存储更多数据,则可以使用数组版本。