1

实验在 Linux,x86 32 位。

所以假设在我的汇编程序中,我需要定期(例如每次执行 100000 个基本块后)将 .bss 部分中的数组从内存转储到磁盘。数组的起始地址和大小是固定的。该数组记录了执行的基本块的地址,现在大小为16M。

我尝试编写一些本机代码,memcpy从 .bss 部分到堆栈,然后将其写回磁盘。但是在我看来这很乏味,我担心性能和内存消耗,比如说,每次在堆栈上分配一个非常大的内存......

所以这是我的问题,如何以有效的方式从全局数据部分转储内存?我够清楚吗?

4

2 回答 2

2

首先,不要在 asm 中编写这部分代码,尤其是。一开始不是。编写一个 C 函数来处理这部分,并从 asm.xml 中调用它。如果您需要对仅在需要转储另一个 16MiB 时才运行的部分进行性能调整,那么您可以手动调整它。系统级编程就是检查来自系统调用(或 C stdio 函数)的错误返回,而在 asm 中这样做会很痛苦。

显然,您可以在 asm 中编写任何内容,因为与 C 相比,进行系统调用并没有什么特别之处。与 C 相比,在 asm 中没有比 C 更容易的任何部分,除了可能会在MFENCE锁定周围抛出一个。

无论如何,我已经解决了您希望缓冲区发生的三个变化:

  1. 原地覆盖相同的缓冲区 ( mmap(2)/ msync(2))
  2. 将缓冲区的快照附加到文件(使用write(2)或可能不工作的零拷贝vmsplice(2)+splice(2)想法。)
  3. 在写入旧缓冲区后启动一个新的(归零)缓冲区。 mmap(2)输出文件的顺序块。

就地覆盖

如果您只想每次都覆盖磁盘的同一区域,mmap(2)请使用一个文件并将其用作您的阵列。(定期调用msync(2)以强制将数据写入磁盘。)但是,mmapped 方法不能保证文件的一致状态。写入可以根据请求刷新到磁盘。IDK,如果有办法通过任何形式的保证来避免这种情况(即不仅仅是选择缓冲区刷新计时器等等,所以你的页面通常不会被写入,除非msync(2).)

附加快照

将缓冲区附加到文件的简单方法是在您希望将其写入时简单地调用write(2)write(2)做你需要的一切。如果您的程序是多线程的,您可能需要在系统调用之前对数据进行锁定,然后再释放锁定。我不确定 write 系统调用返回的速度有多快。它可能只有在内核将您的数据复制到页面缓存后才会返回。

如果你只需要一个快照,但所有写入缓冲区的都是原子事务(即缓冲区始终处于一致状态,而不是需要相互一致的值对),那么你不需要采取调用前的锁write(2)。在这种情况下会有少量的偏差(假设内核按顺序复制,缓冲区末尾的数据将比开始时的数据稍晚)。

IDK 如果write(2)使用直接 IO(零拷贝,绕过页面缓存)返回更慢或更快。 您的文件通常open(2)带有。O_DIRECTwrite(2)

如果要写入缓冲区的快照然后继续修改它,则必须在过程中的某处有一个副本。或者 MMU 写时复制诡计:

零拷贝追加快照

有一个 API 用于将用户页面零拷贝写入磁盘文件。Linux 的vmsplice(2)顺序splice(2)将让您告诉内核将您的页面映射到页面缓存中。如果没有SPLICE_F_GIFT,我假设它将它们设置为写时复制。(哎呀,实际上手册页说没有SPLICE_F_GIFT,以下splice(2)内容必须复制。所以如果有一种机制可以获取写时复制语义的机制。)

假设有一种方法可以为您的页面获取写时复制语义,直到内核完成将它们写入磁盘并可以释放它们:

进一步的写入可能需要内核在数据到达磁盘之前 memcpy 一两页,但保存复制整个缓冲区。无论如何,软页面错误和页表操作开销可能不值得,除非您的数据访问模式在短时间内非常空间本地化,直到写入命中磁盘并且可以释放要写入的页面。(我认为不存在以这种方式工作的 API,因为没有机制可以在页面到达磁盘后立即释放它们。Linux 想要接管它们并将它们保存在页面缓存中。)

我从来没有使用过 vmsplice,所以我可能弄错了一些细节。

如果有一种方法可以为同一内存创建一个新的写时复制映射,可能是mmap通过创建一个临时文件的新映射(在 tmpfs 文件系统上,/dev/shm可能。 . 然后,您可以将快照传递给write(2),并在发生太多写时复制页面错误之前尽快取消映射。

每个块的新缓冲区

如果可以在每次写入后从零缓冲区开始,您可以mmap(2)连续的文件块,因此您生成的数据总是已经在正确的位置。

  • (可选)fallocate(2)输出文件中的一些空间,以防止在您的写入模式不是顺序的情况下出现碎片。
  • mmap(2)您的缓冲区到输出文件的前 16MiB。
  • 正常运行
  • 当您想继续下一个 16MiB 时:
    1. take a lock to prevent other threads from using the buffer
    2. munmap(2) your buffer
    3. mmap(2) the next 16MiB of the file to the same address, so you don't need to pass the new address around to writers. These pages will be pre-zeroed, as required by POSIX (can't have the kernel exposing memory).
    4. release the lock

Possibly mmap(buf, 16MiB, ... MAP_FIXED, fd, new_offset) could replace the munmap / mmap pair. MAP_FIXED discards old mmapings that it overlaps. I assume this doesn't mean that modifications to the file / shared memory are discarded, but rather that the actual mapping changes, even without an munmap.

于 2015-07-18T09:13:02.370 回答
1

彼得的回答中对附加快照案例的两个澄清。

1. 无添加O_DIRECT

正如彼得所说,如果您不使用O_DIRECT,write()将在数据复制到页面缓存后立即返回。如果页面缓存已满,它将阻塞,直到某些过时的页面被刷新到磁盘。

如果您只是附加数据而不读取它(很快),您可以受益于定期调用sync_file_range(2)以调度先前写入的页面的刷新并posix_fadvise(2)使用POSIX_FADV_DONTNEED标志从页面缓存中删除已经刷新的页面。write()这可以显着降低阻塞的可能性。

2. 附加O_DIRECT

使用O_DIRECT,write()通常会阻塞,直到数据被发送到磁盘(尽管没有严格保证,请参见此处)。由于这很慢,如果您需要非阻塞写入,请准备好实现您自己的 I/O 调度。

您可以归档的好处是:更可预测的行为(您可以控制何时阻塞)并且可能通过应用程序和内核的协作减少内存和 CPU 使用率。

于 2015-07-23T10:52:53.513 回答