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,
}
#[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,
};
pub(crate) const ROW_L5: RowConfig = RowConfig {
hash_bits: 19,
row_log: 4,
search_depth: 8,
target_len: 2,
mls: ROW_MIN_MATCH_LEN,
};
#[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> {
use crate::encoding::strategy::StrategyTag;
Some(match self.strategy_tag {
StrategyTag::Fast | StrategyTag::Dfast => 0,
StrategyTag::Greedy => 0,
StrategyTag::Lazy => {
if self.lazy_depth >= 2 {
1 } else {
0 }
}
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 => {
params.row.get_or_insert(ROW_L5);
}
SearchMethod::HashChain | SearchMethod::BinaryTree => {
params.hc.get_or_insert(HcConfig {
search_mls: if matches!(
params.strategy_tag,
crate::encoding::strategy::StrategyTag::Btlazy2
) {
5
} else {
HC_OVERRIDE_DEFAULT.search_mls
},
..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 = min_match;
}
}
}
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 => {
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(search_log) = ov.search_log {
let row_log = (search_log as usize).clamp(4, 6);
row.row_log = row_log;
row.search_depth = 1usize << (search_log as usize).min(row_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);
}
}
}
}
}
#[cfg(feature = "hash")]
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 = 31;
pub(crate) const MAX_FAST_ATTACH_DICT_REGION: usize = 1 << 24;
pub(crate) const DFAST_ATTACH_DICT_CUTOFF_LOG: u8 = 14;
pub(crate) const ROW_ATTACH_DICT_CUTOFF_LOG: u8 = 15;
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) fn cdict_table_logs(
window_log: u8,
hash_log: usize,
chain_log: usize,
uses_bt: bool,
dict_size: usize,
) -> (usize, usize) {
let (h, c) = crate::encoding::cparams::create_cdict_table_logs(
window_log,
hash_log as u32,
chain_log as u32,
uses_bt,
dict_size,
);
(h as usize, c as usize)
}
pub(crate) const MIN_WINDOW_LOG: u8 = 10;
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,
};
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, lazy_depth| LevelParams {
strategy_tag: tag,
search: SearchMethod::RowHash,
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: 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, 0),
4 => row_lvl(StrategyTag::Lazy, 1),
5 => row_lvl(StrategyTag::Lazy, 2),
6 => bt(StrategyTag::Btlazy2),
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, |r| r.hash_bits as u32), 0),
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;
}
}
BackendTag::Dfast => {}
}
params
}
pub fn estimated_compression_workspace_bytes(level: CompressionLevel) -> usize {
use crate::encoding::strategy::StrategyTag;
let params = resolve_level_params(level, None);
let window = 1usize << params.window_log;
let wants_hash3 = matches!(
params.strategy_tag,
StrategyTag::BtUltra | StrategyTag::BtUltra2
);
let uses_bt = matches!(
params.strategy_tag,
StrategyTag::Btlazy2 | StrategyTag::BtOpt | StrategyTag::BtUltra | StrategyTag::BtUltra2
);
let tables = params.fast.map(|f| 4usize << f.hash_log).unwrap_or(0)
+ params
.dfast
.map(|d| (4usize << d.long_hash_log) + (4usize << d.short_hash_log))
.unwrap_or(0)
+ params
.hc
.map(|h| {
let hash3 = if wants_hash3 {
4usize
<< crate::encoding::match_table::storage::HC3_HASH_LOG
.min(params.window_log as usize)
} else {
0
};
(4usize << h.hash_log) + (4usize << h.chain_log) + hash3
})
.unwrap_or(0)
+ params
.row
.map(|r| (4usize << r.hash_bits) + (2usize << r.hash_bits))
.unwrap_or(0);
let bt = if uses_bt {
crate::encoding::bt::BtMatcher::estimated_workspace_bytes()
} else {
0
};
let staging = 3 * (128 * 1024);
window + tables + bt + staging
}
pub fn estimated_bt_strategy_extra_bytes(strategy_ordinal: u32, window_log: u32) -> usize {
if !(6..=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: i32 = match level {
CompressionLevel::Uncompressed => unreachable!("handled above"),
CompressionLevel::Fastest => 1,
CompressionLevel::Default => CompressionLevel::DEFAULT_LEVEL,
CompressionLevel::Better => 7,
CompressionLevel::Best => 13,
CompressionLevel::Level(n) => n,
};
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 level_pre_split(level: CompressionLevel) -> Option<usize> {
if matches!(level, CompressionLevel::Uncompressed) {
return None;
}
resolve_level_params(level, None)
.pre_split()
.map(usize::from)
}