| // 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 |