// Copyright 2007 Google Inc. // Authors: Jeff Dean, Lincoln Smith // // 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. #include <config.h> #include "rolling_hash.h" #include <stdint.h> // uint32_t #include <stdlib.h> // rand, srand #include <vector> #include "testing.h" namespace open_vcdiff { namespace { static const uint32_t kBase = RollingHashUtil::kBase; class RollingHashSimpleTest : public testing::Test { protected: RollingHashSimpleTest() { } virtual ~RollingHashSimpleTest() { } void TestModBase(uint32_t operand) { EXPECT_EQ(operand % kBase, RollingHashUtil::ModBase(operand)); EXPECT_EQ(static_cast<uint32_t>((-static_cast<int32_t>(operand)) % kBase), RollingHashUtil::FindModBaseInverse(operand)); EXPECT_EQ(0U, RollingHashUtil::ModBase( operand + RollingHashUtil::FindModBaseInverse(operand))); } void TestHashFirstTwoBytes(char first_value, char second_value) { char buf[2]; buf[0] = first_value; buf[1] = second_value; EXPECT_EQ(RollingHashUtil::HashFirstTwoBytes(buf), RollingHashUtil::HashStep(RollingHashUtil::HashStep(0, first_value), second_value)); EXPECT_EQ(RollingHashUtil::HashFirstTwoBytes(buf), RollingHashUtil::HashStep(static_cast<unsigned char>(first_value), second_value)); } }; #ifdef GTEST_HAS_DEATH_TEST typedef RollingHashSimpleTest RollingHashDeathTest; #endif // GTEST_HAS_DEATH_TEST TEST_F(RollingHashSimpleTest, KBaseIsAPowerOfTwo) { EXPECT_EQ(0U, kBase & (kBase - 1)); } TEST_F(RollingHashSimpleTest, TestModBaseForValues) { TestModBase(0); TestModBase(10); TestModBase(static_cast<uint32_t>(-10)); TestModBase(kBase - 1); TestModBase(kBase); TestModBase(kBase + 1); TestModBase(0x7FFFFFFF); TestModBase(0x80000000); TestModBase(0xFFFFFFFE); TestModBase(0xFFFFFFFF); } TEST_F(RollingHashSimpleTest, VerifyHashFirstTwoBytes) { TestHashFirstTwoBytes(0x00, 0x00); TestHashFirstTwoBytes(0x00, 0xFF); TestHashFirstTwoBytes(0xFF, 0x00); TestHashFirstTwoBytes(0xFF, 0xFF); TestHashFirstTwoBytes(0x00, 0x80); TestHashFirstTwoBytes(0x7F, 0xFF); TestHashFirstTwoBytes(0x7F, 0x80); TestHashFirstTwoBytes(0x01, 0x8F); } #ifdef GTEST_HAS_DEATH_TEST TEST_F(RollingHashDeathTest, InstantiateRollingHashWithoutCallingInit) { EXPECT_DEBUG_DEATH(RollingHash<16> bad_hash, "Init"); } #endif // GTEST_HAS_DEATH_TEST class RollingHashTest : public testing::Test { public: static const int kUpdateHashBlocks = 1000; static const int kLargestBlockSize = 128; static void MakeRandomBuffer(char* buffer, int buffer_size) { for (int i = 0; i < buffer_size; ++i) { buffer[i] = PortableRandomInRange<unsigned char>(0xFF); } } template<int kBlockSize> static void BM_DefaultHash(int iterations, const char *buffer) { RollingHash<kBlockSize> hasher; static uint32_t result_array[kUpdateHashBlocks]; for (int iter = 0; iter < iterations; ++iter) { for (int i = 0; i < kUpdateHashBlocks; ++i) { result_array[i] = hasher.Hash(&buffer[i]); } } } template<int kBlockSize> static void BM_UpdateHash(int iterations, const char *buffer) { RollingHash<kBlockSize> hasher; static uint32_t result_array[kUpdateHashBlocks]; for (int iter = 0; iter < iterations; ++iter) { uint32_t running_hash = hasher.Hash(buffer); for (int i = 0; i < kUpdateHashBlocks; ++i) { running_hash = hasher.UpdateHash(running_hash, buffer[i], buffer[i + kBlockSize]); result_array[i] = running_hash; } } } protected: static const int kUpdateHashTestIterations = 400; static const int kTimingTestSize = 1 << 14; // 16K iterations RollingHashTest() { } virtual ~RollingHashTest() { } template<int kBlockSize> void UpdateHashMatchesHashForBlockSize() { RollingHash<kBlockSize>::Init(); RollingHash<kBlockSize> hasher; for (int x = 0; x < kUpdateHashTestIterations; ++x) { int random_buffer_size = PortableRandomInRange(kUpdateHashBlocks - 1) + kBlockSize; MakeRandomBuffer(buffer_, random_buffer_size); uint32_t running_hash = hasher.Hash(buffer_); for (int i = kBlockSize; i < random_buffer_size; ++i) { // UpdateHash() calculates the hash value incrementally. running_hash = hasher.UpdateHash(running_hash, buffer_[i - kBlockSize], buffer_[i]); // Hash() calculates the hash value from scratch. Verify that both // methods return the same hash value. EXPECT_EQ(running_hash, hasher.Hash(&buffer_[i + 1 - kBlockSize])); } } } template<int kBlockSize> double DefaultHashTimingTest() { // Execution time is expected to be O(kBlockSize) per hash operation, // so scale the number of iterations accordingly const int kTimingTestIterations = kTimingTestSize / kBlockSize; CycleTimer timer; timer.Start(); BM_DefaultHash<kBlockSize>(kTimingTestIterations, buffer_); timer.Stop(); return static_cast<double>(timer.GetInUsec()) / (kTimingTestIterations * kUpdateHashBlocks); } template<int kBlockSize> double RollingTimingTest() { // Execution time is expected to be O(1) per hash operation, // so leave the number of iterations constant const int kTimingTestIterations = kTimingTestSize; CycleTimer timer; timer.Start(); BM_UpdateHash<kBlockSize>(kTimingTestIterations, buffer_); timer.Stop(); return static_cast<double>(timer.GetInUsec()) / (kTimingTestIterations * kUpdateHashBlocks); } double FindPercentage(double original, double modified) { if (original < 0.0001) { return 0.0; } else { return ((modified - original) / original) * 100.0; } } template<int kBlockSize> void RunTimingTestForBlockSize() { RollingHash<kBlockSize>::Init(); MakeRandomBuffer(buffer_, sizeof(buffer_)); const double time_for_default_hash = DefaultHashTimingTest<kBlockSize>(); const double time_for_rolling_hash = RollingTimingTest<kBlockSize>(); printf("%d\t%f\t%f (%f%%)\n", kBlockSize, time_for_default_hash, time_for_rolling_hash, FindPercentage(time_for_default_hash, time_for_rolling_hash)); CHECK_GT(time_for_default_hash, 0.0); CHECK_GT(time_for_rolling_hash, 0.0); if (kBlockSize > 16) { EXPECT_GT(time_for_default_hash, time_for_rolling_hash); } } char buffer_[kUpdateHashBlocks + kLargestBlockSize]; }; TEST_F(RollingHashTest, UpdateHashMatchesHashFromScratch) { srand(1); // test should be deterministic, including calls to rand() UpdateHashMatchesHashForBlockSize<4>(); UpdateHashMatchesHashForBlockSize<8>(); UpdateHashMatchesHashForBlockSize<16>(); UpdateHashMatchesHashForBlockSize<32>(); UpdateHashMatchesHashForBlockSize<64>(); UpdateHashMatchesHashForBlockSize<128>(); } TEST_F(RollingHashTest, TimingTests) { srand(1); // test should be deterministic, including calls to rand() printf("BlkSize\tHash (us)\tUpdateHash (us)\n"); RunTimingTestForBlockSize<4>(); RunTimingTestForBlockSize<8>(); RunTimingTestForBlockSize<16>(); RunTimingTestForBlockSize<32>(); RunTimingTestForBlockSize<64>(); RunTimingTestForBlockSize<128>(); } } // anonymous namespace } // namespace open_vcdiff