mirror of
https://github.com/claunia/findcrcs.git
synced 2025-12-16 10:44:25 +00:00
1106 lines
33 KiB
C++
1106 lines
33 KiB
C++
// Copyright 2010 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Functionality and performance tests of available CRC implementations.
|
|
|
|
#ifndef CRCUTIL_UNITTEST_H_
|
|
#define CRCUTIL_UNITTEST_H_
|
|
|
|
#if defined(_MSC_VER)
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 4820) // structure was padded
|
|
#pragma warning(disable: 4710) // function not inlined
|
|
// C++ exception handler used, but unwind
|
|
// semantics are not enabled. Specify /EHsc
|
|
#pragma warning(disable: 4530)
|
|
#endif // defined(_MSC_VER)
|
|
|
|
#include <map>
|
|
#include <set>
|
|
#include <string>
|
|
|
|
#if defined(_MSC_VER)
|
|
#pragma warning(pop)
|
|
#endif // defined(_MSC_VER)
|
|
|
|
#include "aligned_alloc.h"
|
|
#include "bob_jenkins_rng.h"
|
|
#include "crc32c_sse4.h"
|
|
#include "generic_crc.h"
|
|
#include "rdtsc.h"
|
|
#include "rolling_crc.h"
|
|
#include "unittest_helper.h"
|
|
|
|
namespace crcutil {
|
|
|
|
#if defined(_INTEL_COMPILER)
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 185) // dynamic initialization in unreachable code
|
|
#endif // defined(_INTEL_COMPILER)
|
|
|
|
#if HAVE_AMD64 || HAVE_I386
|
|
|
|
class Crc32cSSE4_Test : public Crc32cSSE4 {
|
|
public:
|
|
void *operator new(size_t, void *p) {
|
|
return p;
|
|
}
|
|
};
|
|
|
|
template<typename Crc, typename TableEntry, typename Word, int kStride>
|
|
class GenericCrcTest : public GenericCrc<Crc, TableEntry, Word, kStride> {
|
|
public:
|
|
~GenericCrcTest() {
|
|
AlignedFree(aligned_memory_);
|
|
}
|
|
|
|
void InitWithCrc32c(const Crc &generating_polynomial,
|
|
size_t degree,
|
|
bool constant) {
|
|
this->Init(generating_polynomial, degree, constant);
|
|
|
|
crc32c_ = NULL;
|
|
aligned_memory_ = NULL;
|
|
|
|
// Bug in GCC 4.4.3: if (kStride == 4) comparison is put first
|
|
// so that the compiler may optimize out entire "if" statement,
|
|
// GCC 4.4.3 at -O3 level generates incorrect code corrupting "this".
|
|
if (degree == Crc32cSSE4::FixedDegree() &&
|
|
generating_polynomial == Crc32cSSE4::FixedGeneratingPolynomial() &&
|
|
(!CRCUTIL_USE_MM_CRC32 || Crc32cSSE4::IsSSE42Available()) &&
|
|
kStride == 4 &&
|
|
sizeof(Crc) == sizeof(size_t)) {
|
|
aligned_memory_ = AlignedAlloc(sizeof(*crc32c_), 0, 256, NULL);
|
|
crc32c_ = new(aligned_memory_) Crc32cSSE4_Test;
|
|
crc32c_->Init(constant);
|
|
}
|
|
}
|
|
|
|
bool HaveCrc32c() const {
|
|
return (crc32c_ != NULL);
|
|
}
|
|
|
|
Crc CRC32C(const void *data, size_t bytes, const Crc &start) const {
|
|
return static_cast<Crc>(
|
|
crc32c_->CrcDefault(data, bytes, Downcast<Crc, size_t>(start)));
|
|
}
|
|
|
|
private:
|
|
Crc32cSSE4_Test *crc32c_;
|
|
void *aligned_memory_;
|
|
};
|
|
|
|
#else
|
|
|
|
template<typename Crc, typename TableEntry, typename Word, int kStride>
|
|
class GenericCrcTest : public GenericCrc<Crc, TableEntry, Word, kStride> {
|
|
public:
|
|
~GenericCrcTest() {}
|
|
void InitWithCrc32c(const Crc &generating_polynomial,
|
|
size_t degree,
|
|
bool constant) {
|
|
Init(generating_polynomial, degree, constant);
|
|
}
|
|
|
|
bool HaveCrc32c() const {
|
|
return false;
|
|
}
|
|
|
|
Crc CRC32C(const void * /* data */,
|
|
size_t /* bytes */,
|
|
const Crc & /* start */) const {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
#endif // HAVE_AMD64 || HAVE_I386
|
|
|
|
// Forward declaration.
|
|
class PerfTestState;
|
|
|
|
class CrcVerifierInterface {
|
|
public:
|
|
virtual void TestFunctionality(const char *class_title) const = 0;
|
|
virtual void TestPerformance(FILE *output,
|
|
PerfTestState *state,
|
|
bool multiword_only,
|
|
const char *class_title) = 0;
|
|
CrcVerifierInterface() {}
|
|
|
|
protected:
|
|
virtual ~CrcVerifierInterface() {}
|
|
};
|
|
|
|
// Finds the most efficient implementation algorithm for
|
|
// given CRC.
|
|
class AlgSorter {
|
|
public:
|
|
void Add(std::string crc_name, std::string alg_name, double cycles) {
|
|
CrcInfo *crc_info = crcs_[crc_name];
|
|
if (crc_info == NULL) {
|
|
crc_info = new CrcInfo;
|
|
crcs_[crc_name] = crc_info;
|
|
}
|
|
AlgStat *alg_stat = (*crc_info)[alg_name];
|
|
if (alg_stat == NULL) {
|
|
alg_stat = new AlgStat;
|
|
alg_stat->name = alg_name;
|
|
alg_stat->cycles = 0;
|
|
alg_stat->tests = 0;
|
|
(*crc_info)[alg_name] = alg_stat;
|
|
}
|
|
alg_stat->cycles += cycles;
|
|
alg_stat->tests += 1;
|
|
}
|
|
|
|
void PrintBest() {
|
|
for (Crcs::const_iterator it = crcs_.begin(); it != crcs_.end(); ++it) {
|
|
PrintBestCrc(it->first, *(it->second));
|
|
}
|
|
}
|
|
|
|
private:
|
|
struct AlgStat {
|
|
std::string name;
|
|
double cycles;
|
|
int tests;
|
|
};
|
|
|
|
typedef std::map<std::string, AlgStat *> CrcInfo;
|
|
typedef std::map<std::string, CrcInfo *> Crcs;
|
|
|
|
struct AlgStatCompare {
|
|
bool operator()(const AlgStat *a, const AlgStat *b) const {
|
|
return ((a->cycles / a->tests) < (b->cycles / b->tests));
|
|
}
|
|
};
|
|
|
|
void PrintBestCrc(std::string crc_name, const CrcInfo &crc_info) {
|
|
typedef std::set<AlgStat *, AlgStatCompare> SortedAlgStat;
|
|
SortedAlgStat alg_set;
|
|
for (CrcInfo::const_iterator it = crc_info.begin();
|
|
it != crc_info.end();
|
|
++it) {
|
|
alg_set.insert(it->second);
|
|
}
|
|
printf("%s\n", crc_name.c_str());
|
|
for (SortedAlgStat::const_iterator it = alg_set.begin();
|
|
it != alg_set.end();
|
|
++it) {
|
|
printf(" %-28s %6.3f cycles/byte\n",
|
|
(*it)->name.c_str(),
|
|
(*it)->cycles / (*it)->tests);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
Crcs crcs_;
|
|
};
|
|
|
|
|
|
class PerfTestState {
|
|
public:
|
|
enum {
|
|
MAX_TEST_COUNT = 100,
|
|
};
|
|
|
|
explicit PerfTestState(FILE *output)
|
|
: output_(output), test_count_(0), title_printed_(false) {}
|
|
~PerfTestState() {}
|
|
|
|
void Add(uint32 bytes, const char *name, double cycles) {
|
|
if (test_count_ < MAX_TEST_COUNT) {
|
|
bytes_ = bytes;
|
|
test_[test_count_].name = name;
|
|
test_[test_count_].cycles = cycles;
|
|
test_count_ += 1;
|
|
}
|
|
|
|
if (bytes >= 1024 && bytes <= 1024 * 1024 &&
|
|
memcmp(name, "MemSpeed", 9) != 0) {
|
|
crc_sorter_.Add(crc_name_, std::string(name) + "-" + alg_name_, cycles);
|
|
}
|
|
}
|
|
|
|
void Print() {
|
|
if (!title_printed_) {
|
|
for (size_t i = 0; i < test_count_; ++i) {
|
|
fprintf(output_, ", %s", test_[i].name);
|
|
}
|
|
fprintf(output_, ",, BestTime, BestMethod");
|
|
fprintf(output_, "\n");
|
|
fflush(output_);
|
|
title_printed_ = true;
|
|
}
|
|
double min_cycles = 1e30;
|
|
size_t min_index = 0;
|
|
fprintf(output_, "%9u", bytes_);
|
|
for (size_t i = 0; i < test_count_; ++i) {
|
|
fprintf(output_, ", %6.3f", test_[i].cycles);
|
|
if (min_cycles > test_[i].cycles &&
|
|
memcmp(test_[i].name, "MemSpeed", 9) != 0) {
|
|
min_cycles = test_[i].cycles;
|
|
min_index = i + 1;
|
|
}
|
|
}
|
|
if (min_index != 0) {
|
|
min_index -= 1;
|
|
fprintf(output_,
|
|
",, %6.3f, %s",
|
|
test_[min_index].cycles,
|
|
test_[min_index].name);
|
|
}
|
|
fprintf(output_, "\n");
|
|
fflush(output_);
|
|
test_count_ = 0;
|
|
}
|
|
|
|
void Finish() {
|
|
fprintf(output_, "\n");
|
|
fflush(output_);
|
|
test_count_ = 0;
|
|
title_printed_ = false;
|
|
}
|
|
|
|
void StartNewCrc(const char *name) {
|
|
// Assuming the name is in following format:
|
|
// CRC-NNN-ALG-MODE where
|
|
// NNN - CRC width
|
|
// ALG - algorithm
|
|
// MODE - mode (raw or canonical)
|
|
const char *second_dash;
|
|
second_dash = strchr(name, '-');
|
|
if (second_dash != NULL) {
|
|
second_dash = strchr(second_dash + 1, '-');
|
|
}
|
|
if (second_dash == NULL) {
|
|
second_dash = name + strlen(name);
|
|
}
|
|
std::string str_name(name, static_cast<size_t>(second_dash - name));
|
|
crc_name_ = str_name;
|
|
|
|
if (*second_dash == '-') {
|
|
++second_dash;
|
|
}
|
|
const char *last_dash = strrchr(second_dash, '-');
|
|
if (last_dash == NULL) {
|
|
last_dash = name + strlen(name);
|
|
}
|
|
std::string str_alg_name(second_dash,
|
|
static_cast<size_t>(last_dash - second_dash));
|
|
alg_name_ = str_alg_name;
|
|
}
|
|
|
|
void PrintBest() {
|
|
crc_sorter_.PrintBest();
|
|
}
|
|
|
|
private:
|
|
PerfTestState();
|
|
|
|
FILE *output_;
|
|
size_t test_count_;
|
|
bool title_printed_;
|
|
uint32 bytes_;
|
|
struct {
|
|
const char *name;
|
|
double cycles;
|
|
} test_[MAX_TEST_COUNT];
|
|
|
|
std::string crc_name_;
|
|
std::string alg_name_;
|
|
AlgSorter crc_sorter_;
|
|
};
|
|
|
|
template<typename CrcImplementation>
|
|
class RollingCrcTest
|
|
: public RollingCrc<CrcImplementation> {
|
|
public:
|
|
void *operator new(size_t, void *p) {
|
|
return p;
|
|
}
|
|
};
|
|
|
|
template<typename Crc, typename TableEntry, typename Word, int kStride>
|
|
class CrcTest : public GenericCrcTest<Crc, TableEntry, Word, kStride>,
|
|
public CrcVerifierInterface {
|
|
public:
|
|
typedef CrcTest<Crc, TableEntry, Word, kStride> CrcTestSelf;
|
|
typedef RollingCrcTest< GenericCrc<Crc, TableEntry, Word, kStride> >
|
|
RollingCrcSelf;
|
|
|
|
typedef Crc(CrcTestSelf::*CrcCallback) (
|
|
const void *data, size_t bytes, const Crc &start) const;
|
|
|
|
void *operator new(size_t, void *p) {
|
|
return p;
|
|
}
|
|
|
|
void VerifyPow() const {
|
|
Crc x = this->Base().One() >> 1;
|
|
Crc x_pow_i = x;
|
|
for (size_t i = 1; i < 1024; ++i) {
|
|
Crc value = this->Base().XpowN(i);
|
|
CHECK_EQ(x_pow_i, value);
|
|
x_pow_i = this->Base().Multiply(x_pow_i, x);
|
|
}
|
|
}
|
|
|
|
void VerifyCrcZeroes() const {
|
|
uint8 buf[256];
|
|
memset(buf, 0, sizeof(buf));
|
|
for (size_t k = 0; k < 256; ++k) {
|
|
Crc zero_old = this->CrcByte(buf, k, 1);
|
|
Crc zero_new = this->Base().CrcOfZeroes(k, 1);
|
|
CHECK_EQ(zero_old, zero_new);
|
|
}
|
|
}
|
|
|
|
void VerifyChangeStartValue() const {
|
|
#if defined(_MSC_VER) && _MSC_FULL_VER <= 150030729 && defined(_M_IX86)
|
|
// Work around a bug in CL.
|
|
if (sizeof(Crc) > 8) {
|
|
return;
|
|
}
|
|
#endif // defined(_MSC_VER) && _MSC_FULL_VER < 150030729 && defined(_M_IX86)
|
|
uint8 buf[256];
|
|
BobJenkinsRng<size_t> rng;
|
|
for (size_t i = 0; i < 256; ++i) {
|
|
buf[i] = static_cast<uint8>(rng.Get());
|
|
}
|
|
|
|
for (size_t n = 1; n < 256 && (n >> this->Base().Degree()) == 0; ++n) {
|
|
for (size_t i = 0;
|
|
i < 4096 && (i >> this->Base().Degree()) == 0;
|
|
i += 11) {
|
|
Crc crc_old = this->CrcByte(buf, n, 0);
|
|
Crc crc_new = this->CrcByte(buf, n, static_cast<Crc>(i));
|
|
Crc crc_chg = this->Base().ChangeStartValue(crc_old, n, 0,
|
|
static_cast<Crc>(i));
|
|
CHECK_EQ(crc_new, crc_chg);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VerifyConcatenate() const {
|
|
#if defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 150030729 && defined(_M_IX86)
|
|
// Work around a bug in CL
|
|
if (sizeof(Crc) > 8) {
|
|
return;
|
|
}
|
|
#endif
|
|
uint8 buf[256];
|
|
BobJenkinsRng<size_t> rng;
|
|
for (size_t i = 0; i < 256; ++i) {
|
|
buf[i] = static_cast<uint8>(rng.Get());
|
|
}
|
|
for (size_t n = 1; n < 256; ++n) {
|
|
for (size_t i = 0; i < n; ++i) {
|
|
if (((i + 1) >> this->Base().Degree()) != 0) {
|
|
break;
|
|
}
|
|
Crc startA = static_cast<Crc>(i + 1);
|
|
Crc crc_A = this->CrcByte(buf, i, startA);
|
|
Crc crc_B = this->CrcByte(buf + i, n - i, 0);
|
|
Crc crc_AB = this->CrcByte(buf, n, startA);
|
|
Crc crc = this->Base().Concatenate(crc_A, crc_B, n - i);
|
|
CHECK_EQ(crc_AB, crc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VerifyCb(CrcCallback cb, const char *test_name) const {
|
|
uint8 buf[256];
|
|
|
|
fprintf(stderr, " %s\n", test_name);
|
|
fflush(stderr);
|
|
|
|
// See whether it handles 0 bytes correctly.
|
|
(this->*cb)(NULL, 0, 0);
|
|
|
|
BobJenkinsRng<size_t> rng;
|
|
|
|
for (size_t n = 0; n < 256; ++n) {
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
// Check "lonely 1" pattern.
|
|
for (size_t k = 0; k < n; ++k) {
|
|
buf[k] = 0x80;
|
|
Crc crc_old = this->CrcByte(buf, n, 0);
|
|
Crc crc_new = (this->*cb)(buf, n, 0);
|
|
CHECK_EQ(crc_old, crc_new);
|
|
buf[k] = 0;
|
|
}
|
|
|
|
// Check "lonely random byte" pattern.
|
|
for (size_t k = 0; k < n; ++k) {
|
|
buf[k] = static_cast<uint8>(rng.Get());
|
|
Crc crc_old = this->CrcByte(buf, n, 0);
|
|
Crc crc_new = (this->*cb)(buf, n, 0);
|
|
CHECK_EQ(crc_old, crc_new);
|
|
buf[k] = 0;
|
|
}
|
|
|
|
// Check "starts from sequence of 1s" pattern.
|
|
for (size_t k = 0; k < n; ++k) {
|
|
buf[k] = 0x80;
|
|
Crc crc_old = this->CrcByte(buf, n, 0);
|
|
Crc crc_new = (this->*cb)(buf, n, 0);
|
|
CHECK_EQ(crc_old, crc_new);
|
|
}
|
|
|
|
// Check "starts from sequence of random bytes" pattern.
|
|
memset(buf, 0, sizeof(buf));
|
|
for (size_t k = 0; k < n; ++k) {
|
|
buf[k] = static_cast<uint8>(rng.Get());
|
|
Crc crc_old = this->CrcByte(buf, n, 0);
|
|
Crc crc_new = (this->*cb)(buf, n, 0);
|
|
CHECK_EQ(crc_old, crc_new);
|
|
}
|
|
}
|
|
|
|
// Check that alignment is handled correctly.
|
|
for (size_t n = 0; n < 256; ++n) {
|
|
buf[n] = static_cast<uint8>(rng.Get());
|
|
for (size_t k = 0; k < n; ++k) {
|
|
Crc crc_old = this->CrcByte(buf + k, n - k, 0);
|
|
Crc crc_new = (this->*cb)(buf + k, n - k, 0);
|
|
CHECK_EQ(crc_old, crc_new);
|
|
}
|
|
}
|
|
|
|
// Now, do randomized tests.
|
|
size_t buffer_size = 128 * 1024;
|
|
uint8 *buffer = new uint8[buffer_size];
|
|
|
|
for (size_t i = 0; i < buffer_size; ++i) {
|
|
buffer[i] = static_cast<uint8>(rng.Get());
|
|
}
|
|
|
|
for (size_t i = 0; i < 1 * 1000; ++i) {
|
|
size_t bytes = rng.Get() % (buffer_size - 1);
|
|
size_t offset = rng.Get() % (buffer_size - bytes);
|
|
Crc crc_old = this->CrcByte(buffer + offset, bytes, 0);
|
|
Crc crc_new = (this->*cb)(buffer + offset, bytes, 0);
|
|
CHECK_EQ(crc_old, crc_new);
|
|
}
|
|
|
|
delete[] buffer;
|
|
}
|
|
|
|
void VerifyLCD() const {
|
|
Crc A;
|
|
Crc B;
|
|
Crc LCD;
|
|
for (A = this->Base().One(); A != 0; A >>= 1) {
|
|
LCD = this->Base().FindLCD(A, &B);
|
|
CHECK_EQ(this->Base().One(), LCD);
|
|
Crc product = this->Base().Multiply(A, B);
|
|
CHECK_EQ(this->Base().One(), product);
|
|
}
|
|
|
|
int rept = 253;
|
|
uint64 step = static_cast<uint64>(1);
|
|
size_t degree = this->Base().Degree();
|
|
if (degree > 64) degree = 62;
|
|
step = (step << degree) / rept;
|
|
uint64 value = step;
|
|
for (; --rept; value += step) {
|
|
A = static_cast<Crc>(value);
|
|
LCD = this->Base().FindLCD(A, &B);
|
|
if (LCD == this->Base().One()) {
|
|
Crc product = this->Base().Multiply(A, B);
|
|
CHECK_EQ(this->Base().One(), product);
|
|
}
|
|
|
|
uint8 buf[64];
|
|
size_t bytes = this->Base().StoreCrc(buf, A);
|
|
Crc result = this->CrcByte(buf, bytes, A);
|
|
CHECK_EQ(this->Base().CrcOfCrc(), result);
|
|
|
|
bytes = this->Base().StoreComplementaryCrc(buf, A, 0);
|
|
result = this->CrcByte(buf, bytes, A);
|
|
CHECK_EQ(result, 0);
|
|
}
|
|
}
|
|
|
|
void VerifyCrcOfCrc(void) const {
|
|
Crc A;
|
|
int rept = 253;
|
|
uint64 step = static_cast<uint64>(1);
|
|
size_t degree = this->Base().Degree();
|
|
if (degree > 64) degree = 62;
|
|
step = (step << degree) / rept;
|
|
Crc expected = this->Base().CrcOfCrc();
|
|
uint64 value = step;
|
|
for (; --rept; value += step) {
|
|
A = static_cast<Crc>(value);
|
|
Crc value = this->CrcByte(&A, (this->Base().Degree() + 7) >> 3, A);
|
|
CHECK_EQ(expected, value);
|
|
}
|
|
}
|
|
|
|
void VerifyDistribution() const {
|
|
Crc coef = this->Base().One();
|
|
for (int index = 0; coef != 0; ++index, coef >>= 1) {
|
|
int ones = 0;
|
|
for (int i = 0; i < 256; ++i) {
|
|
if ((this->crc_word_[sizeof(Word) - 1][i] & coef) == 0) {
|
|
ones += 1;
|
|
}
|
|
}
|
|
CHECK_EQ(128, ones);
|
|
}
|
|
}
|
|
|
|
void VerifyRollingCrc() const {
|
|
void *aligned_memory = AlignedAlloc(sizeof(RollingCrcSelf), 0, 256, NULL);
|
|
RollingCrcSelf *rolling_crc = new(aligned_memory) RollingCrcSelf;
|
|
|
|
static const size_t kMaxRollBytes = 8;
|
|
static const size_t kBufferSize = 256 + kMaxRollBytes;
|
|
uint8 buffer[kBufferSize];
|
|
|
|
for (size_t starting_value = 0;
|
|
starting_value < 256 &&
|
|
(starting_value >> this->Base().Degree()) == 0;
|
|
++starting_value) {
|
|
for (size_t roll_bytes = 1;
|
|
roll_bytes < kMaxRollBytes;
|
|
++roll_bytes) {
|
|
rolling_crc->Init(*this, roll_bytes, static_cast<Crc>(starting_value));
|
|
|
|
memset(buffer, 0, sizeof(buffer));
|
|
Crc old_crc = rolling_crc->Start(buffer);
|
|
for (size_t offset = roll_bytes; offset < kBufferSize; ++offset) {
|
|
Crc new_crc = rolling_crc->Roll(old_crc,
|
|
buffer[offset - roll_bytes],
|
|
buffer[offset]);
|
|
Crc vfy_crc = this->CrcByte(buffer + offset - roll_bytes + 1,
|
|
roll_bytes,
|
|
static_cast<Crc>(starting_value));
|
|
CHECK_EQ(new_crc, vfy_crc);
|
|
}
|
|
for (size_t offset = roll_bytes; offset < kBufferSize; ++offset) {
|
|
buffer[offset] = static_cast<uint8>(offset);
|
|
Crc new_crc = rolling_crc->Roll(old_crc,
|
|
buffer[offset - roll_bytes],
|
|
buffer[offset]);
|
|
Crc vfy_crc = this->CrcByte(buffer + offset - roll_bytes + 1,
|
|
roll_bytes,
|
|
static_cast<Crc>(starting_value));
|
|
CHECK_EQ(new_crc, vfy_crc);
|
|
old_crc = new_crc;
|
|
}
|
|
}
|
|
}
|
|
|
|
AlignedFree(aligned_memory);
|
|
}
|
|
|
|
virtual void TestFunctionality(const char *class_title) const {
|
|
fprintf(stderr,
|
|
"Functional test of %s (size=%u bytes",
|
|
class_title,
|
|
static_cast<int>(
|
|
sizeof(GenericCrc<Crc, TableEntry, Word, kStride>)));
|
|
if (this->HaveCrc32c()) {
|
|
fprintf(stderr, " [generic], %u bytes [CRC32C]",
|
|
static_cast<int>(sizeof(Crc32cSSE4)));
|
|
}
|
|
fprintf(stderr, ")\n");
|
|
fflush(stderr);
|
|
|
|
VerifyPow();
|
|
|
|
VerifyCb(&CrcTestSelf::CrcByteUnrolled, "ByteUnrolled");
|
|
VerifyCb(&CrcTestSelf::CrcByteWord, "ByteWord");
|
|
VerifyCb(&CrcTestSelf::CrcWord, "Word");
|
|
VerifyCb(&CrcTestSelf::CrcBlockword, "Blockword");
|
|
VerifyCb(&CrcTestSelf::CrcMultiword, "Multiword");
|
|
if (this->HaveCrc32c()) {
|
|
VerifyCb(&CrcTestSelf::CRC32C, "CRC32C");
|
|
}
|
|
|
|
VerifyLCD();
|
|
VerifyCrcZeroes();
|
|
VerifyChangeStartValue();
|
|
VerifyConcatenate();
|
|
VerifyCrcOfCrc();
|
|
VerifyDistribution();
|
|
|
|
VerifyRollingCrc();
|
|
|
|
fprintf(stderr, "\n");
|
|
fflush(stderr);
|
|
}
|
|
|
|
// MemSpeed sets the lowest threshold: it is necessary to touch
|
|
// CRC table at least once per input byte -- and it is necessary
|
|
// to touch all input bytes.
|
|
Crc MemSpeed(const void *data, size_t bytes, const Crc &start) const {
|
|
const uint8 *src = static_cast<const uint8 *>(data);
|
|
const uint8 *end = src + bytes;
|
|
Crc crc0 = start ^ this->Base().Canonize();
|
|
size_t buf0 = 0;
|
|
|
|
end -= 4 * sizeof(Word) - 1;
|
|
if (src < end) {
|
|
Crc crc1 = 0;
|
|
Crc crc2 = 0;
|
|
Crc crc3 = 0;
|
|
do {
|
|
for (size_t byte = 0; byte < sizeof(Word); ++byte) {
|
|
crc0 ^= this->crc_word_interleaved_[0][0 * 64];
|
|
crc1 ^= this->crc_word_interleaved_[1][1 * 64];
|
|
crc2 ^= this->crc_word_interleaved_[2][2 * 64];
|
|
crc3 ^= this->crc_word_interleaved_[3][3 * 64];
|
|
buf0 ^= src[byte * sizeof(Word)];
|
|
}
|
|
src += 4 * sizeof(Word);
|
|
} while (src < end);
|
|
crc0 ^= crc1 ^ crc2 ^ crc3;
|
|
}
|
|
end += 4 * sizeof(Word) - 1;
|
|
|
|
end -= sizeof(Word) - 1;
|
|
while (src < end) {
|
|
buf0 ^= *src;
|
|
src += sizeof(Word);
|
|
for (size_t byte = 0; byte < sizeof(Word); ++byte) {
|
|
crc0 ^= this->crc_word_interleaved_[0][0];
|
|
}
|
|
}
|
|
end += sizeof(Word) - 1;
|
|
|
|
while (src < end) {
|
|
buf0 ^= *src;
|
|
src += 1;
|
|
crc0 ^= this->crc_word_interleaved_[0][0];
|
|
}
|
|
|
|
return (crc0 ^ static_cast<Crc>(buf0));
|
|
}
|
|
|
|
void PerfTestCall(const uint8 *buf, size_t bytes, size_t rept,
|
|
CrcCallback cb, const Crc &expected_crc) const {
|
|
for (size_t r = 0; r < rept; ++r) {
|
|
Crc crc = (this->*cb)(buf, bytes, 0);
|
|
if (cb != &CrcTestSelf::MemSpeed) {
|
|
CHECK_EQ(expected_crc, crc);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint64 PerfTestMeasure(const uint8 *buf, size_t bytes, size_t rept,
|
|
size_t tries, CrcCallback cb, const Crc &expected_crc) const {
|
|
uint64 min_time = ~0ULL;
|
|
for (size_t i = 0; i < tries; ++i) {
|
|
uint64 t = Rdtsc::Get();
|
|
PerfTestCall(buf, bytes, rept, cb, expected_crc);
|
|
t = Rdtsc::Get() - t;
|
|
if (min_time > t) {
|
|
min_time = t;
|
|
}
|
|
}
|
|
return min_time;
|
|
}
|
|
|
|
static const char *CompilerName() {
|
|
#define EXPANDED_STRINGIZE(c) #c
|
|
#define STRINGIZE(c) EXPANDED_STRINGIZE(c)
|
|
#if defined(__GNUC__)
|
|
return "GCC-" STRINGIZE(__GNUC__) "." STRINGIZE(__GNUC_MINOR__) "."
|
|
STRINGIZE(__GNUC_PATCHLEVEL__);
|
|
#elif defined(__INTEL_COMPILER)
|
|
return "ICL-" STRINGIZE(__INTEL_COMPILER);
|
|
#elif defined(_MSC_FULL_VER)
|
|
return "CL-" STRINGIZE(_MSC_FULL_VER);
|
|
#else
|
|
return "Unknown";
|
|
#endif
|
|
#undef EXPANDED_STRINGIZE
|
|
#undef STRINGIZE
|
|
}
|
|
|
|
void PerfTest(PerfTestState *state,
|
|
const uint8 *buf, uint32 bytes,
|
|
CrcCallback cb, const char *title) const {
|
|
Crc expected_crc = this->CrcByte(buf, bytes, 0);
|
|
size_t tries = (64 * 1024 * 1024 + bytes - 1) / bytes;
|
|
if (tries < 20) {
|
|
tries = 20;
|
|
} else if (tries > 1000) {
|
|
tries = 1000;
|
|
}
|
|
size_t rept = (128 * 1024 * 1024 + tries*bytes - 1) / (tries*bytes);
|
|
if (rept > 10000) {
|
|
rept = 10000;
|
|
}
|
|
uint64 min_time = PerfTestMeasure(buf, bytes, rept, tries, cb,
|
|
expected_crc);
|
|
state->Add(bytes, title, min_time / (static_cast<double>(rept) * bytes));
|
|
fflush(stderr);
|
|
}
|
|
|
|
uint64 PerfTestMeasureInit() {
|
|
uint64 min_time = 0;
|
|
min_time = ~min_time;
|
|
for (size_t i = 0; i < 5; ++i) {
|
|
uint64 t = Rdtsc::Get();
|
|
this->InitWithCrc32c(this->Base().GeneratingPolynomial(),
|
|
this->Base().Degree(),
|
|
this->Base().Canonize() != 0);
|
|
t = Rdtsc::Get() - t;
|
|
if (min_time > t) {
|
|
min_time = t;
|
|
}
|
|
}
|
|
return min_time;
|
|
}
|
|
|
|
void PerfTestVariants(PerfTestState *state,
|
|
bool test_multiword_only,
|
|
const uint8 *buf, uint32 buf_bytes,
|
|
uint32 bytes) const {
|
|
if (bytes > buf_bytes) {
|
|
return;
|
|
}
|
|
if (!test_multiword_only) {
|
|
PerfTest(state, buf, bytes,
|
|
&CrcTestSelf::MemSpeed, "MemSpeed");
|
|
PerfTest(state, buf, bytes,
|
|
&CrcTestSelf::CrcByte, "Byte");
|
|
PerfTest(state, buf, bytes,
|
|
&CrcTestSelf::CrcByteUnrolled, "ByteUnrolled");
|
|
PerfTest(state, buf, bytes,
|
|
&CrcTestSelf::CrcByteWord, "ByteWord");
|
|
PerfTest(state, buf, bytes,
|
|
&CrcTestSelf::CrcWord, "Word");
|
|
}
|
|
PerfTest(state, buf, bytes,
|
|
&CrcTestSelf::CrcBlockword, "Blockword");
|
|
PerfTest(state, buf, bytes,
|
|
&CrcTestSelf::CrcMultiword, "Multiword");
|
|
if (this->HaveCrc32c()) {
|
|
PerfTest(state, buf, bytes,
|
|
&CrcTestSelf::CRC32C, "CRC32C");
|
|
}
|
|
state->Print();
|
|
}
|
|
|
|
void PerfTestRun(FILE *output,
|
|
PerfTestState *state,
|
|
bool multiword_only,
|
|
uint8 *buf,
|
|
int align,
|
|
uint32 buf_bytes) {
|
|
BobJenkinsRng<size_t> rng;
|
|
for (size_t i = 0; i < buf_bytes; ++i) {
|
|
buf[i] = static_cast<uint8>(rng.Get());
|
|
}
|
|
|
|
fprintf(output, "ClassTitle, %s\n", class_title_);
|
|
fprintf(output,
|
|
"%12s, %7s, %6s, %3s, %11s, %4s, %6s, %9s, %10s, %10s\n",
|
|
"Compiler",
|
|
"CpuBits",
|
|
"Degree",
|
|
"Crc",
|
|
"TableEntry",
|
|
"Word",
|
|
"Stride",
|
|
"Alignment",
|
|
"TableBytes",
|
|
"InitCycles");
|
|
fprintf(output,
|
|
"%12s, %7d, %6d, %3d, %11d, %4d, %6d, %9d, %10d, %10d\n",
|
|
CompilerName(),
|
|
static_cast<int>(sizeof(size_t) * 8),
|
|
static_cast<int>(this->Base().Degree()),
|
|
static_cast<int>(sizeof(Crc)),
|
|
static_cast<int>(sizeof(TableEntry)),
|
|
static_cast<int>(sizeof(Word)),
|
|
static_cast<int>(kStride),
|
|
static_cast<int>(align),
|
|
static_cast<int>(sizeof(CrcTestSelf)),
|
|
static_cast<int>(PerfTestMeasureInit()));
|
|
fprintf(output, "\n");
|
|
fflush(output);
|
|
|
|
state->StartNewCrc(class_title_);
|
|
|
|
for (uint32 i = 4; i <= 64 * 1024 * 1024; i *= 2) {
|
|
PerfTestVariants(state, multiword_only, buf, buf_bytes, i);
|
|
}
|
|
state->Finish();
|
|
|
|
#if defined(FULL_PERF_TEST)
|
|
// Needed for performance work only.
|
|
for (uint32 i = 1; i <= 128; ++i) {
|
|
PerfTestVariants(state, multiword_only, buf, buf_bytes, i);
|
|
}
|
|
state->Finish();
|
|
#endif // defined(FULL_PERF_TEST)
|
|
}
|
|
|
|
void TestPerformance(FILE *output,
|
|
PerfTestState *state,
|
|
bool multiword_only,
|
|
const char *class_title) {
|
|
class_title_ = class_title;
|
|
uint32 align = sizeof(Word);
|
|
uint32 buf_bytes = 64*1024*1024;
|
|
uint8 *buf0 = new uint8[buf_bytes + align*2];
|
|
uint8 *buf = buf0;
|
|
|
|
if ((reinterpret_cast<size_t>(buf) & (align - 1)) != 0) {
|
|
buf += align - (reinterpret_cast<size_t>(buf) & (align - 1));
|
|
}
|
|
|
|
PerfTestRun(output, state, multiword_only, buf, 0, buf_bytes);
|
|
|
|
#if defined(FULL_PERF_TEST)
|
|
// Needed for performance work only.
|
|
PerfTestRun(output,
|
|
state,
|
|
multiword_only,
|
|
buf + sizeof(Word)/2,
|
|
sizeof(Word)/2,
|
|
buf_bytes);
|
|
#endif // defined(FULL_PERF_TEST)
|
|
|
|
delete[] buf;
|
|
}
|
|
|
|
private:
|
|
const char *class_title_;
|
|
};
|
|
|
|
class CrcVerifierFactoryInterface {
|
|
public:
|
|
virtual CrcVerifierInterface *Create(void *memory) const = 0;
|
|
virtual size_t TellMemoryBytesNeeded() const = 0;
|
|
virtual const char *class_title() const = 0;
|
|
virtual bool test_performance() const = 0;
|
|
virtual bool multiword_only() const = 0;
|
|
CrcVerifierFactoryInterface() {}
|
|
virtual ~CrcVerifierFactoryInterface() {}
|
|
};
|
|
|
|
// Verifies functionality of all registered CRCs first
|
|
// and only then runs time-consuming performance tests.
|
|
// That is helpful during development: we do not want to wait for
|
|
// all performance tests to go through but still want to do full
|
|
// functionality test.
|
|
class CrcVerifier {
|
|
public:
|
|
CrcVerifier() : memory_needed_(0), factory_count_(0) {}
|
|
|
|
void add(const CrcVerifierFactoryInterface *factory) {
|
|
if (factory_count_ < MAX_FACTORY_COUNT) {
|
|
factory_[factory_count_] = factory;
|
|
factory_count_ += 1;
|
|
size_t memory_needed = factory->TellMemoryBytesNeeded();
|
|
if (memory_needed_ < memory_needed) {
|
|
memory_needed_ = memory_needed;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestFunctionality() {
|
|
fprintf(stderr, "Verifying functionality\n");
|
|
fflush(stderr);
|
|
|
|
memory_allocate();
|
|
for (size_t i = 0; i < factory_count_; ++i) {
|
|
const CrcVerifierFactoryInterface *factory = factory_[i];
|
|
const CrcVerifierInterface *instance = factory->Create(aligned_memory_);
|
|
instance->TestFunctionality(factory->class_title());
|
|
}
|
|
memory_release();
|
|
}
|
|
|
|
void TestPerformance() {
|
|
fprintf(stderr, "Verifying performance\n");
|
|
fflush(stderr);
|
|
|
|
FILE *output = stdout;
|
|
PerfTestState state(output);
|
|
|
|
memory_allocate();
|
|
for (size_t i = 0; i < factory_count_; ++i) {
|
|
const CrcVerifierFactoryInterface *factory = factory_[i];
|
|
if (!factory->test_performance()) {
|
|
continue;
|
|
}
|
|
CrcVerifierInterface *instance = factory->Create(aligned_memory_);
|
|
instance->TestPerformance(output,
|
|
&state,
|
|
factory->multiword_only(),
|
|
factory->class_title());
|
|
}
|
|
|
|
state.PrintBest();
|
|
|
|
memory_release();
|
|
}
|
|
|
|
~CrcVerifier() {
|
|
for (size_t i = 0; i < factory_count_; ++i) {
|
|
delete factory_[i];
|
|
}
|
|
}
|
|
|
|
|
|
private:
|
|
void memory_allocate() {
|
|
aligned_memory_ = AlignedAlloc(memory_needed_, 0, 256, NULL);
|
|
}
|
|
|
|
void memory_release() {
|
|
AlignedFree(aligned_memory_);
|
|
}
|
|
|
|
enum {
|
|
MAX_FACTORY_COUNT = 100,
|
|
};
|
|
void *aligned_memory_;
|
|
size_t memory_needed_;
|
|
size_t factory_count_;
|
|
const CrcVerifierFactoryInterface *factory_[MAX_FACTORY_COUNT];
|
|
};
|
|
|
|
// Provides a factory creating ins
|
|
//
|
|
template<typename Crc, typename TableEntry, typename Word, size_t kStride>
|
|
class CrcVerifierFactory : public CrcVerifierFactoryInterface {
|
|
public:
|
|
typedef CrcTest<Crc, TableEntry, Word, kStride> CrcTestSelf;
|
|
typedef CrcVerifierFactory<Crc, TableEntry, Word, kStride>
|
|
CrcVerifierFactorySelf;
|
|
|
|
CrcVerifierFactory(bool constant, size_t degree,
|
|
uint64 poly_hi, uint64 poly_lo,
|
|
const char *class_title,
|
|
bool test_performance,
|
|
bool multiword_only)
|
|
: degree_(degree),
|
|
constant_(constant),
|
|
test_performance_(test_performance),
|
|
multiword_only_(multiword_only) {
|
|
strcpy(class_title_, class_title);
|
|
|
|
// On 32-bit platforms, "this" may be misaligned with respect
|
|
// to sizeof(Crc) -- even though alignment of the structure is
|
|
// correct (e.g. 16 when Crc=uint128_sse2), "new" returns memory
|
|
// that is aligned on 8 boundary only. Respectively, attempt
|
|
// to access a field of Crc type when Crc=uint128_sse2 fails.
|
|
//
|
|
// Use local variable and memcpy to access fields of Crc type.
|
|
//
|
|
Crc generating_polynomial = CrcFromUint64<Crc>(poly_lo, poly_hi);
|
|
memcpy(&generating_polynomial_, &generating_polynomial, sizeof(Crc));
|
|
}
|
|
|
|
virtual CrcVerifierInterface *Create(void *memory) const {
|
|
CrcTestSelf *crc = new(memory) CrcTestSelf;
|
|
// "this" may be misaligned on 32-bit platforms.
|
|
// Use local variable and memcpy to access fields of Crc type.
|
|
Crc generating_polynomial;
|
|
memcpy(&generating_polynomial, &generating_polynomial_, sizeof(Crc));
|
|
crc->InitWithCrc32c(generating_polynomial, degree_, constant_);
|
|
return crc;
|
|
}
|
|
|
|
virtual size_t TellMemoryBytesNeeded() const {
|
|
return sizeof(CrcTestSelf);
|
|
}
|
|
|
|
const char *class_title() const { return class_title_; }
|
|
bool test_performance() const { return test_performance_; }
|
|
bool multiword_only() const { return multiword_only_; }
|
|
|
|
private:
|
|
CrcVerifierFactory() {}
|
|
const CrcVerifierFactory &operator=(const CrcVerifierFactory &src);
|
|
|
|
Crc generating_polynomial_;
|
|
char class_title_[128];
|
|
size_t const degree_;
|
|
bool const constant_;
|
|
bool const test_performance_;
|
|
bool const multiword_only_;
|
|
};
|
|
|
|
// Adds 2 tests to the verifier: one full test with 4 stripes,
|
|
// and one reduced test (only Blockword and Multiword) for 3 stripes.
|
|
template<typename Crc, typename TableEntry, typename Word>
|
|
void CreateTest(size_t degree,
|
|
uint64 poly_hi,
|
|
uint64 poly_lo,
|
|
const char *title,
|
|
bool test_performance,
|
|
CrcVerifier *v) {
|
|
char name[128];
|
|
bool canonical = true;
|
|
for (;;) {
|
|
sprintf(name, "CRC-%d-%s-4-%s", static_cast<int>(degree),
|
|
title, (canonical ? "canonical" : "raw"));
|
|
v->add(new CrcVerifierFactory<Crc, TableEntry, Word, 4>(canonical,
|
|
degree,
|
|
poly_hi,
|
|
poly_lo,
|
|
name,
|
|
test_performance,
|
|
false));
|
|
|
|
if (degree == 128 && HAVE_AMD64) {
|
|
sprintf(name, "CRC-%d-%s-6-%s", static_cast<int>(degree),
|
|
title, (canonical ? "canonical" : "raw"));
|
|
v->add(new CrcVerifierFactory<Crc, TableEntry, Word, 6>(canonical,
|
|
degree,
|
|
poly_hi,
|
|
poly_lo,
|
|
name,
|
|
test_performance,
|
|
true));
|
|
}
|
|
|
|
sprintf(name, "CRC-%d-%s-3-%s", static_cast<int>(degree),
|
|
title, (canonical ? "canonical" : "raw"));
|
|
v->add(new CrcVerifierFactory<Crc, TableEntry, Word, 3>(canonical,
|
|
degree,
|
|
poly_hi,
|
|
poly_lo,
|
|
name,
|
|
test_performance,
|
|
true));
|
|
if (!canonical) {
|
|
break;
|
|
}
|
|
canonical = false;
|
|
test_performance = false;
|
|
}
|
|
}
|
|
|
|
#if defined(_INTEL_COMPILER)
|
|
#pragma warning(pop)
|
|
#endif // defined(_INTEL_COMPILER)
|
|
|
|
} // namespace crcutil
|
|
|
|
#endif // CRCUTIL_UNITTEST_H_
|