blob: 028922be1eb1f97bbd49da07c3c873957943fc12 [file] [log] [blame]
/* Copyright (C) 2007 Josh MacDonald */
extern "C" {
#include "test.h"
#include <assert.h>
}
#include <list>
#include <vector>
#include <map>
#include <algorithm>
using std::list;
using std::map;
using std::vector;
// MLCG parameters
// a, a*
uint32_t good_32bit_values[] = {
1597334677U, // ...
741103597U, 887987685U,
};
// a, a*
uint64_t good_64bit_values[] = {
1181783497276652981ULL, 4292484099903637661ULL,
7664345821815920749ULL, // ...
};
struct true_type { };
struct false_type { };
template <typename Word>
int bitsof();
template<>
int bitsof<uint32_t>() {
return 32;
}
template<>
int bitsof<uint64_t>() {
return 64;
}
struct plain {
int operator()(const uint8_t &c) {
return c;
}
};
template <typename Word>
struct hhash { // take "h" of the high-bits as a hash value for this
// checksum, which are the most "distant" in terms of the
// spectral test for the rabin_karp MLCG. For short windows,
// the high bits aren't enough, XOR "mask" worth of these in.
Word operator()(const Word& t, const int &h, const int &mask) {
return (t >> h) ^ (t & mask);
}
};
template <typename Word>
Word good_word();
template<>
uint32_t good_word<uint32_t>() {
return good_32bit_values[0];
}
template<>
uint64_t good_word<uint64_t>() {
return good_64bit_values[0];
}
// CLASSES
#define SELF Word, CksumSize, CksumSkip, Permute, Hash, Compaction
#define MEMBER template <typename Word, \
int CksumSize, \
int CksumSkip, \
typename Permute, \
typename Hash, \
int Compaction>
MEMBER
struct cksum_params {
typedef Word word_type;
typedef Permute permute_type;
typedef Hash hash_type;
enum { cksum_size = CksumSize,
cksum_skip = CksumSkip,
compaction = Compaction,
};
};
MEMBER
struct rabin_karp {
typedef Word word_type;
typedef Permute permute_type;
typedef Hash hash_type;
enum { cksum_size = CksumSize,
cksum_skip = CksumSkip,
compaction = Compaction,
};
// (a^cksum_size-1 c_0) + (a^cksum_size-2 c_1) ...
rabin_karp() {
multiplier = good_word<Word>();
powers = new Word[cksum_size];
powers[cksum_size - 1] = 1;
for (int i = cksum_size - 2; i >= 0; i--) {
powers[i] = powers[i + 1] * multiplier;
}
product = powers[0] * multiplier;
}
~rabin_karp() {
delete [] powers;
}
Word step(const uint8_t *ptr) {
Word h = 0;
for (int i = 0; i < cksum_size; i++) {
h += permute_type()(ptr[i]) * powers[i];
}
return h;
}
Word state0(const uint8_t *ptr) {
incr_state = step(ptr);
return incr_state;
}
Word incr(const uint8_t *ptr) {
incr_state = multiplier * incr_state -
product * permute_type()(ptr[-1]) +
permute_type()(ptr[cksum_size - 1]);
return incr_state;
}
Word *powers;
Word product;
Word multiplier;
Word incr_state;
};
MEMBER
struct adler32_cksum {
typedef Word word_type;
typedef Permute permute_type;
typedef Hash hash_type;
enum { cksum_size = CksumSize,
cksum_skip = CksumSkip,
compaction = Compaction,
};
Word step(const uint8_t *ptr) {
return xd3_lcksum (ptr, cksum_size);
}
Word state0(const uint8_t *ptr) {
incr_state = step(ptr);
return incr_state;
}
Word incr(const uint8_t *ptr) {
incr_state = xd3_large_cksum_update (incr_state, ptr - 1, cksum_size);
return incr_state;
}
Word incr_state;
};
// TESTS
template <typename Word>
struct file_stats {
typedef list<const uint8_t*> ptr_list;
typedef Word word_type;
typedef map<word_type, ptr_list> table_type;
typedef typename table_type::iterator table_iterator;
typedef typename ptr_list::iterator ptr_iterator;
int cksum_size;
int cksum_skip;
int unique;
int unique_values;
int count;
table_type table;
file_stats(int size, int skip)
: cksum_size(size),
cksum_skip(skip),
unique(0),
unique_values(0),
count(0) {
}
void reset() {
unique = 0;
unique_values = 0;
count = 0;
table.clear();
}
void update(const word_type &word, const uint8_t *ptr) {
table_iterator t_i = table.find(word);
count++;
if (t_i == table.end()) {
table.insert(make_pair(word, ptr_list()));
}
ptr_list &pl = table[word];
for (ptr_iterator p_i = pl.begin();
p_i != pl.end();
++p_i) {
if (memcmp(*p_i, ptr, cksum_size) == 0) {
return;
}
}
unique++;
pl.push_back(ptr);
}
void freeze() {
unique_values = table.size();
table.clear();
}
};
struct test_result_base;
static vector<test_result_base*> all_tests;
struct test_result_base {
virtual ~test_result_base() {
}
virtual void reset() = 0;
virtual void print() = 0;
virtual void get(const uint8_t* buf, const int buf_size, int iters) = 0;
virtual void stat() = 0;
virtual int count() = 0;
virtual int dups() = 0;
virtual double uniqueness() = 0;
virtual double fullness() = 0;
virtual double collisions() = 0;
virtual double coverage() = 0;
virtual double compression() = 0;
virtual double time() = 0;
virtual double score() = 0;
virtual void set_score(double min_dups_frac, double min_time) = 0;
virtual double total_time() = 0;
virtual int total_count() = 0;
virtual int total_dups() = 0;
};
struct compare_h {
bool operator()(test_result_base *a,
test_result_base *b) {
return a->score() < b->score();
}
};
MEMBER
struct test_result : public test_result_base {
typedef Word word_type;
typedef Permute permute_type;
typedef Hash hash_type;
enum { cksum_size = CksumSize,
cksum_skip = CksumSkip,
compaction = Compaction,
};
const char *test_name;
file_stats<Word> fstats;
int test_size;
int n_steps;
int n_incrs;
int s_bits;
int s_mask;
int t_entries;
int h_bits;
int h_buckets_full;
double h_score;
char *hash_table;
long accum_millis;
int accum_iters;
// These are not reset
double accum_time;
int accum_count;
int accum_dups;
int accum_colls;
int accum_size;
test_result(const char *name)
: test_name(name),
fstats(cksum_size, cksum_skip),
hash_table(NULL),
accum_millis(0),
accum_iters(0),
accum_time(0.0),
accum_count(0),
accum_dups(0),
accum_colls(0),
accum_size(0) {
all_tests.push_back(this);
}
~test_result() {
reset();
}
void reset() {
// size of file
test_size = -1;
// count
n_steps = -1;
n_incrs = -1;
// four values used by new_table()/summarize_table()
s_bits = -1;
s_mask = -1;
t_entries = -1;
h_bits = -1;
h_buckets_full = -1;
accum_millis = 0;
accum_iters = 0;
fstats.reset();
// temporary
if (hash_table) {
delete(hash_table);
hash_table = NULL;
}
}
int count() {
if (cksum_skip == 1) {
return n_incrs;
} else {
return n_steps;
}
}
int dups() {
return fstats.count - fstats.unique;
}
int colls() {
return fstats.unique - fstats.unique_values;
}
double uniqueness() {
return 1.0 - (double) dups() / count();
}
double fullness() {
return (double) h_buckets_full / (1 << h_bits);
}
double collisions() {
return (double) colls() / fstats.unique;
}
double coverage() {
return (double) h_buckets_full / uniqueness() / count();
}
double compression() {
return 1.0 - coverage();
}
double time() {
return (double) accum_millis / accum_iters;
}
double score() {
return h_score;
}
void set_score(double min_compression, double min_time) {
h_score = (compression() - 0.99 * min_compression)
* (time() - 0.99 * min_time);
}
double total_time() {
return accum_time;
}
int total_count() {
return accum_count;
}
int total_dups() {
return accum_dups;
}
int total_colls() {
return accum_dups;
}
void stat() {
accum_time += time();
accum_count += count();
accum_dups += dups();
accum_colls += colls();
accum_size += test_size;
}
void print() {
if (fstats.count != count()) {
fprintf(stderr, "internal error: %d != %d\n", fstats.count, count());
abort();
}
printf("%s: (%u#%u) count %u uniq %0.2f%% full %u (%0.4f%% coll %0.4f%%) covers %0.2f%% w/ 2^%d @ %.4f MB/s %u iters\n",
test_name,
cksum_size,
cksum_skip,
count(),
100.0 * uniqueness(),
h_buckets_full,
100.0 * fullness(),
100.0 * collisions(),
100.0 * coverage(),
h_bits,
0.001 * accum_iters * test_size / accum_millis,
accum_iters);
}
int size_log2 (int slots)
{
int bits = bitsof<word_type>() - 1;
int i;
for (i = 3; i <= bits; i += 1) {
if (slots <= (1 << i)) {
return i - compaction;
}
}
return bits;
}
void new_table(int entries) {
t_entries = entries;
h_bits = size_log2(entries);
int n = 1 << h_bits;
s_bits = bitsof<word_type>() - h_bits;
s_mask = n - 1;
hash_table = new char[n / 8];
memset(hash_table, 0, n / 8);
}
int get_table_bit(int i) {
return hash_table[i/8] & (1 << i%8);
}
int set_table_bit(int i) {
return hash_table[i/8] |= (1 << i%8);
}
void summarize_table() {
int n = 1 << h_bits;
int f = 0;
for (int i = 0; i < n; i++) {
if (get_table_bit(i)) {
f++;
}
}
h_buckets_full = f;
}
void get(const uint8_t* buf, const int buf_size, int test_iters) {
rabin_karp<SELF> test;
//adler32_cksum<SELF> test;
hash_type hash;
const uint8_t *ptr;
const uint8_t *end;
int last_offset;
int periods;
int stop;
test_size = buf_size;
last_offset = buf_size - cksum_size;
if (last_offset < 0) {
periods = 0;
n_steps = 0;
n_incrs = 0;
stop = -cksum_size;
} else {
periods = last_offset / cksum_skip;
n_steps = periods + 1;
n_incrs = last_offset + 1;
stop = last_offset - (periods + 1) * cksum_skip;
}
// Compute file stats once.
if (fstats.unique_values == 0) {
if (cksum_skip == 1) {
for (int i = 0; i <= buf_size - cksum_size; i++) {
fstats.update(hash(test.step(buf + i), s_bits, s_mask), buf + i);
}
} else {
ptr = buf + last_offset;
end = buf + stop;
for (; ptr != end; ptr -= cksum_skip) {
fstats.update(hash(test.step(ptr), s_bits, s_mask), ptr);
}
}
fstats.freeze();
}
long start_test = get_millisecs_now();
if (cksum_skip != 1) {
new_table(n_steps);
for (int i = 0; i < test_iters; i++) {
ptr = buf + last_offset;
end = buf + stop;
for (; ptr != end; ptr -= cksum_skip) {
set_table_bit(hash(test.step(ptr), s_bits, s_mask));
}
}
summarize_table();
}
stop = buf_size - cksum_size + 1;
if (stop < 0) {
stop = 0;
}
if (cksum_skip == 1) {
new_table(n_incrs);
for (int i = 0; i < test_iters; i++) {
ptr = buf;
end = buf + stop;
if (ptr != end) {
set_table_bit(hash(test.state0(ptr++), s_bits, s_mask));
}
for (; ptr != end; ptr++) {
Word w = test.incr(ptr);
assert(w == test.step(ptr));
set_table_bit(hash(w, s_bits, s_mask));
}
}
summarize_table();
}
accum_iters += test_iters;
accum_millis += get_millisecs_now() - start_test;
}
};
template <typename Word>
void print_array(const char *tname) {
printf("static const %s hash_multiplier[64] = {\n", tname);
Word p = 1;
for (int i = 0; i < 64; i++) {
printf(" %uU,\n", p);
p *= good_word<Word>();
}
printf("};\n", tname);
}
int main(int argc, char** argv) {
int i;
uint8_t *buf = NULL;
size_t buf_len = 0;
int ret;
if (argc <= 1) {
fprintf(stderr, "usage: %s file ...\n", argv[0]);
return 1;
}
//print_array<uint32_t>("uint32_t");
#define TEST(T,Z,S,P,H,C) test_result<T,Z,S,P,H<T>,C> \
_ ## T ## _ ## Z ## _ ## S ## _ ## P ## _ ## H ## _ ## C \
(#T "_" #Z "_" #S "_" #P "_" #H "_" #C)
#if 0
TEST(uint32_t, 4, SKIP, plain, hhash, 0); /* x */ \
TEST(uint32_t, 4, SKIP, plain, hhash, 1); /* x */ \
TEST(uint32_t, 4, SKIP, plain, hhash, 2); /* x */ \
TEST(uint32_t, 4, SKIP, plain, hhash, 3); /* x */ \
#endif
#define TESTS(SKIP) \
TEST(uint32_t, 9, SKIP, plain, hhash, 0); /* x */ \
TEST(uint32_t, 9, SKIP, plain, hhash, 1); /* x */ \
TEST(uint32_t, 9, SKIP, plain, hhash, 2); /* x */ \
TEST(uint32_t, 9, SKIP, plain, hhash, 3)
#define TESTS_ALL(SKIP) \
TEST(uint32_t, 3, SKIP, plain, hhash, 0); \
TEST(uint32_t, 3, SKIP, plain, hhash, 1); \
TEST(uint32_t, 4, SKIP, plain, hhash, 0); /* x */ \
TEST(uint32_t, 4, SKIP, plain, hhash, 1); /* x */ \
TEST(uint32_t, 4, SKIP, plain, hhash, 2); /* x */ \
TEST(uint32_t, 4, SKIP, plain, hhash, 3); /* x */ \
TEST(uint32_t, 5, SKIP, plain, hhash, 0); \
TEST(uint32_t, 5, SKIP, plain, hhash, 1); \
TEST(uint32_t, 8, SKIP, plain, hhash, 0); \
TEST(uint32_t, 8, SKIP, plain, hhash, 1); \
TEST(uint32_t, 9, SKIP, plain, hhash, 0); /* x */ \
TEST(uint32_t, 9, SKIP, plain, hhash, 1); /* x */ \
TEST(uint32_t, 9, SKIP, plain, hhash, 2); /* x */ \
TEST(uint32_t, 9, SKIP, plain, hhash, 3); /* x */ \
TEST(uint32_t, 11, SKIP, plain, hhash, 0); /* x */ \
TEST(uint32_t, 11, SKIP, plain, hhash, 1); /* x */ \
TEST(uint32_t, 13, SKIP, plain, hhash, 0); \
TEST(uint32_t, 13, SKIP, plain, hhash, 1); \
TEST(uint32_t, 15, SKIP, plain, hhash, 0); /* x */ \
TEST(uint32_t, 15, SKIP, plain, hhash, 1); /* x */ \
TEST(uint32_t, 16, SKIP, plain, hhash, 0); /* x */ \
TEST(uint32_t, 16, SKIP, plain, hhash, 1); /* x */ \
TEST(uint32_t, 21, SKIP, plain, hhash, 0); \
TEST(uint32_t, 21, SKIP, plain, hhash, 1); \
TEST(uint32_t, 34, SKIP, plain, hhash, 0); \
TEST(uint32_t, 34, SKIP, plain, hhash, 1); \
TEST(uint32_t, 55, SKIP, plain, hhash, 0); \
TEST(uint32_t, 55, SKIP, plain, hhash, 1)
TESTS(1); // *
// TESTS(2); // *
// TESTS(3); // *
// TESTS(5); // *
// TESTS(8); // *
// TESTS(9);
// TESTS(11);
// TESTS(13); // *
TESTS(15);
// TESTS(16);
// TESTS(21); // *
// TESTS(34); // *
// TESTS(55); // *
// TESTS(89); // *
for (i = 1; i < argc; i++) {
if ((ret = read_whole_file(argv[i],
& buf,
& buf_len))) {
return 1;
}
fprintf(stderr, "file %s is %zu bytes\n",
argv[i], buf_len);
double min_time = -1.0;
double min_compression = 0.0;
for (vector<test_result_base*>::iterator i = all_tests.begin();
i != all_tests.end(); ++i) {
test_result_base *test = *i;
test->reset();
int iters = 100;
long start_test = get_millisecs_now();
do {
test->get(buf, buf_len, iters);
iters *= 3;
iters /= 2;
} while (get_millisecs_now() - start_test < 2000);
test->stat();
if (min_time < 0.0) {
min_compression = test->compression();
min_time = test->time();
}
if (min_time > test->time()) {
min_time = test->time();
}
if (min_compression > test->compression()) {
min_compression = test->compression();
}
test->print();
}
// for (vector<test_result_base*>::iterator i = all_tests.begin();
// i != all_tests.end(); ++i) {
// test_result_base *test = *i;
// test->set_score(min_compression, min_time);
// }
// sort(all_tests.begin(), all_tests.end(), compare_h());
// for (vector<test_result_base*>::iterator i = all_tests.begin();
// i != all_tests.end(); ++i) {
// test_result_base *test = *i;
// test->print();
// }
free(buf);
buf = NULL;
}
return 0;
}