Compare commits

...
2 Commits
Author SHA1 Message Date
krosh ebb00ee69f rm more magick numbers 2026-08-07 04:50:05 +03:00
krosh eda5e3454a rm some magick numbers 2026-08-07 04:23:20 +03:00
3 changed files with 134 additions and 144 deletions
+1
View File
@@ -1,3 +1,4 @@
BasedOnStyle: Google
IndentWidth: 2
BracedInitializerIndentWidth: 2
ColumnLimit: 100
+1 -1
View File
@@ -36,5 +36,5 @@
## улучшение работы с хэшем
- если условно "отсортировать" массив provides/requires, можно получить лучшую работу с кжшом
- если условно "отсортировать" массив provides/requires, можно получить лучшую работу с кэшом
- (надеюсь, что под капотом оно уже и так это делает, но проверить стоит)
+131 -142
View File
@@ -2,6 +2,7 @@
#undef NDEBUG
#endif
#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -15,6 +16,38 @@
#include "system.h"
#define CACHE_SIZE 512
#define SET_HASH_SEED UINT32_C(0x9e3779b9)
#define CACHE_FINGERPRINT_MULTIPLIER UINT32_C(0x9e3779b1)
#define CACHE_TARGET_BPP_MULTIPLIER UINT32_C(0x85ebca6b)
enum {
CACHE_BUCKETS = 1024,
SET_HEADER_SIZE = 2,
SET_PARAM_CHAR_OFFSET = 7,
SET_BPP_MIN = 10,
SET_BPP_MAX = 32,
SET_MSHIFT_MIN = 7,
SET_MSHIFT_MAX = 31,
BASE62_ESCAPE_BITS = 4,
BASE62_VALUE_BITS = 6,
BASE62_ESCAPE_CHUNK_BITS = BASE62_ESCAPE_BITS + BASE62_VALUE_BITS,
BASE62_MIN_BITS_PER_CHAR = BASE62_VALUE_BITS - 1,
BASE62_MAX_PADDING_BITS = BASE62_VALUE_BITS - 1,
BASE62_LOWERCASE_OFFSET = 10,
BASE62_UPPERCASE_OFFSET = 36,
BASE62_ESCAPE_VALUE = 61,
BASE62_VALUE_COUNT = 62,
BASE62_ESCAPE_LOW_MASK = (1u << BASE62_ESCAPE_BITS) - 1,
BASE62_ESCAPE_HIGH_MASK = 3u << BASE62_ESCAPE_BITS,
BASE62_INVALID = 0xee,
BASE62_END = UCHAR_MAX,
};
_Static_assert(CHAR_BIT == 8, "set:version requires 8-bit bytes");
struct set {
size_t cnt;
@@ -78,7 +111,7 @@ struct set* set_free(struct set* set) {
// ---
static unsigned hash(const char* str) {
unsigned hash = 0x9e3779b9;
unsigned hash = SET_HASH_SEED;
const unsigned char* p = (const unsigned char*)str;
while (*p) {
@@ -158,8 +191,8 @@ static int encode_golomb_Mshift(int cnt, int bpp) {
int Mshift = bpp - log2i(cnt) - 1;
// Adjust out-of-range values.
Mshift = (Mshift < 7) ? 7 : Mshift;
Mshift = (Mshift > 31) ? 31 : Mshift;
Mshift = (Mshift < SET_MSHIFT_MIN) ? SET_MSHIFT_MIN : Mshift;
Mshift = (Mshift > SET_MSHIFT_MAX) ? SET_MSHIFT_MAX : Mshift;
assert(Mshift < bpp);
return Mshift;
@@ -169,7 +202,7 @@ static int encode_golomb_Mshift(int cnt, int bpp) {
static inline int encode_golomb_size(int cnt, int Mshift) {
// XXX No precise estimation. However, we do not expect unary-encoded bits
// to take more than binary-encoded Mshift bits.
return Mshift * 2 * cnt + 16;
return 2 * Mshift * cnt + 16;
}
// Estimate base62 buffer size required to encode a given number of bits.
@@ -177,14 +210,14 @@ static inline int encode_base62_size(int bit_cnt) {
// In the worst case, which is ZxZxZx..., five bits can make a character;
// the remaining bits can make a character, too. And the string must be
// null-terminated.
return bit_cnt / 5 + 2;
return bit_cnt / BASE62_MIN_BITS_PER_CHAR + 2;
}
static int encode_set_size(int cnt, int bpp) {
int Mshift = encode_golomb_Mshift(cnt, bpp);
int bit_cnt = encode_golomb_size(cnt, Mshift);
// two leading characters are special
return 2 + encode_base62_size(bit_cnt);
// The leading characters encode bpp and Mshift.
return SET_HEADER_SIZE + encode_base62_size(bit_cnt);
}
// ---
@@ -205,7 +238,7 @@ static int encode_set_size(int cnt, int bpp) {
// ---
static inline char encode_bpp(int bpp) { return (char)(bpp - 7 + 'a'); }
static inline char encode_bpp(int bpp) { return (char)(bpp - SET_PARAM_CHAR_OFFSET + 'a'); }
struct encode_writer {
uint64_t bits;
@@ -216,20 +249,20 @@ struct encode_writer {
};
static inline void encode_writer_digit(struct encode_writer* writer, unsigned value) {
assert(value < 62);
assert(value < BASE62_VALUE_COUNT);
if (value < 10) {
if (value < BASE62_LOWERCASE_OFFSET) {
*writer->output++ = (char)('0' + value);
} else if (value < 36) {
*writer->output++ = (char)('a' + value - 10);
} else if (value < BASE62_UPPERCASE_OFFSET) {
*writer->output++ = (char)('a' + value - BASE62_LOWERCASE_OFFSET);
} else {
*writer->output++ = (char)('A' + value - 36);
*writer->output++ = (char)('A' + value - BASE62_UPPERCASE_OFFSET);
}
}
static inline void encode_writer_flush(struct encode_writer* writer) {
for (;;) {
unsigned width = writer->escaped ? 4u : 6u;
unsigned width = writer->escaped ? BASE62_ESCAPE_BITS : BASE62_VALUE_BITS;
if (writer->filled < width) return;
unsigned value = (unsigned)writer->bits & ((1u << width) - 1);
@@ -239,9 +272,9 @@ static inline void encode_writer_flush(struct encode_writer* writer) {
if (writer->escaped) {
encode_writer_digit(writer, writer->pending_high | value);
writer->escaped = 0;
} else if (value >= 61) {
encode_writer_digit(writer, 61);
writer->pending_high = (value - 61) << 4;
} else if (value >= BASE62_ESCAPE_VALUE) {
encode_writer_digit(writer, BASE62_ESCAPE_VALUE);
writer->pending_high = (value - BASE62_ESCAPE_VALUE) << BASE62_ESCAPE_BITS;
writer->escaped = 1;
} else {
encode_writer_digit(writer, value);
@@ -250,6 +283,8 @@ static inline void encode_writer_flush(struct encode_writer* writer) {
}
static inline void encode_writer_zeros(struct encode_writer* writer, unsigned count) {
// encode_writer_flush() leaves fewer than BASE62_VALUE_BITS bits buffered.
// Adding at most 56 bits therefore cannot overflow uint64_t.
while (count) {
unsigned take = count > 56 ? 56 : count;
writer->filled += take;
@@ -306,9 +341,9 @@ const char* set_fini(struct set* set, int bpp) {
assert(set != NULL);
assert(set->cnt > 0);
assert(bpp >= 10 && bpp <= 32);
assert(bpp >= SET_BPP_MIN && bpp <= SET_BPP_MAX);
unsigned mask = (bpp < 32) ? (1u << bpp) - 1 : ~0u;
unsigned mask = (bpp < SET_BPP_MAX) ? (1u << bpp) - 1 : ~0u;
for (size_t i = 0; i < set->cnt; ++i) {
set->symbols_v[i].hash = hash(set->strings + set->symbols_v[i].offset) & mask;
@@ -357,21 +392,21 @@ struct set_meta {
};
static int set_meta_init(const char* str, struct set_meta* meta) {
// len >= 3
if (!str[0] || !str[1] || !str[2]) return -4;
// The header must be followed by at least one payload character.
if (!str[0] || !str[1] || !str[SET_HEADER_SIZE]) return -4;
int bpp = str[0] + 7 - 'a';
if (bpp < 10 || bpp > 32) return -1;
int bpp = str[0] + SET_PARAM_CHAR_OFFSET - 'a';
if (bpp < SET_BPP_MIN || bpp > SET_BPP_MAX) return -1;
int Mshift = str[1] + 7 - 'a';
if (Mshift < 7 || Mshift > 31) return -2;
int Mshift = str[1] + SET_PARAM_CHAR_OFFSET - 'a';
if (Mshift < SET_MSHIFT_MIN || Mshift > SET_MSHIFT_MAX) return -2;
if (Mshift >= bpp) return -3;
*meta = (struct set_meta){
.str = str,
.payload = str + 2,
.bpp = bpp,
.Mshift = Mshift,
.str = str,
.payload = str + SET_HEADER_SIZE,
.bpp = bpp,
.Mshift = Mshift,
};
return 0;
@@ -380,9 +415,9 @@ static int set_meta_init(const char* str, struct set_meta* meta) {
static int set_meta_fini(struct set_meta* meta) {
size_t len = strlen(meta->str);
size_t payload_len = len - 2;
size_t payload_len = len - SET_HEADER_SIZE;
int bit_capacity = (int)payload_len * 6;
int bit_capacity = (int)payload_len * BASE62_VALUE_BITS;
int value_capacity = bit_capacity / (meta->Mshift + 1);
if (value_capacity < 1) return -4;
@@ -394,104 +429,59 @@ static int set_meta_fini(struct set_meta* meta) {
return 0;
}
// UCHAR_MAX == 255
__extension__ static const unsigned char char_to_num[255 + 1] = {[0] = 0xff, /* конец строки */
// clang-format off
__extension__ static const unsigned char char_to_num[UCHAR_MAX + 1] = {
[0] = BASE62_END, // конец строки
[1 ... ('0' - 1)] = BASE62_INVALID,
[1 ...('0' - 1)] = 0xee,
['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4,
['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9,
['0'] = 0,
['1'] = 1,
['2'] = 2,
['3'] = 3,
['4'] = 4,
['5'] = 5,
['6'] = 6,
['7'] = 7,
['8'] = 8,
['9'] = 9,
[('9' + 1) ... ('A' - 1)] = BASE62_INVALID,
[('9' + 1)...('A' - 1)] = 0xee,
['A'] = 36, ['B'] = 37, ['C'] = 38, ['D'] = 39, ['E'] = 40,
['F'] = 41, ['G'] = 42, ['H'] = 43, ['I'] = 44, ['J'] = 45,
['K'] = 46, ['L'] = 47, ['M'] = 48, ['N'] = 49, ['O'] = 50,
['P'] = 51, ['Q'] = 52, ['R'] = 53, ['S'] = 54, ['T'] = 55,
['U'] = 56, ['V'] = 57, ['W'] = 58, ['X'] = 59, ['Y'] = 60,
['Z'] = BASE62_ESCAPE_VALUE,
['A'] = 36,
['B'] = 37,
['C'] = 38,
['D'] = 39,
['E'] = 40,
['F'] = 41,
['G'] = 42,
['H'] = 43,
['I'] = 44,
['J'] = 45,
['K'] = 46,
['L'] = 47,
['M'] = 48,
['N'] = 49,
['O'] = 50,
['P'] = 51,
['Q'] = 52,
['R'] = 53,
['S'] = 54,
['T'] = 55,
['U'] = 56,
['V'] = 57,
['W'] = 58,
['X'] = 59,
['Y'] = 60,
['Z'] = 61,
[('Z' + 1) ... ('a' - 1)] = BASE62_INVALID,
[('Z' + 1)...('a' - 1)] = 0xee,
['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14,
['f'] = 15, ['g'] = 16, ['h'] = 17, ['i'] = 18, ['j'] = 19,
['k'] = 20, ['l'] = 21, ['m'] = 22, ['n'] = 23, ['o'] = 24,
['p'] = 25, ['q'] = 26, ['r'] = 27, ['s'] = 28, ['t'] = 29,
['u'] = 30, ['v'] = 31, ['w'] = 32, ['x'] = 33, ['y'] = 34,
['z'] = 35,
['a'] = 10,
['b'] = 11,
['c'] = 12,
['d'] = 13,
['e'] = 14,
['f'] = 15,
['g'] = 16,
['h'] = 17,
['i'] = 18,
['j'] = 19,
['k'] = 20,
['l'] = 21,
['m'] = 22,
['n'] = 23,
['o'] = 24,
['p'] = 25,
['q'] = 26,
['r'] = 27,
['s'] = 28,
['t'] = 29,
['u'] = 30,
['v'] = 31,
['w'] = 32,
['x'] = 33,
['y'] = 34,
['z'] = 35,
[('z' + 1) ... UCHAR_MAX] = BASE62_INVALID,
};
// clang-format on
[('z' + 1)... 255] = 0xee};
// Decode base62 and Golomb-Rice in one pass. Base62 is LSB-first; a Z escape contributes 10 stream
// bits.
// Decode base62 and Golomb-Rice in one pass. Base62 is LSB-first; a Z escape contributes
// BASE62_ESCAPE_CHUNK_BITS stream bits.
static inline int decode_chunk(const unsigned char** input, uint64_t* chunk, unsigned* width) {
unsigned value = char_to_num[*(*input)++];
if (value < 61) {
if (value < BASE62_ESCAPE_VALUE) {
*chunk = value;
*width = 6;
*width = BASE62_VALUE_BITS;
return 1;
}
if (value == 0xff) return 0;
if (value == 0xee) return -1;
if (value == BASE62_END) return 0;
if (value == BASE62_INVALID) return -1;
unsigned escaped = char_to_num[*(*input)++];
if (escaped == 0xff) return -2;
if (escaped == 0xee) return -3;
if (escaped == BASE62_END) return -2;
if (escaped == BASE62_INVALID) return -3;
unsigned high = escaped & 0x30u;
if (high == 0x30u) return -4;
unsigned high = escaped & BASE62_ESCAPE_HIGH_MASK;
if (high == BASE62_ESCAPE_HIGH_MASK) return -4;
*chunk = (61u + (high >> 4)) | ((uint64_t)(escaped & 0x0fu) << 6);
*width = 10;
*chunk = (BASE62_ESCAPE_VALUE + (high >> BASE62_ESCAPE_BITS)) |
((uint64_t)(escaped & BASE62_ESCAPE_LOW_MASK) << BASE62_VALUE_BITS);
*width = BASE62_ESCAPE_CHUNK_BITS;
return 1;
}
@@ -515,7 +505,7 @@ static int decode_set(const struct set_meta* meta, unsigned* hash_arr) {
int rc = decode_chunk(&input, &chunk, &width);
if (rc < 0) return rc;
if (rc == 0) return q <= 5 ? count : -10;
if (rc == 0) return q <= BASE62_MAX_PADDING_BITS ? count : -10;
bits = chunk;
filled = width;
@@ -565,9 +555,17 @@ static int decode_set(const struct set_meta* meta, unsigned* hash_arr) {
// Bounded decoded-set cache: bucketed lookup plus O(1) LRU updates.
static int downsample_set(int cnt, const unsigned* hash_pt, unsigned* ds_pt, int bpp);
static inline unsigned cache_bucket(uint32_t fingerprint, int target_bpp) {
uint32_t mixed = fingerprint ^ ((uint32_t)target_bpp * CACHE_TARGET_BPP_MULTIPLIER);
mixed ^= mixed >> 11;
mixed *= CACHE_FINGERPRINT_MULTIPLIER;
mixed ^= mixed >> 16;
return mixed & (CACHE_BUCKETS - 1);
}
static int cache_decode_set(struct set_meta* meta, int target_bpp, const unsigned** hash_pt,
unsigned cache_id) {
enum { CACHE_BUCKETS = 1024 };
struct cache_ent {
struct cache_ent* bucket_next;
struct cache_ent* newer;
@@ -589,11 +587,7 @@ static int cache_decode_set(struct set_meta* meta, int target_bpp, const unsigne
const unsigned char* str = (const unsigned char*)meta->str;
uint32_t fp = (uint32_t)str[0] | ((uint32_t)str[2] << 8) | ((uint32_t)str[3] << 16);
uint32_t mixed = fp ^ ((uint32_t)target_bpp * UINT32_C(0x85ebca6b));
mixed ^= mixed >> 11;
mixed *= UINT32_C(0x9e3779b1);
mixed ^= mixed >> 16;
unsigned bucket = mixed & (CACHE_BUCKETS - 1);
unsigned bucket = cache_bucket(fp, target_bpp);
for (struct cache_ent* ent = buckets[cache_id][bucket]; ent; ent = ent->bucket_next) {
if (ent->fingerprint != fp || ent->target_bpp != target_bpp || strcmp(meta->str, ent->str) != 0)
@@ -659,12 +653,7 @@ static int cache_decode_set(struct set_meta* meta, int target_bpp, const unsigne
if (oldest[cache_id]) oldest[cache_id]->older = NULL;
if (victim == newest[cache_id]) newest[cache_id] = NULL;
uint32_t victim_mixed =
victim->fingerprint ^ ((uint32_t)victim->target_bpp * UINT32_C(0x85ebca6b));
victim_mixed ^= victim_mixed >> 11;
victim_mixed *= UINT32_C(0x9e3779b1);
victim_mixed ^= victim_mixed >> 16;
unsigned victim_bucket = victim_mixed & (CACHE_BUCKETS - 1);
unsigned victim_bucket = cache_bucket(victim->fingerprint, victim->target_bpp);
struct cache_ent** link = &buckets[cache_id][victim_bucket];
while (*link != victim) link = &(*link)->bucket_next;
*link = victim->bucket_next;
@@ -866,8 +855,8 @@ static void test_hash(void) {
static void test_sort(void) {
struct symbols small[] = {
{.offset = 0, .hash = 9}, {.offset = 1, .hash = 1}, {.offset = 2, .hash = 7},
{.offset = 3, .hash = 3}, {.offset = 4, .hash = 5},
{.offset = 0, .hash = 9}, {.offset = 1, .hash = 1}, {.offset = 2, .hash = 7},
{.offset = 3, .hash = 3}, {.offset = 4, .hash = 5},
};
const unsigned small_expected[] = {1, 3, 5, 7, 9};
const size_t offset_expected[] = {1, 3, 4, 2, 0};
@@ -906,8 +895,8 @@ static void test_sort(void) {
static void test_encode_decode(void) {
const unsigned original_values[] = {
0x020a, 0x07e5, 0x3305, 0x35f5, 0x4980, 0x4c4f, 0x74ef, 0x7739,
0x82ae, 0x8415, 0xa3e7, 0xb07e, 0xb584, 0xb89f, 0xbb40, 0xf39e,
0x020a, 0x07e5, 0x3305, 0x35f5, 0x4980, 0x4c4f, 0x74ef, 0x7739,
0x82ae, 0x8415, 0xa3e7, 0xb07e, 0xb584, 0xb89f, 0xbb40, 0xf39e,
};
const int original_count = (int)(sizeof(original_values) / sizeof(*original_values));
@@ -954,10 +943,10 @@ static void test_encode_decode(void) {
}
static void test_metadata_and_chunks(void) {
for (int c = 0; c <= 255; ++c) {
unsigned char expected = 0xee;
for (int c = 0; c <= UCHAR_MAX; ++c) {
unsigned char expected = BASE62_INVALID;
if (c == 0) {
expected = 0xff;
expected = BASE62_END;
} else if (c >= '0' && c <= '9') {
expected = (unsigned char)(c - '0');
} else if (c >= 'a' && c <= 'z') {
@@ -976,16 +965,16 @@ static void test_metadata_and_chunks(void) {
};
const struct chunk_case cases[] = {
{.input = "0", .rc = 1, .chunk = 0, .width = 6},
{.input = "Y", .rc = 1, .chunk = 60, .width = 6},
{.input = "Z0", .rc = 1, .chunk = 61, .width = 10},
{.input = "Zg", .rc = 1, .chunk = 62, .width = 10},
{.input = "Zw", .rc = 1, .chunk = 63, .width = 10},
{.input = "", .rc = 0, .chunk = 0, .width = 0},
{.input = "!", .rc = -1, .chunk = 0, .width = 0},
{.input = "Z", .rc = -2, .chunk = 0, .width = 0},
{.input = "Z!", .rc = -3, .chunk = 0, .width = 0},
{.input = "ZM", .rc = -4, .chunk = 0, .width = 0},
{.input = "0", .rc = 1, .chunk = 0, .width = 6},
{.input = "Y", .rc = 1, .chunk = 60, .width = 6},
{.input = "Z0", .rc = 1, .chunk = 61, .width = 10},
{.input = "Zg", .rc = 1, .chunk = 62, .width = 10},
{.input = "Zw", .rc = 1, .chunk = 63, .width = 10},
{.input = "", .rc = 0, .chunk = 0, .width = 0},
{.input = "!", .rc = -1, .chunk = 0, .width = 0},
{.input = "Z", .rc = -2, .chunk = 0, .width = 0},
{.input = "Z!", .rc = -3, .chunk = 0, .width = 0},
{.input = "ZM", .rc = -4, .chunk = 0, .width = 0},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(*cases); ++i) {