xz: Add support for threaded compression.

This commit is contained in:
Lasse Collin 2011-04-11 22:06:03 +03:00
parent de678e0c92
commit 24e0406c0f
2 changed files with 124 additions and 79 deletions

View File

@ -179,8 +179,9 @@ parse_real(args_info *args, int argc, char **argv)
break; break;
case 'T': case 'T':
// The max is from src/liblzma/common/common.h.
hardware_threads_set(str_to_uint64("threads", hardware_threads_set(str_to_uint64("threads",
optarg, 0, LZMA_THREADS_MAX)); optarg, 0, 16384));
break; break;
// --version // --version

View File

@ -55,6 +55,14 @@ static lzma_check check;
/// This becomes false if the --check=CHECK option is used. /// This becomes false if the --check=CHECK option is used.
static bool check_default = true; static bool check_default = true;
#ifdef HAVE_PTHREAD
static lzma_mt mt_options = {
.flags = 0,
.timeout = 300,
.filters = filters,
};
#endif
extern void extern void
coder_set_check(lzma_check new_check) coder_set_check(lzma_check new_check)
@ -117,6 +125,15 @@ memlimit_too_small(uint64_t memory_usage)
extern void extern void
coder_set_compression_settings(void) coder_set_compression_settings(void)
{ {
// The default check type is CRC64, but fallback to CRC32
// if CRC64 isn't supported by the copy of liblzma we are
// using. CRC32 is always supported.
if (check_default) {
check = LZMA_CHECK_CRC64;
if (!lzma_check_is_supported(check))
check = LZMA_CHECK_CRC32;
}
// Options for LZMA1 or LZMA2 in case we are using a preset. // Options for LZMA1 or LZMA2 in case we are using a preset.
static lzma_options_lzma opt_lzma; static lzma_options_lzma opt_lzma;
@ -170,15 +187,26 @@ coder_set_compression_settings(void)
// Print the selected filter chain. // Print the selected filter chain.
message_filters_show(V_DEBUG, filters); message_filters_show(V_DEBUG, filters);
// If using --format=raw, we can be decoding. The memusage function // Get the memory usage. Note that if --format=raw was used,
// also validates the filter chain and the options used for the // we can be decompressing.
// filters.
const uint64_t memory_limit = hardware_memlimit_get(opt_mode); const uint64_t memory_limit = hardware_memlimit_get(opt_mode);
uint64_t memory_usage; uint64_t memory_usage;
if (opt_mode == MODE_COMPRESS) if (opt_mode == MODE_COMPRESS) {
memory_usage = lzma_raw_encoder_memusage(filters); #ifdef HAVE_PTHREAD
else if (opt_format == FORMAT_XZ && hardware_threads_get() > 1) {
mt_options.threads = hardware_threads_get();
mt_options.block_size = opt_block_size;
mt_options.check = check;
memory_usage = lzma_stream_encoder_mt_memusage(
&mt_options);
} else
#endif
{
memory_usage = lzma_raw_encoder_memusage(filters);
}
} else {
memory_usage = lzma_raw_decoder_memusage(filters); memory_usage = lzma_raw_decoder_memusage(filters);
}
if (memory_usage == UINT64_MAX) if (memory_usage == UINT64_MAX)
message_fatal(_("Unsupported filter chain or filter options")); message_fatal(_("Unsupported filter chain or filter options"));
@ -194,90 +222,99 @@ coder_set_compression_settings(void)
round_up_to_mib(decmem), 0)); round_up_to_mib(decmem), 0));
} }
if (memory_usage > memory_limit) { if (memory_usage <= memory_limit)
// If --no-auto-adjust was used or we didn't find LZMA1 or return;
// LZMA2 as the last filter, give an error immediately.
// --format=raw implies --no-auto-adjust.
if (!opt_auto_adjust || opt_format == FORMAT_RAW)
memlimit_too_small(memory_usage);
assert(opt_mode == MODE_COMPRESS); // If --no-auto-adjust was used or we didn't find LZMA1 or
// LZMA2 as the last filter, give an error immediately.
// --format=raw implies --no-auto-adjust.
if (!opt_auto_adjust || opt_format == FORMAT_RAW)
memlimit_too_small(memory_usage);
// Look for the last filter if it is LZMA2 or LZMA1, so assert(opt_mode == MODE_COMPRESS);
// we can make it use less RAM. With other filters we don't
// know what to do. #ifdef HAVE_PTHREAD
size_t i = 0; if (opt_format == FORMAT_XZ && mt_options.threads > 1) {
while (filters[i].id != LZMA_FILTER_LZMA2 // Try to reduce the number of threads before
&& filters[i].id != LZMA_FILTER_LZMA1) { // adjusting the compression settings down.
if (filters[i].id == LZMA_VLI_UNKNOWN) do {
// FIXME? The real single-threaded mode has
// lower memory usage, but it's not comparable
// because it doesn't write the size info
// into Block Headers.
if (--mt_options.threads == 0)
memlimit_too_small(memory_usage); memlimit_too_small(memory_usage);
++i; memory_usage = lzma_stream_encoder_mt_memusage(
} &mt_options);
// Decrease the dictionary size until we meet the memory
// usage limit. First round down to full mebibytes.
lzma_options_lzma *opt = filters[i].options;
const uint32_t orig_dict_size = opt->dict_size;
opt->dict_size &= ~((UINT32_C(1) << 20) - 1);
while (true) {
// If it is below 1 MiB, auto-adjusting failed. We
// could be more sophisticated and scale it down even
// more, but let's see if many complain about this
// version.
//
// FIXME: Displays the scaled memory usage instead
// of the original.
if (opt->dict_size < (UINT32_C(1) << 20))
memlimit_too_small(memory_usage);
memory_usage = lzma_raw_encoder_memusage(filters);
if (memory_usage == UINT64_MAX) if (memory_usage == UINT64_MAX)
message_bug(); message_bug();
// Accept it if it is low enough. } while (memory_usage > memory_limit);
if (memory_usage <= memory_limit)
break;
// Otherwise 1 MiB down and try again. I hope this message(V_WARNING, _("Adjusted the number of threads "
// isn't too slow method for cases where the original "from %s to %s to not exceed "
// dict_size is very big. "the memory usage limit of %s MiB"),
opt->dict_size -= UINT32_C(1) << 20; uint64_to_str(hardware_threads_get(), 0),
} uint64_to_str(mt_options.threads, 1),
uint64_to_str(round_up_to_mib(
memory_limit), 2));
}
#endif
// Tell the user that we decreased the dictionary size. if (memory_usage <= memory_limit)
message(V_WARNING, _("Adjusted LZMA%c dictionary size " return;
"from %s MiB to %s MiB to not exceed "
"the memory usage limit of %s MiB"), // Look for the last filter if it is LZMA2 or LZMA1, so we can make
filters[i].id == LZMA_FILTER_LZMA2 // it use less RAM. With other filters we don't know what to do.
? '2' : '1', size_t i = 0;
uint64_to_str(orig_dict_size >> 20, 0), while (filters[i].id != LZMA_FILTER_LZMA2
uint64_to_str(opt->dict_size >> 20, 1), && filters[i].id != LZMA_FILTER_LZMA1) {
uint64_to_str(round_up_to_mib( if (filters[i].id == LZMA_VLI_UNKNOWN)
memory_limit), 2)); memlimit_too_small(memory_usage);
++i;
} }
/* // Decrease the dictionary size until we meet the memory
// Limit the number of worker threads so that memory usage // usage limit. First round down to full mebibytes.
// limit isn't exceeded. lzma_options_lzma *opt = filters[i].options;
assert(memory_usage > 0); const uint32_t orig_dict_size = opt->dict_size;
size_t thread_limit = memory_limit / memory_usage; opt->dict_size &= ~((UINT32_C(1) << 20) - 1);
if (thread_limit == 0) while (true) {
thread_limit = 1; // If it is below 1 MiB, auto-adjusting failed. We could be
// more sophisticated and scale it down even more, but let's
// see if many complain about this version.
//
// FIXME: Displays the scaled memory usage instead
// of the original.
if (opt->dict_size < (UINT32_C(1) << 20))
memlimit_too_small(memory_usage);
if (opt_threads > thread_limit) memory_usage = lzma_raw_encoder_memusage(filters);
opt_threads = thread_limit; if (memory_usage == UINT64_MAX)
*/ message_bug();
if (check_default) { // Accept it if it is low enough.
// The default check type is CRC64, but fallback to CRC32 if (memory_usage <= memory_limit)
// if CRC64 isn't supported by the copy of liblzma we are break;
// using. CRC32 is always supported.
check = LZMA_CHECK_CRC64; // Otherwise 1 MiB down and try again. I hope this
if (!lzma_check_is_supported(check)) // isn't too slow method for cases where the original
check = LZMA_CHECK_CRC32; // dict_size is very big.
opt->dict_size -= UINT32_C(1) << 20;
} }
// Tell the user that we decreased the dictionary size.
message(V_WARNING, _("Adjusted LZMA%c dictionary size "
"from %s MiB to %s MiB to not exceed "
"the memory usage limit of %s MiB"),
filters[i].id == LZMA_FILTER_LZMA2
? '2' : '1',
uint64_to_str(orig_dict_size >> 20, 0),
uint64_to_str(opt->dict_size >> 20, 1),
uint64_to_str(round_up_to_mib(memory_limit), 2));
return; return;
} }
@ -356,7 +393,14 @@ coder_init(file_pair *pair)
break; break;
case FORMAT_XZ: case FORMAT_XZ:
ret = lzma_stream_encoder(&strm, filters, check); #ifdef HAVE_PTHREAD
if (hardware_threads_get() > 1)
ret = lzma_stream_encoder_mt(
&strm, &mt_options);
else
#endif
ret = lzma_stream_encoder(
&strm, filters, check);
break; break;
case FORMAT_LZMA: case FORMAT_LZMA:
@ -477,8 +521,8 @@ coder_normal(file_pair *pair)
// to the .xz format. If block_remaining == UINT64_MAX, only // to the .xz format. If block_remaining == UINT64_MAX, only
// a single block is created. // a single block is created.
uint64_t block_remaining = UINT64_MAX; uint64_t block_remaining = UINT64_MAX;
if (opt_mode == MODE_COMPRESS && opt_format == FORMAT_XZ if (hardware_threads_get() == 1 && opt_mode == MODE_COMPRESS
&& opt_block_size > 0) && opt_format == FORMAT_XZ && opt_block_size > 0)
block_remaining = opt_block_size; block_remaining = opt_block_size;
strm.next_out = out_buf.u8; strm.next_out = out_buf.u8;