1
0
Fork 0
mirror of https://git.tukaani.org/xz.git synced 2024-04-04 12:36:23 +02:00

xz: Add a default soft memory usage limit for --threads=0.

This is a soft limit in sense that it only affects the number of
threads. It never makes xz fail and it never makes xz change
settings that would affect the compressed output.

The idea is to make -T0 have more reasonable behavior when
the system has very many cores or when a memory-hungry
compression options are used. This also helps with 32-bit xz,
preventing it from running out of address space.

The downside of this commit is that now the number of threads
might become too low compared to what the user expected. I
hope this to be an acceptable compromise as the old behavior
has been a source of well-argued complaints for a long time.
This commit is contained in:
Lasse Collin 2022-04-14 14:20:46 +03:00
parent 0adc13bfe3
commit c77fe55ddb
3 changed files with 82 additions and 11 deletions

View file

@ -220,12 +220,16 @@ coder_set_compression_settings(void)
// Get the memory usage. Note that if --format=raw was used, // Get the memory usage. Note that if --format=raw was used,
// we can be decompressing. // we can be decompressing.
const uint64_t memory_limit = hardware_memlimit_get(opt_mode); //
// If multithreaded .xz compression is done, this value will be
// replaced.
uint64_t memory_limit = hardware_memlimit_get(opt_mode);
uint64_t memory_usage = UINT64_MAX; uint64_t memory_usage = UINT64_MAX;
if (opt_mode == MODE_COMPRESS) { if (opt_mode == MODE_COMPRESS) {
#ifdef HAVE_ENCODERS #ifdef HAVE_ENCODERS
# ifdef MYTHREAD_ENABLED # ifdef MYTHREAD_ENABLED
if (opt_format == FORMAT_XZ && hardware_threads_is_mt()) { if (opt_format == FORMAT_XZ && hardware_threads_is_mt()) {
memory_limit = hardware_memlimit_mtenc_get();
mt_options.threads = hardware_threads_get(); mt_options.threads = hardware_threads_get();
mt_options.block_size = opt_block_size; mt_options.block_size = opt_block_size;
mt_options.check = check; mt_options.check = check;
@ -304,6 +308,27 @@ coder_set_compression_settings(void)
} }
} }
// If the memory usage limit is only a soft limit (automatic
// number of threads and no --memlimit-compress), the limit
// is only used to reduce the number of threads and once at
// just one thread, the limit is completely ignored. This
// way -T0 won't use insane amount of memory but at the same
// time the soft limit will never make xz fail and never make
// xz change settings that would affect the compressed output.
if (hardware_memlimit_mtenc_is_default()) {
message(V_WARNING, _("Reduced the number of threads "
"from %s to one. The automatic memory usage "
"limit of %s MiB is still being exceeded. "
"%s MiB of memory is required. "
"Continuing anyway."),
uint64_to_str(hardware_threads_get(), 0),
uint64_to_str(
round_up_to_mib(memory_limit), 1),
uint64_to_str(
round_up_to_mib(memory_usage), 2));
return;
}
// If --no-adjust was used, we cannot drop to single-threaded // If --no-adjust was used, we cannot drop to single-threaded
// mode since it produces different compressed output. // mode since it produces different compressed output.
// //
@ -321,7 +346,6 @@ coder_set_compression_settings(void)
message(V_WARNING, _("Switching to single-threaded mode " message(V_WARNING, _("Switching to single-threaded mode "
"to not exceed the memory usage limit of %s MiB"), "to not exceed the memory usage limit of %s MiB"),
uint64_to_str(round_up_to_mib(memory_limit), 0)); uint64_to_str(round_up_to_mib(memory_limit), 0));
} }
# endif # endif

View file

@ -29,6 +29,13 @@ static uint64_t memlimit_decompress = 0;
/// Default memory usage for multithreaded modes: /// Default memory usage for multithreaded modes:
/// ///
/// - Default value for --memlimit-compress when automatic number of threads
/// is used. However, if the limit wouldn't allow even one thread then
/// the limit is ignored in coder.c and one thread will be used anyway.
/// This mess is a compromise: we wish to prevent -T0 from using too
/// many threads but we also don't want xz to give an error due to
/// a memlimit that the user didn't explicitly set.
///
/// - Default value for --memlimit-mt-decompress /// - Default value for --memlimit-mt-decompress
/// ///
/// This value is caluclated in hardware_init() and cannot be changed later. /// This value is caluclated in hardware_init() and cannot be changed later.
@ -151,21 +158,34 @@ hardware_memlimit_set(uint64_t new_memlimit,
extern uint64_t extern uint64_t
hardware_memlimit_get(enum operation_mode mode) hardware_memlimit_get(enum operation_mode mode)
{ {
// Zero is a special value that indicates the default. Currently // 0 is a special value that indicates the default.
// the default simply disables the limit. Once there is threading // It disables the limit in single-threaded mode.
// support, this might be a little more complex, because there will //
// probably be a special case where a user asks for "optimal" number // NOTE: For multithreaded decompression, this is the hard limit
// of threads instead of a specific number (this might even become // (memlimit_stop). hardware_memlimit_mtdec_get() gives the
// the default mode). Each thread may use a significant amount of // soft limit (memlimit_threaded).
// memory. When there are no memory usage limits set, we need some
// default soft limit for calculating the "optimal" number of
// threads.
const uint64_t memlimit = mode == MODE_COMPRESS const uint64_t memlimit = mode == MODE_COMPRESS
? memlimit_compress : memlimit_decompress; ? memlimit_compress : memlimit_decompress;
return memlimit != 0 ? memlimit : UINT64_MAX; return memlimit != 0 ? memlimit : UINT64_MAX;
} }
extern uint64_t
hardware_memlimit_mtenc_get(void)
{
return memlimit_compress == 0 && threads_are_automatic
? memlimit_mt_default
: hardware_memlimit_get(MODE_COMPRESS);
}
extern bool
hardware_memlimit_mtenc_is_default(void)
{
return memlimit_compress == 0 && threads_are_automatic;
}
extern uint64_t extern uint64_t
hardware_memlimit_mtdec_get(void) hardware_memlimit_mtdec_get(void)
{ {

View file

@ -37,9 +37,36 @@ extern void hardware_memlimit_set(uint64_t new_memlimit,
bool is_percentage); bool is_percentage);
/// Get the current memory usage limit for compression or decompression. /// Get the current memory usage limit for compression or decompression.
/// This is a hard limit that will not be exceeded. This is obeyed in
/// both single-threaded and multithreaded modes.
extern uint64_t hardware_memlimit_get(enum operation_mode mode); extern uint64_t hardware_memlimit_get(enum operation_mode mode);
/// This returns a system-specific default value if all of the following
/// conditions are true:
///
/// - An automatic number of threads was requested (--threads=0).
///
/// - --memlimit-compress wasn't used or it was reset to the default
/// value by setting it to 0.
///
/// Otherwise this is identical to hardware_memlimit_get(MODE_COMPRESS).
///
/// The idea is to keep automatic thread count reasonable so that too
/// high memory usage is avoided and, with 32-bit xz, running out of
/// address space is avoided.
extern uint64_t hardware_memlimit_mtenc_get(void);
/// Returns true if the value returned by hardware_memlimit_mtenc_get() is
/// a system-specific default value. coder.c uses this to ignore the default
/// memlimit in case it's too small even for a single thread in multithreaded
/// mode. This way the default limit will never make xz fail or affect the
/// compressed output; it will only make xz reduce the number of threads.
extern bool hardware_memlimit_mtenc_is_default(void);
/// Get the current memory usage limit for multithreaded decompression. /// Get the current memory usage limit for multithreaded decompression.
/// This is only used to reduce the number of threads. This limit can be
/// exceeded if the number of threads are reduce to one. Then the value
/// from hardware_memlimit_get() will be honored like in single-threaded mode.
extern uint64_t hardware_memlimit_mtdec_get(void); extern uint64_t hardware_memlimit_mtdec_get(void);
/// Display the amount of RAM and memory usage limits and exit. /// Display the amount of RAM and memory usage limits and exit.