use crate::encoding::CompressionLevel;
use crate::encoding::match_generator::{HC_SEARCH_DEPTH, HC_TARGET_LEN, ROW_MIN_MATCH_LEN};
#[cfg(test)]
use crate::encoding::match_generator::{ROW_HASH_BITS, ROW_LOG, ROW_SEARCH_DEPTH, ROW_TARGET_LEN};
#[cfg(test)]
use crate::encoding::match_table::storage::{HC_CHAIN_LOG, HC_HASH_LOG};
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) struct HcConfig {
pub(crate) hash_log: usize,
pub(crate) chain_log: usize,
pub(crate) search_depth: usize,
pub(crate) target_len: usize,
pub(crate) search_mls: usize,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) struct RowConfig {
pub(crate) hash_bits: usize,
pub(crate) row_log: usize,
pub(crate) search_depth: usize,
pub(crate) target_len: usize,
pub(crate) mls: usize,
pub(crate) chain_log: usize,
pub(crate) bt: bool,
}
#[cfg(test)]
pub(crate) const HC_CONFIG: HcConfig = HcConfig {
hash_log: HC_HASH_LOG,
chain_log: HC_CHAIN_LOG,
search_depth: HC_SEARCH_DEPTH,
target_len: HC_TARGET_LEN,
search_mls: 4,
};
pub(crate) const HC_OVERRIDE_DEFAULT: HcConfig = HcConfig {
hash_log: crate::encoding::match_table::storage::HC_HASH_LOG,
chain_log: crate::encoding::match_table::storage::HC_CHAIN_LOG,
search_depth: HC_SEARCH_DEPTH,
target_len: HC_TARGET_LEN,
search_mls: 4,
};
#[cfg(test)]
pub(crate) const ROW_CONFIG: RowConfig = RowConfig {
hash_bits: ROW_HASH_BITS,
row_log: ROW_LOG,
search_depth: ROW_SEARCH_DEPTH,
target_len: ROW_TARGET_LEN,
mls: ROW_MIN_MATCH_LEN,
chain_log: ROW_HASH_BITS,
bt: false,
};
pub(crate) const ROW_L5: RowConfig = RowConfig {
hash_bits: 19,
row_log: 4,
search_depth: 8,
target_len: 2,
mls: ROW_MIN_MATCH_LEN,
chain_log: 18,
bt: false,
};
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) struct DfastConfig {
pub(crate) long_hash_log: u8,
pub(crate) short_hash_log: u8,
}
pub(crate) const DFAST_L3: DfastConfig = DfastConfig {
long_hash_log: 17,
short_hash_log: 16,
};
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) struct FastConfig {
pub(crate) hash_log: u32,
pub(crate) mls: u32,
pub(crate) step_size: usize,
}
pub(crate) const FAST_L1: FastConfig = FastConfig {
hash_log: 14,
mls: 7,
step_size: 2,
};
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) struct LevelParams {
pub(crate) strategy_tag: crate::encoding::strategy::StrategyTag,
pub(crate) search: crate::encoding::strategy::SearchMethod,
pub(crate) window_log: u8,
pub(crate) lazy_depth: u8,
pub(crate) fast: Option<FastConfig>,
pub(crate) dfast: Option<DfastConfig>,
pub(crate) hc: Option<HcConfig>,
pub(crate) row: Option<RowConfig>,
}
impl LevelParams {
pub(crate) fn backend(&self) -> crate::encoding::strategy::BackendTag {
self.search.backend()
}
pub(crate) fn parse(&self) -> crate::encoding::strategy::ParseMode {
match self.search {
crate::encoding::strategy::SearchMethod::BinaryTree => {
crate::encoding::strategy::ParseMode::Optimal
}
_ => crate::encoding::strategy::ParseMode::from_lazy_depth(self.lazy_depth),
}
}
pub(crate) fn pre_split(&self) -> Option<u8> {
Some(pre_split_for(self.strategy_tag, self.lazy_depth))
}
}
pub(crate) fn pre_split_for(tag: crate::encoding::strategy::StrategyTag, lazy_depth: u8) -> u8 {
use crate::encoding::strategy::StrategyTag;
match tag {
StrategyTag::Fast => 0,
StrategyTag::Dfast => 1,
StrategyTag::Greedy => 2,
StrategyTag::Lazy => {
if lazy_depth >= 2 {
1
} else {
2
}
}
StrategyTag::Btlazy2 => 1,
StrategyTag::BtOpt | StrategyTag::BtUltra | StrategyTag::BtUltra2 => 2,
}
}
pub(crate) fn apply_param_overrides(
params: &mut LevelParams,
ov: &crate::encoding::parameters::ParamOverrides,
) {
use crate::encoding::strategy::SearchMethod;
if let Some(strategy) = ov.strategy {
let tag = strategy.tag();
params.strategy_tag = tag;
params.search = tag.search();
params.lazy_depth = strategy.lazy_depth();
}
match params.search {
SearchMethod::Fast => {
params.fast.get_or_insert(FAST_L1);
}
SearchMethod::DoubleFast => {
params.dfast.get_or_insert(DFAST_L3);
}
SearchMethod::RowHash | SearchMethod::BinaryTreeLazy => {
let row = params.row.get_or_insert(ROW_L5);
row.bt = matches!(params.search, SearchMethod::BinaryTreeLazy);
}
SearchMethod::HashChain | SearchMethod::BinaryTree => {
params.hc.get_or_insert(HC_OVERRIDE_DEFAULT);
}
}
if let Some(window_log) = ov.window_log {
params.window_log = window_log;
}
match params.search {
SearchMethod::Fast => {
if let Some(fast) = params.fast.as_mut() {
if let Some(hash_log) = ov.hash_log {
fast.hash_log = hash_log;
}
if let Some(min_match) = ov.min_match {
fast.mls = fast_key_len(min_match);
}
if let Some(target_length) = ov.target_length {
fast.step_size = (target_length as usize).max(1) + 1;
}
}
}
SearchMethod::DoubleFast => {
if let Some(dfast) = params.dfast.as_mut() {
if let Some(hash_log) = ov.hash_log {
dfast.long_hash_log = hash_log as u8;
}
if let Some(chain_log) = ov.chain_log {
dfast.short_hash_log = chain_log as u8;
}
}
}
SearchMethod::RowHash | SearchMethod::BinaryTreeLazy => {
if let Some(row) = params.row.as_mut() {
if let Some(hash_log) = ov.hash_log {
row.hash_bits = hash_log as usize;
}
if let Some(chain_log) = ov.chain_log {
row.chain_log = chain_log as usize;
}
if let Some(search_log) = ov.search_log {
row.row_log = (search_log as usize).clamp(4, 6);
row.search_depth = 1usize << search_log;
}
if let Some(target_length) = ov.target_length {
row.target_len = target_length as usize;
}
if let Some(min_match) = ov.min_match {
row.mls = min_match as usize;
}
}
}
SearchMethod::HashChain | SearchMethod::BinaryTree => {
if let Some(hc) = params.hc.as_mut() {
if let Some(hash_log) = ov.hash_log {
hc.hash_log = hash_log as usize;
}
if let Some(chain_log) = ov.chain_log {
hc.chain_log = chain_log as usize;
}
if let Some(search_log) = ov.search_log {
hc.search_depth = 1usize << search_log;
}
if let Some(target_length) = ov.target_length {
hc.target_len = target_length as usize;
}
if let Some(min_match) = ov.min_match {
hc.search_mls = (min_match as usize).clamp(3, 6);
}
}
}
}
}
fn fast_key_len(min_match: u32) -> u32 {
min_match.max(4)
}
#[cfg(feature = "ldm")]
pub(crate) fn ldm_strategy_ordinal(
tag: crate::encoding::strategy::StrategyTag,
lazy_depth: u8,
) -> u32 {
use crate::encoding::strategy::StrategyTag;
match tag {
StrategyTag::Fast => 1,
StrategyTag::Dfast => 2,
StrategyTag::Greedy => 3,
StrategyTag::Lazy => {
if lazy_depth >= 2 {
5
} else {
4
}
}
StrategyTag::Btlazy2 => 6,
StrategyTag::BtOpt => 7,
StrategyTag::BtUltra => 8,
StrategyTag::BtUltra2 => 9,
}
}
pub(crate) fn source_size_ceil_log(size: u64) -> u8 {
if size == 0 {
MIN_WINDOW_LOG
} else {
(64 - (size - 1).leading_zeros()) as u8
}
}
pub(crate) const FAST_ATTACH_DICT_CUTOFF_LOG: u8 = 13;
pub(crate) const MAX_FAST_ATTACH_DICT_REGION: usize = 1 << 24;
pub(crate) const DFAST_ATTACH_DICT_CUTOFF_LOG: u8 = 14;
pub(crate) const HC_ATTACH_DICT_CUTOFF_LOG: u8 = 15;
pub(crate) const BT_OPT_ATTACH_DICT_CUTOFF_LOG: u8 = 15;
pub(crate) const BT_ULTRA_ATTACH_DICT_CUTOFF_LOG: u8 = 13;
pub(crate) fn dfast_hash_bits_for_window(max_window_size: usize) -> usize {
let window_log = (usize::BITS - 1 - max_window_size.leading_zeros()) as usize;
window_log.max(MIN_WINDOW_LOG as usize)
}
pub(crate) fn row_hash_bits_for_window(max_window_size: usize) -> usize {
let window_log = (usize::BITS - 1 - max_window_size.leading_zeros()) as usize;
(window_log + 1).max(MIN_WINDOW_LOG as usize)
}
pub(crate) fn hc_hash_bits_for_window(max_window_size: usize) -> usize {
let window_log = (usize::BITS - 1 - max_window_size.leading_zeros()) as usize;
window_log.max(MIN_WINDOW_LOG as usize)
}
pub(crate) const MIN_WINDOW_LOG: u8 = 10;
pub(crate) const MAX_ESTIMATED_WINDOW_LOG: u8 = 30;
fn level_params_from_cparams(cp: crate::encoding::cparams::CParams) -> LevelParams {
use crate::encoding::strategy::{SearchMethod, StrategyTag};
let window_log = cp.window_log as u8;
let search_depth = 1usize << cp.search_log;
let target_len = cp.target_length as usize;
let hc = HcConfig {
hash_log: cp.hash_log as usize,
chain_log: cp.chain_log as usize,
search_depth,
target_len,
search_mls: cp.min_match.clamp(4, 6) as usize,
};
let row = RowConfig {
hash_bits: cp.hash_log as usize,
row_log: cp.search_log.clamp(4, 6) as usize,
search_depth,
target_len,
mls: cp.min_match as usize,
chain_log: cp.chain_log as usize,
bt: cp.strategy == 6,
};
let bt = |tag| LevelParams {
strategy_tag: tag,
search: SearchMethod::BinaryTree,
window_log,
lazy_depth: 2,
fast: None,
dfast: None,
hc: Some(hc),
row: None,
};
let row_lvl = |tag, search, lazy_depth| LevelParams {
strategy_tag: tag,
search,
window_log,
lazy_depth,
fast: None,
dfast: None,
hc: None,
row: Some(row),
};
match cp.strategy {
1 => LevelParams {
strategy_tag: StrategyTag::Fast,
search: SearchMethod::Fast,
window_log,
lazy_depth: 0,
fast: Some(FastConfig {
hash_log: cp.hash_log,
mls: fast_key_len(cp.min_match),
step_size: target_len.max(1) + 1,
}),
dfast: None,
hc: None,
row: None,
},
2 => LevelParams {
strategy_tag: StrategyTag::Dfast,
search: SearchMethod::DoubleFast,
window_log,
lazy_depth: 1,
fast: None,
dfast: Some(DfastConfig {
long_hash_log: cp.hash_log as u8,
short_hash_log: cp.chain_log as u8,
}),
hc: None,
row: None,
},
3 => row_lvl(StrategyTag::Greedy, SearchMethod::RowHash, 0),
4 => row_lvl(StrategyTag::Lazy, SearchMethod::RowHash, 1),
5 => row_lvl(StrategyTag::Lazy, SearchMethod::RowHash, 2),
6 => row_lvl(StrategyTag::Btlazy2, SearchMethod::BinaryTreeLazy, 2),
7 => bt(StrategyTag::BtOpt),
8 => bt(StrategyTag::BtUltra),
_ => bt(StrategyTag::BtUltra2),
}
}
pub(crate) fn adjust_params_for_source_size(mut params: LevelParams, src_size: u64) -> LevelParams {
use crate::encoding::cparams::{CParams, adjust_cparams};
use crate::encoding::strategy::{BackendTag, StrategyTag};
let backend = params.backend();
let (hash_log, chain_log): (u32, u32) = match backend {
BackendTag::Simple => (params.fast.as_ref().map_or(0, |f| f.hash_log), 0),
BackendTag::HashChain => params
.hc
.as_ref()
.map_or((0, 0), |h| (h.hash_log as u32, h.chain_log as u32)),
BackendTag::Row => params
.row
.as_ref()
.map_or((0, 0), |r| (r.hash_bits as u32, r.chain_log as u32)),
BackendTag::Dfast => (0, 0),
};
let strategy = if matches!(
params.strategy_tag,
StrategyTag::Btlazy2 | StrategyTag::BtOpt | StrategyTag::BtUltra | StrategyTag::BtUltra2
) {
6
} else {
3
};
let adj = adjust_cparams(
CParams {
window_log: u32::from(params.window_log),
chain_log,
hash_log,
search_log: 1,
min_match: 4,
target_length: 0,
strategy,
},
src_size,
0,
false,
);
params.window_log = adj.window_log as u8;
match backend {
BackendTag::Simple => {
if let Some(f) = params.fast.as_mut() {
f.hash_log = adj.hash_log;
}
}
BackendTag::HashChain => {
if let Some(h) = params.hc.as_mut() {
h.hash_log = adj.hash_log as usize;
h.chain_log = adj.chain_log as usize;
}
}
BackendTag::Row => {
if let Some(r) = params.row.as_mut() {
r.hash_bits = adj.hash_log as usize;
r.chain_log = adj.chain_log as usize;
}
}
BackendTag::Dfast => {}
}
params
}
pub(crate) fn apply_frame_overrides(
params: &mut LevelParams,
ov: &crate::encoding::parameters::ParamOverrides,
dictionary_frame: bool,
hint: Option<u64>,
) {
if ov.is_empty() {
return;
}
if dictionary_frame {
if let Some(window_log) = ov.window_log {
params.window_log = match hint {
Some(src) => {
(crate::encoding::cparams::adjusted_window_log(u32::from(window_log), src, 0)
as u8)
.max(MIN_WINDOW_LOG)
}
None => window_log,
};
}
} else {
apply_param_overrides(params, ov);
if let Some(hint_size) = hint {
*params = adjust_params_for_source_size(*params, hint_size);
}
}
}
#[cfg(feature = "ldm")]
pub(crate) fn frame_ldm_params(
params: &LevelParams,
ldm: &crate::encoding::parameters::LdmOverride,
) -> crate::encoding::ldm::params::LdmParams {
let seed = crate::encoding::ldm::params::LdmParams {
window_log: params.window_log as u32,
hash_log: ldm.hash_log.unwrap_or(0),
hash_rate_log: ldm.hash_rate_log.unwrap_or(0),
min_match_length: ldm.min_match.unwrap_or(0),
bucket_size_log: ldm.bucket_size_log.unwrap_or(0),
};
seed.derive(ldm_strategy_ordinal(params.strategy_tag, params.lazy_depth))
}
pub fn estimated_compression_workspace_bytes(level: CompressionLevel) -> usize {
estimated_compression_workspace_bytes_for_source(level, None)
}
pub fn estimated_compression_workspace_bytes_for_source(
level: CompressionLevel,
src_size_hint: Option<u64>,
) -> usize {
estimated_compression_workspace_bytes_for_run(level, src_size_hint, None, false, None)
}
pub fn estimated_compression_workspace_bytes_for_run(
level: CompressionLevel,
src_size_hint: Option<u64>,
window_log: Option<u8>,
long_distance_matching: bool,
dictionary: Option<crate::encoding::DictionarySizes>,
) -> usize {
let mut params = match dictionary.filter(|sizes| sizes.content != 0) {
Some(sizes) => {
resolve_level_params_with_dict(
level,
src_size_hint,
sizes,
&crate::encoding::parameters::ParamOverrides::default(),
)
.0
}
None => resolve_level_params(level, src_size_hint),
};
if let Some(requested) = window_log {
let requested = requested.min(MAX_ESTIMATED_WINDOW_LOG);
let capped = match src_size_hint {
Some(src) => {
crate::encoding::cparams::adjusted_window_log(u32::from(requested), src, 0) as u8
}
None => requested,
};
params.window_log = capped.clamp(MIN_WINDOW_LOG, MAX_ESTIMATED_WINDOW_LOG);
}
#[cfg(feature = "ldm")]
let ldm = if long_distance_matching {
let strategy = ldm_strategy_ordinal(params.strategy_tag, params.lazy_depth);
let ldm_params = crate::encoding::ldm::params::LdmParams::adjust_for(
u32::from(params.window_log),
strategy,
);
crate::encoding::ldm::table::LdmHashTable::estimated_workspace_bytes(
ldm_params.hash_log,
ldm_params.bucket_size_log,
)
} else {
0
};
#[cfg(not(feature = "ldm"))]
let ldm = {
let _ = long_distance_matching;
0
};
workspace_bytes(¶ms, ldm)
}
pub fn estimated_compression_workspace_bytes_for_parameters(
parameters: &crate::encoding::CompressionParameters,
src_size_hint: Option<u64>,
dictionary: Option<crate::encoding::DictionarySizes>,
) -> usize {
let level = parameters.level();
let overrides = parameters.overrides();
let dictionary = dictionary.filter(|sizes| sizes.content != 0);
let mut params = match dictionary {
Some(sizes) => resolve_level_params_with_dict(level, src_size_hint, sizes, &overrides).0,
None => resolve_level_params(level, src_size_hint),
};
apply_frame_overrides(&mut params, &overrides, dictionary.is_some(), src_size_hint);
#[cfg(feature = "ldm")]
let ldm = overrides.ldm.map_or(0, |ldm| {
let ldm_params = frame_ldm_params(¶ms, &ldm);
crate::encoding::ldm::table::LdmHashTable::estimated_workspace_bytes(
ldm_params.hash_log,
ldm_params.bucket_size_log,
)
});
#[cfg(not(feature = "ldm"))]
let ldm = 0;
workspace_bytes(¶ms, ldm)
}
fn table_bytes(entry: usize, log: usize) -> usize {
u32::try_from(log)
.ok()
.and_then(|log| 1usize.checked_shl(log))
.and_then(|slots| slots.checked_mul(entry))
.unwrap_or(usize::MAX)
}
fn workspace_bytes(params: &LevelParams, ldm: usize) -> usize {
use crate::encoding::strategy::{SearchMethod, StrategyTag};
let window = 1usize
.checked_shl(u32::from(params.window_log))
.unwrap_or(usize::MAX);
let wants_hash3 = matches!(
params.strategy_tag,
StrategyTag::BtUltra | StrategyTag::BtUltra2
);
let uses_bt = matches!(
params.strategy_tag,
StrategyTag::BtOpt | StrategyTag::BtUltra | StrategyTag::BtUltra2
);
let tables = match params.search {
SearchMethod::Fast => params
.fast
.map_or(0, |f| table_bytes(4, f.hash_log as usize)),
SearchMethod::DoubleFast => params.dfast.map_or(0, |d| {
table_bytes(4, usize::from(d.long_hash_log))
.saturating_add(table_bytes(4, usize::from(d.short_hash_log)))
}),
SearchMethod::RowHash | SearchMethod::BinaryTreeLazy => params.row.map_or(0, |r| {
if r.bt || params.window_log <= 14 {
table_bytes(4, r.hash_bits).saturating_add(table_bytes(4, r.chain_log))
} else {
table_bytes(4, r.hash_bits).saturating_add(table_bytes(2, r.hash_bits))
}
}),
SearchMethod::HashChain | SearchMethod::BinaryTree => params.hc.map_or(0, |h| {
let hash3 = if wants_hash3 {
table_bytes(
4,
crate::encoding::match_table::storage::HC3_HASH_LOG
.min(params.window_log as usize),
)
} else {
0
};
table_bytes(4, h.hash_log)
.saturating_add(table_bytes(4, h.chain_log))
.saturating_add(hash3)
}),
};
let bt = if uses_bt {
crate::encoding::bt::BtMatcher::estimated_workspace_bytes()
} else {
0
};
let staging = 3 * (128 * 1024);
window
.saturating_add(tables)
.saturating_add(bt)
.saturating_add(staging)
.saturating_add(ldm)
}
pub fn estimated_bt_strategy_extra_bytes(strategy_ordinal: u32, window_log: u32) -> usize {
if !(7..=9).contains(&strategy_ordinal) {
return 0;
}
let hash3 = if matches!(strategy_ordinal, 8 | 9) {
4usize << crate::encoding::match_table::storage::HC3_HASH_LOG.min(window_log as usize)
} else {
0
};
crate::encoding::bt::BtMatcher::estimated_workspace_bytes() + hash3
}
pub(crate) fn resolve_level_params(
level: CompressionLevel,
source_size: Option<u64>,
) -> LevelParams {
if matches!(level, CompressionLevel::Uncompressed) {
return LevelParams {
strategy_tag: crate::encoding::strategy::StrategyTag::Fast,
search: crate::encoding::strategy::SearchMethod::Fast,
window_log: 17,
lazy_depth: 0,
fast: Some(FastConfig {
hash_log: 14,
mls: 6,
step_size: 2,
}),
dfast: None,
hc: None,
row: None,
};
}
let numeric = numeric_level(level);
let src = source_size.unwrap_or(crate::encoding::cparams::CONTENTSIZE_UNKNOWN);
level_params_from_cparams(crate::encoding::cparams::get_cparams(numeric, src, 0))
}
pub(crate) fn numeric_level(level: CompressionLevel) -> i32 {
match level {
CompressionLevel::Uncompressed => unreachable!("raw frames resolve no cParams"),
CompressionLevel::Fastest => 1,
CompressionLevel::Default => CompressionLevel::DEFAULT_LEVEL,
CompressionLevel::Better => 7,
CompressionLevel::Best => 13,
CompressionLevel::Level(n) => n,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct RowDictPlan {
pub(crate) attach: bool,
pub(crate) use_row: bool,
pub(crate) cdict: crate::encoding::cparams::CParams,
}
pub(crate) fn resolve_level_params_with_dict(
level: CompressionLevel,
source_size: Option<u64>,
sizes: crate::encoding::DictionarySizes,
overrides: &crate::encoding::parameters::ParamOverrides,
) -> (LevelParams, Option<RowDictPlan>) {
use crate::encoding::cparams::{
CONTENTSIZE_UNKNOWN, attach_cparams, copy_cparams, get_cdict_cparams, should_attach_dict,
uses_row_match_finder,
};
let base = resolve_level_params(level, source_size);
if sizes.content == 0 {
return (base, None);
}
let cdict = get_cdict_cparams(numeric_level(level), sizes.serialized, overrides);
let attach_fits = match cdict.strategy {
1 => sizes.content <= MAX_FAST_ATTACH_DICT_REGION,
2 => sizes.content <= crate::encoding::dfast::DFAST_ATTACH_DICT_MAX_LEN,
_ => true,
};
let attach = should_attach_dict(&cdict, source_size) && attach_fits;
let window_log = u32::from(base.window_log);
let frame = if attach {
attach_cparams(
cdict,
source_size.unwrap_or(CONTENTSIZE_UNKNOWN),
window_log,
)
} else {
copy_cparams(cdict, window_log)
};
let params = level_params_from_cparams(frame);
if !(3..=6).contains(&cdict.strategy) {
return (params, None);
}
(
params,
Some(RowDictPlan {
attach,
use_row: uses_row_match_finder(&cdict),
cdict,
}),
)
}
pub(crate) fn level_pre_split(level: CompressionLevel) -> Option<usize> {
if matches!(level, CompressionLevel::Uncompressed) {
return None;
}
resolve_level_params(level, None)
.pre_split()
.map(usize::from)
}
#[cfg(test)]
mod tests;