Message ID | 20250121-kernel-compress-fast-v1-1-fa693b6167d4@google.com |
---|---|
State | New |
Headers | show |
Series | [RFC] x86: Add CONFIG_KERNEL_UNCOMPRESSED support | expand |
Hi Jann, On Tue, 21 Jan 2025 at 23:16, Jann Horn <jannh@google.com> wrote: > > Support storing the kernel uncompressed for developers who want to quickly > iterate with one-off kernel builds. > Store it in the usual format with a 4-byte length suffix and keep this new > codepath as close as possible to the normal path where decompression > happens. > > The other compression methods offered by the kernel take some time; > even LZ4 (which the kernel uses at compression level 9) takes ~2.8 > seconds to compress a 110M large vmlinux.bin on my machine. > > An alternate approach to this would be to offer customization of the LZ4 > compression level through a kconfig variable; and yet another approach > would be to abuse the existing gzip decompression logic by storing the > kernel as "non-compressed" DEFLATE blocks, so that the decompression code > will essentially end up just doing a bunch of memcpy() calls. > This all seems pretty complicated, and adding yet another (pseudo-)compression method is not great in terms of maintenance burden, especially because there are other consumers of the compressed images (both for bzImage and EFI zboot) Did you try running gzip with -1 instead of -9? On my build machine, this reduces the compression time of a defconfig bzImage build from 4.3 seconds to 0.9 seconds.
On Wed, Jan 22, 2025 at 2:31 PM Ard Biesheuvel <ardb@kernel.org> wrote: > Hi Jann, > > On Tue, 21 Jan 2025 at 23:16, Jann Horn <jannh@google.com> wrote: > > > > Support storing the kernel uncompressed for developers who want to quickly > > iterate with one-off kernel builds. > > Store it in the usual format with a 4-byte length suffix and keep this new > > codepath as close as possible to the normal path where decompression > > happens. > > > > The other compression methods offered by the kernel take some time; > > even LZ4 (which the kernel uses at compression level 9) takes ~2.8 > > seconds to compress a 110M large vmlinux.bin on my machine. > > > > An alternate approach to this would be to offer customization of the LZ4 > > compression level through a kconfig variable; and yet another approach > > would be to abuse the existing gzip decompression logic by storing the > > kernel as "non-compressed" DEFLATE blocks, so that the decompression code > > will essentially end up just doing a bunch of memcpy() calls. > > > > This all seems pretty complicated, and adding yet another > (pseudo-)compression method is not great in terms of maintenance > burden, especially because there are other consumers of the compressed > images (both for bzImage and EFI zboot) > > Did you try running gzip with -1 instead of -9? On my build machine, > this reduces the compression time of a defconfig bzImage build from > 4.3 seconds to 0.9 seconds. I tried lz4 with -1; that is very fast (240ms wall clock time on my machine, and just 120ms user time): $ ls -lh arch/x86/boot/compressed/vmlinux.bin -rwxr-x--- 1 [...] 110M Jan 22 00:01 arch/x86/boot/compressed/vmlinux.bin $ cat arch/x86/boot/compressed/vmlinux.bin | time lz4 -l -9 - - | wc -c 2.86user 0.04system 0:02.96elapsed 97%CPU (0avgtext+0avgdata 15756maxresident)k 0inputs+0outputs (0major+220minor)pagefaults 0swaps 46309676 $ cat arch/x86/boot/compressed/vmlinux.bin | time lz4 -l -1 - - | wc -c 0.12user 0.06system 0:00.24elapsed 75%CPU (0avgtext+0avgdata 15524maxresident)k 0inputs+0outputs (0major+94minor)pagefaults 0swaps 56029608 But I wasn't sure how to wire that up in a nice way. I guess the nicest option would be to create a separate kconfig variable for the compression level to use for any cmd_lz4/cmd_lz4_with_size invocations in the build process; and then maybe only make this option visible if LZ4 is selected as kernel compression method? Another option would be to create a new option in the "Kernel compression mode" choice menu with a name like "LZ4 (fast)", turn CONFIG_KERNEL_LZ4 into an internal flag that is selected by both LZ4 variants shown in the choice menu, and duplicate some of the make rules, but that seems overly complicated.
On 1/22/25 14:54, Jann Horn wrote: > On Wed, Jan 22, 2025 at 2:31 PM Ard Biesheuvel <ardb@kernel.org> wrote: >> Hi Jann, >> >> On Tue, 21 Jan 2025 at 23:16, Jann Horn <jannh@google.com> wrote: >>> >>> Support storing the kernel uncompressed for developers who want to quickly >>> iterate with one-off kernel builds. >>> Store it in the usual format with a 4-byte length suffix and keep this new >>> codepath as close as possible to the normal path where decompression >>> happens. >>> >>> The other compression methods offered by the kernel take some time; >>> even LZ4 (which the kernel uses at compression level 9) takes ~2.8 >>> seconds to compress a 110M large vmlinux.bin on my machine. >>> >>> An alternate approach to this would be to offer customization of the LZ4 >>> compression level through a kconfig variable; and yet another approach >>> would be to abuse the existing gzip decompression logic by storing the >>> kernel as "non-compressed" DEFLATE blocks, so that the decompression code >>> will essentially end up just doing a bunch of memcpy() calls. >>> >> >> This all seems pretty complicated, and adding yet another >> (pseudo-)compression method is not great in terms of maintenance >> burden, especially because there are other consumers of the compressed >> images (both for bzImage and EFI zboot) >> >> Did you try running gzip with -1 instead of -9? On my build machine, >> this reduces the compression time of a defconfig bzImage build from >> 4.3 seconds to 0.9 seconds. > > I tried lz4 with -1; that is very fast (240ms wall clock time on my > machine, and just 120ms user time): > > $ ls -lh arch/x86/boot/compressed/vmlinux.bin > -rwxr-x--- 1 [...] 110M Jan 22 00:01 arch/x86/boot/compressed/vmlinux.bin > $ cat arch/x86/boot/compressed/vmlinux.bin | time lz4 -l -9 - - | wc -c > 2.86user 0.04system 0:02.96elapsed 97%CPU (0avgtext+0avgdata 15756maxresident)k > 0inputs+0outputs (0major+220minor)pagefaults 0swaps > 46309676 > $ cat arch/x86/boot/compressed/vmlinux.bin | time lz4 -l -1 - - | wc -c > 0.12user 0.06system 0:00.24elapsed 75%CPU (0avgtext+0avgdata 15524maxresident)k > 0inputs+0outputs (0major+94minor)pagefaults 0swaps > 56029608 > > But I wasn't sure how to wire that up in a nice way. I guess the > nicest option would be to create a separate kconfig variable for the > compression level to use for any cmd_lz4/cmd_lz4_with_size invocations > in the build process; and then maybe only make this option visible if > LZ4 is selected as kernel compression method? > > Another option would be to create a new option in the "Kernel > compression mode" choice menu with a name like "LZ4 (fast)", turn > CONFIG_KERNEL_LZ4 into an internal flag that is selected by both LZ4 > variants shown in the choice menu, and duplicate some of the make > rules, but that seems overly complicated. > Hello, In my opinion 'lz4 -9' doesn't make much sense. It's terribly slow and the compression ratio is also not exactly good. Instead, zstd seems to be a much better choice. Not quite as ultra fast as lz4 levels 1 to 3, but much better compression. As an example, I compressed a kernel source tarball (zstd is multithreaded with 4 threads here, which are not fully used with small-ish files like vmlinux): - zstd -3: from 1.32 GB to 199 MB in 5.23 seconds - zstd -6: to 173 MB in 10.77 seconds - zstd -10: to 165 MB in 24.52 seconds - lz4 -1: to 373 MB in 1.60 seconds - lz4 -3: to 278 MB in 6.45 seconds - lz4 -9: to 266 MB in 22.03 seconds And a vmlinux from my installed kernel 6.13: - zstd -3: from 51.8 MB to 16.7 MB in 0.60 seconds - zstd -6: to 15.8 MB in 1.39 seconds - zstd -10: to 15.3 MB in 3.54 seconds - lz4 -1: to 23.7 MB in 0.08 seconds - lz4 -3: to 20.2 MB in 0.51 seconds - lz4 -9: to 19.7 MB in 1.23 seconds For my kernel, I use a Kconfig option to choose a zstd compression level. I could submit it if there is interest. Tor
On Wed, 22 Jan 2025 at 14:54, Jann Horn <jannh@google.com> wrote: > > On Wed, Jan 22, 2025 at 2:31 PM Ard Biesheuvel <ardb@kernel.org> wrote: > > Hi Jann, > > > > On Tue, 21 Jan 2025 at 23:16, Jann Horn <jannh@google.com> wrote: > > > > > > Support storing the kernel uncompressed for developers who want to quickly > > > iterate with one-off kernel builds. > > > Store it in the usual format with a 4-byte length suffix and keep this new > > > codepath as close as possible to the normal path where decompression > > > happens. > > > > > > The other compression methods offered by the kernel take some time; > > > even LZ4 (which the kernel uses at compression level 9) takes ~2.8 > > > seconds to compress a 110M large vmlinux.bin on my machine. > > > > > > An alternate approach to this would be to offer customization of the LZ4 > > > compression level through a kconfig variable; and yet another approach > > > would be to abuse the existing gzip decompression logic by storing the > > > kernel as "non-compressed" DEFLATE blocks, so that the decompression code > > > will essentially end up just doing a bunch of memcpy() calls. > > > > > > > This all seems pretty complicated, and adding yet another > > (pseudo-)compression method is not great in terms of maintenance > > burden, especially because there are other consumers of the compressed > > images (both for bzImage and EFI zboot) > > > > Did you try running gzip with -1 instead of -9? On my build machine, > > this reduces the compression time of a defconfig bzImage build from > > 4.3 seconds to 0.9 seconds. > > I tried lz4 with -1; that is very fast (240ms wall clock time on my > machine, and just 120ms user time): > > $ ls -lh arch/x86/boot/compressed/vmlinux.bin > -rwxr-x--- 1 [...] 110M Jan 22 00:01 arch/x86/boot/compressed/vmlinux.bin > $ cat arch/x86/boot/compressed/vmlinux.bin | time lz4 -l -9 - - | wc -c > 2.86user 0.04system 0:02.96elapsed 97%CPU (0avgtext+0avgdata 15756maxresident)k > 0inputs+0outputs (0major+220minor)pagefaults 0swaps > 46309676 > $ cat arch/x86/boot/compressed/vmlinux.bin | time lz4 -l -1 - - | wc -c > 0.12user 0.06system 0:00.24elapsed 75%CPU (0avgtext+0avgdata 15524maxresident)k > 0inputs+0outputs (0major+94minor)pagefaults 0swaps > 56029608 > Excellent. > But I wasn't sure how to wire that up in a nice way. I guess the > nicest option would be to create a separate kconfig variable for the > compression level to use for any cmd_lz4/cmd_lz4_with_size invocations > in the build process; and then maybe only make this option visible if > LZ4 is selected as kernel compression method? > > Another option would be to create a new option in the "Kernel > compression mode" choice menu with a name like "LZ4 (fast)", turn > CONFIG_KERNEL_LZ4 into an internal flag that is selected by both LZ4 > variants shown in the choice menu, and duplicate some of the make > rules, but that seems overly complicated. > I didn't realise that KERNEL_UNCOMPRESSED already exists and you are just wiring it up for x86. But I still think that we should avoid that, not only because it is yet another bzImage format but also because I still see a 3x size reduction even with the fastest setting. I think adding one Kconfig symbol that depends on KERNEL_LZ4 and switches from -9 to -1 for LZ4 only is reasonable.
On Wed, Jan 22, 2025 at 3:19 PM Tor Vic <torvic9@mailbox.org> wrote: > On 1/22/25 14:54, Jann Horn wrote: > > On Wed, Jan 22, 2025 at 2:31 PM Ard Biesheuvel <ardb@kernel.org> wrote: > >> Hi Jann, > >> > >> On Tue, 21 Jan 2025 at 23:16, Jann Horn <jannh@google.com> wrote: > >>> > >>> Support storing the kernel uncompressed for developers who want to quickly > >>> iterate with one-off kernel builds. > >>> Store it in the usual format with a 4-byte length suffix and keep this new > >>> codepath as close as possible to the normal path where decompression > >>> happens. > >>> > >>> The other compression methods offered by the kernel take some time; > >>> even LZ4 (which the kernel uses at compression level 9) takes ~2.8 > >>> seconds to compress a 110M large vmlinux.bin on my machine. > >>> > >>> An alternate approach to this would be to offer customization of the LZ4 > >>> compression level through a kconfig variable; and yet another approach > >>> would be to abuse the existing gzip decompression logic by storing the > >>> kernel as "non-compressed" DEFLATE blocks, so that the decompression code > >>> will essentially end up just doing a bunch of memcpy() calls. > >>> > >> > >> This all seems pretty complicated, and adding yet another > >> (pseudo-)compression method is not great in terms of maintenance > >> burden, especially because there are other consumers of the compressed > >> images (both for bzImage and EFI zboot) > >> > >> Did you try running gzip with -1 instead of -9? On my build machine, > >> this reduces the compression time of a defconfig bzImage build from > >> 4.3 seconds to 0.9 seconds. > > > > I tried lz4 with -1; that is very fast (240ms wall clock time on my > > machine, and just 120ms user time): > > > > $ ls -lh arch/x86/boot/compressed/vmlinux.bin > > -rwxr-x--- 1 [...] 110M Jan 22 00:01 arch/x86/boot/compressed/vmlinux.bin > > $ cat arch/x86/boot/compressed/vmlinux.bin | time lz4 -l -9 - - | wc -c > > 2.86user 0.04system 0:02.96elapsed 97%CPU (0avgtext+0avgdata 15756maxresident)k > > 0inputs+0outputs (0major+220minor)pagefaults 0swaps > > 46309676 > > $ cat arch/x86/boot/compressed/vmlinux.bin | time lz4 -l -1 - - | wc -c > > 0.12user 0.06system 0:00.24elapsed 75%CPU (0avgtext+0avgdata 15524maxresident)k > > 0inputs+0outputs (0major+94minor)pagefaults 0swaps > > 56029608 > > > > But I wasn't sure how to wire that up in a nice way. I guess the > > nicest option would be to create a separate kconfig variable for the > > compression level to use for any cmd_lz4/cmd_lz4_with_size invocations > > in the build process; and then maybe only make this option visible if > > LZ4 is selected as kernel compression method? > > > > Another option would be to create a new option in the "Kernel > > compression mode" choice menu with a name like "LZ4 (fast)", turn > > CONFIG_KERNEL_LZ4 into an internal flag that is selected by both LZ4 > > variants shown in the choice menu, and duplicate some of the make > > rules, but that seems overly complicated. > > > > Hello, > > In my opinion 'lz4 -9' doesn't make much sense. > It's terribly slow and the compression ratio is also not exactly good. > > Instead, zstd seems to be a much better choice. Not quite as ultra fast > as lz4 levels 1 to 3, but much better compression. I think you're describing a slightly different usecase. My goal here is something I can use for when I build a kernel, boot it in QEMU, test something, and then immediately throw the kernel away - I don't care that much how much disk space the kernel image uses, and the goal I'm optimizing for is pretty much just the time needed for one build followed by one boot.
On 1/22/25 15:30, Jann Horn wrote: >> >> Hello, >> >> In my opinion 'lz4 -9' doesn't make much sense. >> It's terribly slow and the compression ratio is also not exactly good. >> >> Instead, zstd seems to be a much better choice. Not quite as ultra fast >> as lz4 levels 1 to 3, but much better compression. > > I think you're describing a slightly different usecase. > > My goal here is something I can use for when I build a kernel, boot it > in QEMU, test something, and then immediately throw the kernel away - > I don't care that much how much disk space the kernel image uses, and > the goal I'm optimizing for is pretty much just the time needed for > one build followed by one boot. Ah, yes, fair enough. In that case, 'lz4 -1' makes the most sense I guess. Sorry for the noise. Tor
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index ef6cfea9df7333c52e331f487a0b29f037a6bf14..6d468d47861ae0b6ec6b7649af6ab4dd123eb5c8 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -250,6 +250,7 @@ config X86 select HAVE_KERNEL_LZO select HAVE_KERNEL_XZ select HAVE_KERNEL_ZSTD + select HAVE_KERNEL_UNCOMPRESSED select HAVE_KPROBES select HAVE_KPROBES_ON_FTRACE select HAVE_FUNCTION_ERROR_INJECTION diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile index f2051644de9432e3466ac0ef1c4d3abc378e37d3..06079e02d9e01704cc0da06c1195854c8d0602ac 100644 --- a/arch/x86/boot/compressed/Makefile +++ b/arch/x86/boot/compressed/Makefile @@ -137,6 +137,8 @@ $(obj)/vmlinux.bin.lz4: $(vmlinux.bin.all-y) FORCE $(call if_changed,lz4_with_size) $(obj)/vmlinux.bin.zst: $(vmlinux.bin.all-y) FORCE $(call if_changed,zstd22_with_size) +$(obj)/vmlinux.bin.store: $(vmlinux.bin.all-y) FORCE + $(call if_changed,store_with_size) suffix-$(CONFIG_KERNEL_GZIP) := gz suffix-$(CONFIG_KERNEL_BZIP2) := bz2 @@ -145,6 +147,7 @@ suffix-$(CONFIG_KERNEL_XZ) := xz suffix-$(CONFIG_KERNEL_LZO) := lzo suffix-$(CONFIG_KERNEL_LZ4) := lz4 suffix-$(CONFIG_KERNEL_ZSTD) := zst +suffix-$(CONFIG_KERNEL_UNCOMPRESSED) := store quiet_cmd_mkpiggy = MKPIGGY $@ cmd_mkpiggy = $(obj)/mkpiggy $< > $@ diff --git a/arch/x86/boot/compressed/misc.c b/arch/x86/boot/compressed/misc.c index 0d37420cad0259554f8160dea0c502cb7e2fc6cd..5d514a147d5d1ae252419e4c7cdc09e9c29f110f 100644 --- a/arch/x86/boot/compressed/misc.c +++ b/arch/x86/boot/compressed/misc.c @@ -88,6 +88,10 @@ static int cols __section(".data"); #ifdef CONFIG_KERNEL_ZSTD #include "../../../../lib/decompress_unzstd.c" #endif + +#ifdef CONFIG_KERNEL_UNCOMPRESSED +#include "../../../../lib/decompress_dummy.c" +#endif /* * NOTE: When adding a new decompressor, please update the analysis in * ../header.S. diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S index b5c79f43359bcde2c4c3c5ed796e8780f7979774..8397470231cf571a33ac75f7ca7020608e170eef 100644 --- a/arch/x86/boot/header.S +++ b/arch/x86/boot/header.S @@ -483,6 +483,8 @@ pref_address: .quad LOAD_PHYSICAL_ADDR # preferred load addr # larger margin. # # extra_bytes = (uncompressed_size >> 8) + 131072 +# +# Uncompressed data does not grow. #define ZO_z_extra_bytes ((ZO_z_output_len >> 8) + 131072) #if ZO_z_output_len > ZO_z_input_len diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c index af23b3c502282f9bd644c38af445875c225cdf42..1c43a6ae5e665aa3bff3bd467c8a2f5525b1a6e9 100644 --- a/drivers/firmware/efi/libstub/zboot.c +++ b/drivers/firmware/efi/libstub/zboot.c @@ -27,6 +27,8 @@ static unsigned long free_mem_ptr, free_mem_end_ptr; #include "../../../../lib/decompress_unxz.c" #elif defined(CONFIG_KERNEL_ZSTD) #include "../../../../lib/decompress_unzstd.c" +#elif defined(CONFIG_KERNEL_UNCOMPRESSED) +#include "../../../../lib/decompress_dummy.c" #endif extern char efi_zboot_header[]; diff --git a/lib/decompress_dummy.c b/lib/decompress_dummy.c new file mode 100644 index 0000000000000000000000000000000000000000..49435e199a07f6ed376ff93adeae8ee08a9dd3d7 --- /dev/null +++ b/lib/decompress_dummy.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 +STATIC int INIT __decompress(unsigned char *buf, long len, + long (*fill)(void*, unsigned long), + long (*flush)(void*, unsigned long), + unsigned char *out_buf, long out_len, + long *pos, + void (*error)(char *x)) +{ + if (out_len < len-4) { + error("output buffer too small"); + return -1; + } + memcpy(out_buf, buf, len-4); + return 0; +} diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 7395200538da89a2f6e6d21f8959f3f60d291d79..bb8116ba8ba189d5246fcb0e71c7be6e05ce5148 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -525,6 +525,9 @@ quiet_cmd_zstd22 = ZSTD22 $@ quiet_cmd_zstd22_with_size = ZSTD22 $@ cmd_zstd22_with_size = { cat $(real-prereqs) | $(ZSTD) -22 --ultra; $(size_append); } > $@ +quiet_cmd_store_with_size = STORE $@ + cmd_store_with_size = { cat $(real-prereqs); $(size_append); } > $@ + # ASM offsets # ---------------------------------------------------------------------------
Support storing the kernel uncompressed for developers who want to quickly iterate with one-off kernel builds. Store it in the usual format with a 4-byte length suffix and keep this new codepath as close as possible to the normal path where decompression happens. The other compression methods offered by the kernel take some time; even LZ4 (which the kernel uses at compression level 9) takes ~2.8 seconds to compress a 110M large vmlinux.bin on my machine. An alternate approach to this would be to offer customization of the LZ4 compression level through a kconfig variable; and yet another approach would be to abuse the existing gzip decompression logic by storing the kernel as "non-compressed" DEFLATE blocks, so that the decompression code will essentially end up just doing a bunch of memcpy() calls. Signed-off-by: Jann Horn <jannh@google.com> --- arch/x86/Kconfig | 1 + arch/x86/boot/compressed/Makefile | 3 +++ arch/x86/boot/compressed/misc.c | 4 ++++ arch/x86/boot/header.S | 2 ++ drivers/firmware/efi/libstub/zboot.c | 2 ++ lib/decompress_dummy.c | 15 +++++++++++++++ scripts/Makefile.lib | 3 +++ 7 files changed, 30 insertions(+) --- base-commit: 95ec54a420b8f445e04a7ca0ea8deb72c51fe1d3 change-id: 20250121-kernel-compress-fast-350ce5801c28