mbox series

[PATCHSET,v2,0/15] Uncached buffered IO

Message ID 20241110152906.1747545-1-axboe@kernel.dk (mailing list archive)
Headers show
Series Uncached buffered IO | expand

Message

Jens Axboe Nov. 10, 2024, 3:27 p.m. UTC
Hi,

5 years ago I posted patches adding support for RWF_UNCACHED, as a way
to do buffered IO that isn't page cache persistent. The approach back
then was to have private pages for IO, and then get rid of them once IO
was done. But that then runs into all the issues that O_DIRECT has, in
terms of synchronizing with the page cache.

So here's a new approach to the same concent, but using the page cache
as synchronization. That makes RWF_UNCACHED less special, in that it's
just page cache IO, except it prunes the ranges once IO is completed.

Why do this, you may ask? The tldr is that device speeds are only
getting faster, while reclaim is not. Doing normal buffered IO can be
very unpredictable, and suck up a lot of resources on the reclaim side.
This leads people to use O_DIRECT as a work-around, which has its own
set of restrictions in terms of size, offset, and length of IO. It's
also inherently synchronous, and now you need async IO as well. While
the latter isn't necessarily a big problem as we have good options
available there, it also should not be a requirement when all you want
to do is read or write some data without caching.

Even on desktop type systems, a normal NVMe device can fill the entire
page cache in seconds. On the big system I used for testing, there's a
lot more RAM, but also a lot more devices. As can be seen in some of the
results in the following patches, you can still fill RAM in seconds even
when there's 1TB of it. Hence this problem isn't solely a "big
hyperscaler system" issue, it's common across the board.

Common for both reads and writes with RWF_UNCACHED is that they use the
page cache for IO. Reads work just like a normal buffered read would,
with the only exception being that the touched ranges will get pruned
after data has been copied. For writes, the ranges will get writeback
kicked off before the syscall returns, and then writeback completion
will prune the range. Hence writes aren't synchronous, and it's easy to
pipeline writes using RWF_UNCACHED.

File systems need to support this. The patches add support for the
generic filemap helpers, and for iomap. Then ext4 and XFS are marked as
supporting it. The amount of code here is really trivial, and the only
reason the fs opt-in is necessary is to have an RWF_UNCACHED IO return
-EOPNOTSUPP just in case the fs doesn't use either the generic paths or
iomap. Adding "support" to other file systems should be trivial, most of
the time just a one-liner adding FOP_UNCACHED to the fop_flags in the
file_operations struct.

Performance results are in patch 7 for reads and patch 10 for writes,
with the tldr being that I see about a 65% improvement in performance
for both, with fully predictable IO times. CPU reduction is substantial
as well, with no kswapd activity at all for reclaim when using uncached
IO.

Using it from applications is trivial - just set RWF_UNCACHED for the
read or write, using pwritev2(2) or preadv2(2). For io_uring, same
thing, just set RWF_UNCACHED in sqe->rw_flags for a buffered read/write
operation. And that's it.

The goal with this patchset was to make it less special than before. I
think if you look at the diffstat you'll agree that this is the case.

Patches 1..6 are just prep patches, and should have no functional
changes at all. Patch 7 adds support for the filemap path for
RWF_UNCACHED reads, patch 10 adds support for filemap RWF_UNCACHED
writes, and patches 12..15 adds ext4 and xfs/iomap support iomap.

Git tree available here:

https://git.kernel.dk/cgit/linux/log/?h=buffered-uncached.5

 fs/ext4/ext4.h                 |  1 +
 fs/ext4/file.c                 |  2 +-
 fs/ext4/inline.c               |  7 ++-
 fs/ext4/inode.c                | 18 ++++++-
 fs/ext4/page-io.c              | 28 ++++++-----
 fs/iomap/buffered-io.c         | 15 +++++-
 fs/xfs/xfs_aops.c              |  7 ++-
 fs/xfs/xfs_file.c              |  4 +-
 include/linux/fs.h             | 10 +++-
 include/linux/iomap.h          |  4 +-
 include/linux/page-flags.h     |  5 ++
 include/linux/pagemap.h        | 34 +++++++++++++
 include/trace/events/mmflags.h |  3 +-
 include/uapi/linux/fs.h        |  6 ++-
 mm/filemap.c                   | 90 ++++++++++++++++++++++++++++------
 mm/readahead.c                 | 22 +++++++--
 mm/swap.c                      |  2 +
 mm/truncate.c                  |  9 ++--
 18 files changed, 218 insertions(+), 49 deletions(-)

Since v1
- Move iocb->ki_flags checking into filemap_create_folio()
- Use __folio_set_uncached() when marking a folio as uncached right
  after creation
- Shuffle patches around to not have a bisection issue
- Combine some patches
- Add FGP_UNCACHED folio get flag. If set, newly created folios will
  get marked as uncached.
- Add foliop_uncached to be able to pass whether this is an uncached
  folio creation or not into ->write_begin(). Fixes the issue of
  uncached writes pruning existing ranges. Not the prettiest, but the
  prospect of changing ->write_begin() is daunting. I did go that
  route but it's a lot of churn. Have the patch and the scars.
- Ensure that both ext4 and xfs punt to their workqueue handling
  writeback completion for uncached writebacks, as we need workqueue
  context to prune ranges.
- Add generic_uncached_write() helper to make it easy for file systems
  to prune the ranges.
- Add folio_end_uncached() writeback completion helper, and also check
  in_task() in there. Can trigger if the fs needs hints on punting to
  a safe context for this completion, but normal writeback races with
  us and starts writeback on a range that includes uncached folios.
- Rebase on current master

Comments

Matthew Wilcox Nov. 11, 2024, 5:25 p.m. UTC | #1
On Sun, Nov 10, 2024 at 08:27:52AM -0700, Jens Axboe wrote:
> 5 years ago I posted patches adding support for RWF_UNCACHED, as a way
> to do buffered IO that isn't page cache persistent. The approach back
> then was to have private pages for IO, and then get rid of them once IO
> was done. But that then runs into all the issues that O_DIRECT has, in
> terms of synchronizing with the page cache.

Today's a holiday, and I suspect you're going to do a v3 before I have
a chance to do a proper review of this version of the series.

I think "uncached" isn't quite the right word.  Perhaps 'RWF_STREAMING'
so that userspace is indicating that this is a streaming I/O and the
kernel gets to choose what to do with that information.

Also, do we want to fail I/Os to filesystems which don't support
it?  I suppose really sophisticated userspace might fall back to
madvise(DONTNEED), but isn't most userspace going to just clear the flag
and retry the I/O?

Um.  Now I've looked, we also have posix_fadvise(POSIX_FADV_NOREUSE),
which is currently a noop.  But would we be better off honouring
POSIX_FADV_NOREUSE than introducing RWF_UNCACHED?  I'll think about this
some more while I'm offline.
Jens Axboe Nov. 11, 2024, 5:39 p.m. UTC | #2
On 11/11/24 10:25 AM, Matthew Wilcox wrote:
> On Sun, Nov 10, 2024 at 08:27:52AM -0700, Jens Axboe wrote:
>> 5 years ago I posted patches adding support for RWF_UNCACHED, as a way
>> to do buffered IO that isn't page cache persistent. The approach back
>> then was to have private pages for IO, and then get rid of them once IO
>> was done. But that then runs into all the issues that O_DIRECT has, in
>> terms of synchronizing with the page cache.
> 
> Today's a holiday, and I suspect you're going to do a v3 before I have
> a chance to do a proper review of this version of the series.

Probably, since I've done some fixes since v2 :-). So you can wait for
v3, I'll post it later today anyway.

> I think "uncached" isn't quite the right word.  Perhaps 'RWF_STREAMING'
> so that userspace is indicating that this is a streaming I/O and the
> kernel gets to choose what to do with that information.

Yeah not sure, it's the one I used back in the day, and I still haven't
found a more descriptive word for it. That doesn't mean one doesn't
exist, certainly taking suggestions. I don't think STREAMING is the
right one however, you could most certainly be doing random uncached IO.

> Also, do we want to fail I/Os to filesystems which don't support
> it?  I suppose really sophisticated userspace might fall back to
> madvise(DONTNEED), but isn't most userspace going to just clear the flag
> and retry the I/O?

Also something that's a bit undecided, you can make arguments for both
ways. For just ignoring the flag if not support, the argument would be
that the application just wants to do IO, uncached if available. For the
other argument, maybe you have an application that wants to fallback to
O_DIRECT if uncached isn't available. That application certainly wants
to know if it works or not.

Which is why I defaulted to return -EOPNOTSUPP if it's not available.
An applicaton may probe this upfront if it so desires, and just not set
the flag for IO. That'd keep it out of the hot path.

Seems to me that returning whether it's supported or not is the path of
least surprises for applications, which is why I went that way.

> Um.  Now I've looked, we also have posix_fadvise(POSIX_FADV_NOREUSE),
> which is currently a noop.  But would we be better off honouring
> POSIX_FADV_NOREUSE than introducing RWF_UNCACHED?  I'll think about this
> some more while I'm offline.

That would certainly work too, for synchronous IO. But per-file hints
are a bad idea for async IO, for obvious reasons. We really want per-IO
hints for that, we have a long history of messing that up. That doesn't
mean that FMODE_NOREUSE couldn't just set RWF_UNCACHED, if it's set.
That'd be trivial.

Then the next question is if setting POSIX_FADV_NOREUSE should fail of
file->f_op->fop_flags & FOP_UNCACHED isn't true. Probably not, since
it'd potentially break applications. So probably best to just set
f_iocb_flags IFF FOP_UNCACHED is true for that file.

And the bigger question is why on earth do we have this thing in the
kernel that doesn't do anything... But yeah, now we could make it do
something.
Yu Zhao Nov. 11, 2024, 9:24 p.m. UTC | #3
On Mon, Nov 11, 2024 at 10:25 AM Matthew Wilcox <willy@infradead.org> wrote:
>
> On Sun, Nov 10, 2024 at 08:27:52AM -0700, Jens Axboe wrote:
> > 5 years ago I posted patches adding support for RWF_UNCACHED, as a way
> > to do buffered IO that isn't page cache persistent. The approach back
> > then was to have private pages for IO, and then get rid of them once IO
> > was done. But that then runs into all the issues that O_DIRECT has, in
> > terms of synchronizing with the page cache.
>
> Today's a holiday, and I suspect you're going to do a v3 before I have
> a chance to do a proper review of this version of the series.
>
> I think "uncached" isn't quite the right word.  Perhaps 'RWF_STREAMING'
> so that userspace is indicating that this is a streaming I/O and the
> kernel gets to choose what to do with that information.
>
> Also, do we want to fail I/Os to filesystems which don't support
> it?  I suppose really sophisticated userspace might fall back to
> madvise(DONTNEED), but isn't most userspace going to just clear the flag
> and retry the I/O?
>
> Um.  Now I've looked, we also have posix_fadvise(POSIX_FADV_NOREUSE),
> which is currently a noop.

Just to clarify that NOREUSE is NOT a noop since commit 17e8102 ("mm:
support POSIX_FADV_NOREUSE"). And it had at least one user (we made
the userpspace change after the kernel supported it): SVT-AV1 [1]; it
was also added to FIO for testing purposes.

[1] https://gitlab.com/AOMediaCodec/SVT-AV1

> But would we be better off honouring
> POSIX_FADV_NOREUSE than introducing RWF_UNCACHED?  I'll think about this
> some more while I'm offline.

But I guess the flag isn't honored the way UNCACHED works?
Matthew Wilcox Nov. 11, 2024, 9:48 p.m. UTC | #4
On Mon, Nov 11, 2024 at 02:24:54PM -0700, Yu Zhao wrote:
> Just to clarify that NOREUSE is NOT a noop since commit 17e8102 ("mm:

maybe you should send a patch to the manpage?
Yu Zhao Nov. 11, 2024, 10:07 p.m. UTC | #5
On Mon, Nov 11, 2024 at 2:48 PM Matthew Wilcox <willy@infradead.org> wrote:
>
> On Mon, Nov 11, 2024 at 02:24:54PM -0700, Yu Zhao wrote:
> > Just to clarify that NOREUSE is NOT a noop since commit 17e8102 ("mm:
>
> maybe you should send a patch to the manpage?

I was under the impression that our engineers took care of that. But
apparently it's still pending:
https://lore.kernel.org/linux-man/20230320222057.1976956-1-talumbau@google.com/

Will find someone else to follow up on that.