我正在为Wayland编写一个小程序,该程序使用软件渲染和wl_shm进行显示。这需要我将屏幕缓冲区的文件描述符传递给Wayland服务器,然后Wayland服务器在其上调用mmap()
,即屏幕缓冲区必须可在进程之间共享。
在这个程序中,启动延迟是关键。目前,只剩下一个瓶颈:屏幕缓冲区的初始绘制,整个缓冲区都被覆盖。下面的代码显示了这个的简化版本:
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
/* Fullscreen buffers are around 10-30 MiB for common resolutions. */
const size_t size = 2880 * 1800 * 4;
int fd = memfd_create("shm", 0);
ftruncate(fd, size);
void *pool = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
/* Ideally, we could just malloc, but this memory needs to be shared. */
//void *pool = malloc(size);
/* In reality this is a cairo_paint() call. */
memset(pool, 0xCF, size);
/* Subsequent paints (or memsets) after the first take negligible time. */
}
在我的笔记本电脑上,上面的memset()
大约需要21-28毫秒。切换到malloc()
的内存会将其降至12毫秒,但问题是需要在进程之间共享内存。在我的桌面上的行为类似:mmap()
为7毫秒,malloc()
为3毫秒。
我的问题是:我是否缺少一些可以提高Linux上共享内存性能的东西?我尝试过madvise()
、MADV_WILLNEED
和MADV_SEQUENTIAL
,以及使用mlock()
,但都没有什么不同。我也考虑过2MB的巨大页面是否会有帮助,因为缓冲区大小约为10-30MB,但这通常不可用。
编辑:我试过mmap()
和MAP_ANONYMOUS | MAP_SHARED
,它和以前一样慢。MAP_ANONYMOUS | MAP_PRIVATE
产生与malloc()
相同的速度,但这违背了目的。
malloc()
和mmap()
之间的性能差异似乎是由于透明护肩的不同应用。
默认情况下,在x86_64上,页面大小为4KiB,大页面大小为2MiB。透明的Hugepages允许不了解Hugepages的程序仍然使用它们,从而减少页面错误。然而,这在默认情况下仅对私有匿名内存启用,因此对于设置了MAP_ANONYMOUS | MAP_PRIVATE
的malloc()
和mmap()
,这解释了为什么它们的性能相同。对于共享内存映射,这将被禁用,从而导致更多的页面处理开销(对于我需要的10-30MiB缓冲区(,并导致速度减慢。
可以通过/sys/kernel/mm/transparent_hugepage/shmem_enabled
旋钮为共享内存映射启用Hugepages,如内核文档页面中所述。这默认为never
,但将其设置为always
(或advise
,并添加相应的madvise(..., MADV_HUGEPAGE)
调用(允许使用MAP_SHARED
映射的内存使用hugepage,并且性能与malloc()
的内存匹配。
我不确定为什么共享内存的默认值是never
。虽然不是很令人满意,但目前看来,唯一的解决方案是使用madvise(MADV_HUGEPAGE)
来提高任何碰巧将shmem_enabled
设置为至少advise
的系统的性能(或者如果将来默认启用(。