Unverified Commit 46af8e24 authored by Mateusz Guzik's avatar Mateusz Guzik Committed by Christian Brauner
Browse files

pipe: cache 2 pages instead of 1



User data is kept in a circular buffer backed by pages allocated as
needed. Only having space for one spare is still prone to having to
resort to allocation / freeing.

In my testing this decreases page allocs by 60% during a kernel build.

Signed-off-by: default avatarMateusz Guzik <mjguzik@gmail.com>
Link: https://lore.kernel.org/r/20250303230409.452687-3-mjguzik@gmail.com


Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parent a40cd584
Loading
Loading
Loading
Loading
+40 −20
Original line number Diff line number Diff line
@@ -112,20 +112,40 @@ void pipe_double_lock(struct pipe_inode_info *pipe1,
	pipe_lock(pipe2);
}

static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe)
{
	for (int i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) {
		if (pipe->tmp_page[i]) {
			struct page *page = pipe->tmp_page[i];
			pipe->tmp_page[i] = NULL;
			return page;
		}
	}

	return alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
}

static void anon_pipe_put_page(struct pipe_inode_info *pipe,
			       struct page *page)
{
	if (page_count(page) == 1) {
		for (int i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) {
			if (!pipe->tmp_page[i]) {
				pipe->tmp_page[i] = page;
				return;
			}
		}
	}

	put_page(page);
}

static void anon_pipe_buf_release(struct pipe_inode_info *pipe,
				  struct pipe_buffer *buf)
{
	struct page *page = buf->page;

	/*
	 * If nobody else uses this page, and we don't already have a
	 * temporary page, let's keep track of it as a one-deep
	 * allocation cache. (Otherwise just release our reference to it)
	 */
	if (page_count(page) == 1 && !pipe->tmp_page)
		pipe->tmp_page = page;
	else
		put_page(page);
	anon_pipe_put_page(pipe, page);
}

static bool anon_pipe_buf_try_steal(struct pipe_inode_info *pipe,
@@ -493,27 +513,25 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)
		if (!pipe_full(head, pipe->tail, pipe->max_usage)) {
			unsigned int mask = pipe->ring_size - 1;
			struct pipe_buffer *buf;
			struct page *page = pipe->tmp_page;
			struct page *page;
			int copied;

			if (!page) {
				page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
			page = anon_pipe_get_page(pipe);
			if (unlikely(!page)) {
					ret = ret ? : -ENOMEM;
				if (!ret)
					ret = -ENOMEM;
				break;
			}
				pipe->tmp_page = page;
			}

			copied = copy_page_from_iter(page, 0, PAGE_SIZE, from);
			if (unlikely(copied < PAGE_SIZE && iov_iter_count(from))) {
				anon_pipe_put_page(pipe, page);
				if (!ret)
					ret = -EFAULT;
				break;
			}

			pipe->head = head + 1;
			pipe->tmp_page = NULL;
			/* Insert it into the buffer array */
			buf = &pipe->bufs[head & mask];
			buf->page = page;
@@ -846,8 +864,10 @@ void free_pipe_info(struct pipe_inode_info *pipe)
	if (pipe->watch_queue)
		put_watch_queue(pipe->watch_queue);
#endif
	if (pipe->tmp_page)
		__free_page(pipe->tmp_page);
	for (i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) {
		if (pipe->tmp_page[i])
			__free_page(pipe->tmp_page[i]);
	}
	kfree(pipe->bufs);
	kfree(pipe);
}
+1 −1
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ struct pipe_inode_info {
#ifdef CONFIG_WATCH_QUEUE
	bool note_loss;
#endif
	struct page *tmp_page;
	struct page *tmp_page[2];
	struct fasync_struct *fasync_readers;
	struct fasync_struct *fasync_writers;
	struct pipe_buffer *bufs;