Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | // Copyright 2005 and onwards Google Inc. |
michael@0 | 2 | // |
michael@0 | 3 | // Redistribution and use in source and binary forms, with or without |
michael@0 | 4 | // modification, are permitted provided that the following conditions are |
michael@0 | 5 | // met: |
michael@0 | 6 | // |
michael@0 | 7 | // * Redistributions of source code must retain the above copyright |
michael@0 | 8 | // notice, this list of conditions and the following disclaimer. |
michael@0 | 9 | // * Redistributions in binary form must reproduce the above |
michael@0 | 10 | // copyright notice, this list of conditions and the following disclaimer |
michael@0 | 11 | // in the documentation and/or other materials provided with the |
michael@0 | 12 | // distribution. |
michael@0 | 13 | // * Neither the name of Google Inc. nor the names of its |
michael@0 | 14 | // contributors may be used to endorse or promote products derived from |
michael@0 | 15 | // this software without specific prior written permission. |
michael@0 | 16 | // |
michael@0 | 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
michael@0 | 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
michael@0 | 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
michael@0 | 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
michael@0 | 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
michael@0 | 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
michael@0 | 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
michael@0 | 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
michael@0 | 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
michael@0 | 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
michael@0 | 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
michael@0 | 28 | |
michael@0 | 29 | #include <math.h> |
michael@0 | 30 | #include <stdlib.h> |
michael@0 | 31 | |
michael@0 | 32 | |
michael@0 | 33 | #include <algorithm> |
michael@0 | 34 | #include <string> |
michael@0 | 35 | #include <vector> |
michael@0 | 36 | |
michael@0 | 37 | #include "snappy.h" |
michael@0 | 38 | #include "snappy-internal.h" |
michael@0 | 39 | #include "snappy-test.h" |
michael@0 | 40 | #include "snappy-sinksource.h" |
michael@0 | 41 | |
michael@0 | 42 | DEFINE_int32(start_len, -1, |
michael@0 | 43 | "Starting prefix size for testing (-1: just full file contents)"); |
michael@0 | 44 | DEFINE_int32(end_len, -1, |
michael@0 | 45 | "Starting prefix size for testing (-1: just full file contents)"); |
michael@0 | 46 | DEFINE_int32(bytes, 10485760, |
michael@0 | 47 | "How many bytes to compress/uncompress per file for timing"); |
michael@0 | 48 | |
michael@0 | 49 | DEFINE_bool(zlib, false, |
michael@0 | 50 | "Run zlib compression (http://www.zlib.net)"); |
michael@0 | 51 | DEFINE_bool(lzo, false, |
michael@0 | 52 | "Run LZO compression (http://www.oberhumer.com/opensource/lzo/)"); |
michael@0 | 53 | DEFINE_bool(quicklz, false, |
michael@0 | 54 | "Run quickLZ compression (http://www.quicklz.com/)"); |
michael@0 | 55 | DEFINE_bool(liblzf, false, |
michael@0 | 56 | "Run libLZF compression " |
michael@0 | 57 | "(http://www.goof.com/pcg/marc/liblzf.html)"); |
michael@0 | 58 | DEFINE_bool(fastlz, false, |
michael@0 | 59 | "Run FastLZ compression (http://www.fastlz.org/"); |
michael@0 | 60 | DEFINE_bool(snappy, true, "Run snappy compression"); |
michael@0 | 61 | |
michael@0 | 62 | |
michael@0 | 63 | DEFINE_bool(write_compressed, false, |
michael@0 | 64 | "Write compressed versions of each file to <file>.comp"); |
michael@0 | 65 | DEFINE_bool(write_uncompressed, false, |
michael@0 | 66 | "Write uncompressed versions of each file to <file>.uncomp"); |
michael@0 | 67 | |
michael@0 | 68 | namespace snappy { |
michael@0 | 69 | |
michael@0 | 70 | |
michael@0 | 71 | #ifdef HAVE_FUNC_MMAP |
michael@0 | 72 | |
michael@0 | 73 | // To test against code that reads beyond its input, this class copies a |
michael@0 | 74 | // string to a newly allocated group of pages, the last of which |
michael@0 | 75 | // is made unreadable via mprotect. Note that we need to allocate the |
michael@0 | 76 | // memory with mmap(), as POSIX allows mprotect() only on memory allocated |
michael@0 | 77 | // with mmap(), and some malloc/posix_memalign implementations expect to |
michael@0 | 78 | // be able to read previously allocated memory while doing heap allocations. |
michael@0 | 79 | class DataEndingAtUnreadablePage { |
michael@0 | 80 | public: |
michael@0 | 81 | explicit DataEndingAtUnreadablePage(const string& s) { |
michael@0 | 82 | const size_t page_size = getpagesize(); |
michael@0 | 83 | const size_t size = s.size(); |
michael@0 | 84 | // Round up space for string to a multiple of page_size. |
michael@0 | 85 | size_t space_for_string = (size + page_size - 1) & ~(page_size - 1); |
michael@0 | 86 | alloc_size_ = space_for_string + page_size; |
michael@0 | 87 | mem_ = mmap(NULL, alloc_size_, |
michael@0 | 88 | PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); |
michael@0 | 89 | CHECK_NE(MAP_FAILED, mem_); |
michael@0 | 90 | protected_page_ = reinterpret_cast<char*>(mem_) + space_for_string; |
michael@0 | 91 | char* dst = protected_page_ - size; |
michael@0 | 92 | memcpy(dst, s.data(), size); |
michael@0 | 93 | data_ = dst; |
michael@0 | 94 | size_ = size; |
michael@0 | 95 | // Make guard page unreadable. |
michael@0 | 96 | CHECK_EQ(0, mprotect(protected_page_, page_size, PROT_NONE)); |
michael@0 | 97 | } |
michael@0 | 98 | |
michael@0 | 99 | ~DataEndingAtUnreadablePage() { |
michael@0 | 100 | // Undo the mprotect. |
michael@0 | 101 | CHECK_EQ(0, mprotect(protected_page_, getpagesize(), PROT_READ|PROT_WRITE)); |
michael@0 | 102 | CHECK_EQ(0, munmap(mem_, alloc_size_)); |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | const char* data() const { return data_; } |
michael@0 | 106 | size_t size() const { return size_; } |
michael@0 | 107 | |
michael@0 | 108 | private: |
michael@0 | 109 | size_t alloc_size_; |
michael@0 | 110 | void* mem_; |
michael@0 | 111 | char* protected_page_; |
michael@0 | 112 | const char* data_; |
michael@0 | 113 | size_t size_; |
michael@0 | 114 | }; |
michael@0 | 115 | |
michael@0 | 116 | #else // HAVE_FUNC_MMAP |
michael@0 | 117 | |
michael@0 | 118 | // Fallback for systems without mmap. |
michael@0 | 119 | typedef string DataEndingAtUnreadablePage; |
michael@0 | 120 | |
michael@0 | 121 | #endif |
michael@0 | 122 | |
michael@0 | 123 | enum CompressorType { |
michael@0 | 124 | ZLIB, LZO, LIBLZF, QUICKLZ, FASTLZ, SNAPPY |
michael@0 | 125 | }; |
michael@0 | 126 | |
michael@0 | 127 | const char* names[] = { |
michael@0 | 128 | "ZLIB", "LZO", "LIBLZF", "QUICKLZ", "FASTLZ", "SNAPPY" |
michael@0 | 129 | }; |
michael@0 | 130 | |
michael@0 | 131 | static size_t MinimumRequiredOutputSpace(size_t input_size, |
michael@0 | 132 | CompressorType comp) { |
michael@0 | 133 | switch (comp) { |
michael@0 | 134 | #ifdef ZLIB_VERSION |
michael@0 | 135 | case ZLIB: |
michael@0 | 136 | return ZLib::MinCompressbufSize(input_size); |
michael@0 | 137 | #endif // ZLIB_VERSION |
michael@0 | 138 | |
michael@0 | 139 | #ifdef LZO_VERSION |
michael@0 | 140 | case LZO: |
michael@0 | 141 | return input_size + input_size/64 + 16 + 3; |
michael@0 | 142 | #endif // LZO_VERSION |
michael@0 | 143 | |
michael@0 | 144 | #ifdef LZF_VERSION |
michael@0 | 145 | case LIBLZF: |
michael@0 | 146 | return input_size; |
michael@0 | 147 | #endif // LZF_VERSION |
michael@0 | 148 | |
michael@0 | 149 | #ifdef QLZ_VERSION_MAJOR |
michael@0 | 150 | case QUICKLZ: |
michael@0 | 151 | return input_size + 36000; // 36000 is used for scratch. |
michael@0 | 152 | #endif // QLZ_VERSION_MAJOR |
michael@0 | 153 | |
michael@0 | 154 | #ifdef FASTLZ_VERSION |
michael@0 | 155 | case FASTLZ: |
michael@0 | 156 | return max(static_cast<int>(ceil(input_size * 1.05)), 66); |
michael@0 | 157 | #endif // FASTLZ_VERSION |
michael@0 | 158 | |
michael@0 | 159 | case SNAPPY: |
michael@0 | 160 | return snappy::MaxCompressedLength(input_size); |
michael@0 | 161 | |
michael@0 | 162 | default: |
michael@0 | 163 | LOG(FATAL) << "Unknown compression type number " << comp; |
michael@0 | 164 | } |
michael@0 | 165 | } |
michael@0 | 166 | |
michael@0 | 167 | // Returns true if we successfully compressed, false otherwise. |
michael@0 | 168 | // |
michael@0 | 169 | // If compressed_is_preallocated is set, do not resize the compressed buffer. |
michael@0 | 170 | // This is typically what you want for a benchmark, in order to not spend |
michael@0 | 171 | // time in the memory allocator. If you do set this flag, however, |
michael@0 | 172 | // "compressed" must be preinitialized to at least MinCompressbufSize(comp) |
michael@0 | 173 | // number of bytes, and may contain junk bytes at the end after return. |
michael@0 | 174 | static bool Compress(const char* input, size_t input_size, CompressorType comp, |
michael@0 | 175 | string* compressed, bool compressed_is_preallocated) { |
michael@0 | 176 | if (!compressed_is_preallocated) { |
michael@0 | 177 | compressed->resize(MinimumRequiredOutputSpace(input_size, comp)); |
michael@0 | 178 | } |
michael@0 | 179 | |
michael@0 | 180 | switch (comp) { |
michael@0 | 181 | #ifdef ZLIB_VERSION |
michael@0 | 182 | case ZLIB: { |
michael@0 | 183 | ZLib zlib; |
michael@0 | 184 | uLongf destlen = compressed->size(); |
michael@0 | 185 | int ret = zlib.Compress( |
michael@0 | 186 | reinterpret_cast<Bytef*>(string_as_array(compressed)), |
michael@0 | 187 | &destlen, |
michael@0 | 188 | reinterpret_cast<const Bytef*>(input), |
michael@0 | 189 | input_size); |
michael@0 | 190 | CHECK_EQ(Z_OK, ret); |
michael@0 | 191 | if (!compressed_is_preallocated) { |
michael@0 | 192 | compressed->resize(destlen); |
michael@0 | 193 | } |
michael@0 | 194 | return true; |
michael@0 | 195 | } |
michael@0 | 196 | #endif // ZLIB_VERSION |
michael@0 | 197 | |
michael@0 | 198 | #ifdef LZO_VERSION |
michael@0 | 199 | case LZO: { |
michael@0 | 200 | unsigned char* mem = new unsigned char[LZO1X_1_15_MEM_COMPRESS]; |
michael@0 | 201 | lzo_uint destlen; |
michael@0 | 202 | int ret = lzo1x_1_15_compress( |
michael@0 | 203 | reinterpret_cast<const uint8*>(input), |
michael@0 | 204 | input_size, |
michael@0 | 205 | reinterpret_cast<uint8*>(string_as_array(compressed)), |
michael@0 | 206 | &destlen, |
michael@0 | 207 | mem); |
michael@0 | 208 | CHECK_EQ(LZO_E_OK, ret); |
michael@0 | 209 | delete[] mem; |
michael@0 | 210 | if (!compressed_is_preallocated) { |
michael@0 | 211 | compressed->resize(destlen); |
michael@0 | 212 | } |
michael@0 | 213 | break; |
michael@0 | 214 | } |
michael@0 | 215 | #endif // LZO_VERSION |
michael@0 | 216 | |
michael@0 | 217 | #ifdef LZF_VERSION |
michael@0 | 218 | case LIBLZF: { |
michael@0 | 219 | int destlen = lzf_compress(input, |
michael@0 | 220 | input_size, |
michael@0 | 221 | string_as_array(compressed), |
michael@0 | 222 | input_size); |
michael@0 | 223 | if (destlen == 0) { |
michael@0 | 224 | // lzf *can* cause lots of blowup when compressing, so they |
michael@0 | 225 | // recommend to limit outsize to insize, and just not compress |
michael@0 | 226 | // if it's bigger. Ideally, we'd just swap input and output. |
michael@0 | 227 | compressed->assign(input, input_size); |
michael@0 | 228 | destlen = input_size; |
michael@0 | 229 | } |
michael@0 | 230 | if (!compressed_is_preallocated) { |
michael@0 | 231 | compressed->resize(destlen); |
michael@0 | 232 | } |
michael@0 | 233 | break; |
michael@0 | 234 | } |
michael@0 | 235 | #endif // LZF_VERSION |
michael@0 | 236 | |
michael@0 | 237 | #ifdef QLZ_VERSION_MAJOR |
michael@0 | 238 | case QUICKLZ: { |
michael@0 | 239 | qlz_state_compress *state_compress = new qlz_state_compress; |
michael@0 | 240 | int destlen = qlz_compress(input, |
michael@0 | 241 | string_as_array(compressed), |
michael@0 | 242 | input_size, |
michael@0 | 243 | state_compress); |
michael@0 | 244 | delete state_compress; |
michael@0 | 245 | CHECK_NE(0, destlen); |
michael@0 | 246 | if (!compressed_is_preallocated) { |
michael@0 | 247 | compressed->resize(destlen); |
michael@0 | 248 | } |
michael@0 | 249 | break; |
michael@0 | 250 | } |
michael@0 | 251 | #endif // QLZ_VERSION_MAJOR |
michael@0 | 252 | |
michael@0 | 253 | #ifdef FASTLZ_VERSION |
michael@0 | 254 | case FASTLZ: { |
michael@0 | 255 | // Use level 1 compression since we mostly care about speed. |
michael@0 | 256 | int destlen = fastlz_compress_level( |
michael@0 | 257 | 1, |
michael@0 | 258 | input, |
michael@0 | 259 | input_size, |
michael@0 | 260 | string_as_array(compressed)); |
michael@0 | 261 | if (!compressed_is_preallocated) { |
michael@0 | 262 | compressed->resize(destlen); |
michael@0 | 263 | } |
michael@0 | 264 | CHECK_NE(destlen, 0); |
michael@0 | 265 | break; |
michael@0 | 266 | } |
michael@0 | 267 | #endif // FASTLZ_VERSION |
michael@0 | 268 | |
michael@0 | 269 | case SNAPPY: { |
michael@0 | 270 | size_t destlen; |
michael@0 | 271 | snappy::RawCompress(input, input_size, |
michael@0 | 272 | string_as_array(compressed), |
michael@0 | 273 | &destlen); |
michael@0 | 274 | CHECK_LE(destlen, snappy::MaxCompressedLength(input_size)); |
michael@0 | 275 | if (!compressed_is_preallocated) { |
michael@0 | 276 | compressed->resize(destlen); |
michael@0 | 277 | } |
michael@0 | 278 | break; |
michael@0 | 279 | } |
michael@0 | 280 | |
michael@0 | 281 | |
michael@0 | 282 | default: { |
michael@0 | 283 | return false; // the asked-for library wasn't compiled in |
michael@0 | 284 | } |
michael@0 | 285 | } |
michael@0 | 286 | return true; |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | static bool Uncompress(const string& compressed, CompressorType comp, |
michael@0 | 290 | int size, string* output) { |
michael@0 | 291 | switch (comp) { |
michael@0 | 292 | #ifdef ZLIB_VERSION |
michael@0 | 293 | case ZLIB: { |
michael@0 | 294 | output->resize(size); |
michael@0 | 295 | ZLib zlib; |
michael@0 | 296 | uLongf destlen = output->size(); |
michael@0 | 297 | int ret = zlib.Uncompress( |
michael@0 | 298 | reinterpret_cast<Bytef*>(string_as_array(output)), |
michael@0 | 299 | &destlen, |
michael@0 | 300 | reinterpret_cast<const Bytef*>(compressed.data()), |
michael@0 | 301 | compressed.size()); |
michael@0 | 302 | CHECK_EQ(Z_OK, ret); |
michael@0 | 303 | CHECK_EQ(static_cast<uLongf>(size), destlen); |
michael@0 | 304 | break; |
michael@0 | 305 | } |
michael@0 | 306 | #endif // ZLIB_VERSION |
michael@0 | 307 | |
michael@0 | 308 | #ifdef LZO_VERSION |
michael@0 | 309 | case LZO: { |
michael@0 | 310 | output->resize(size); |
michael@0 | 311 | lzo_uint destlen; |
michael@0 | 312 | int ret = lzo1x_decompress( |
michael@0 | 313 | reinterpret_cast<const uint8*>(compressed.data()), |
michael@0 | 314 | compressed.size(), |
michael@0 | 315 | reinterpret_cast<uint8*>(string_as_array(output)), |
michael@0 | 316 | &destlen, |
michael@0 | 317 | NULL); |
michael@0 | 318 | CHECK_EQ(LZO_E_OK, ret); |
michael@0 | 319 | CHECK_EQ(static_cast<lzo_uint>(size), destlen); |
michael@0 | 320 | break; |
michael@0 | 321 | } |
michael@0 | 322 | #endif // LZO_VERSION |
michael@0 | 323 | |
michael@0 | 324 | #ifdef LZF_VERSION |
michael@0 | 325 | case LIBLZF: { |
michael@0 | 326 | output->resize(size); |
michael@0 | 327 | int destlen = lzf_decompress(compressed.data(), |
michael@0 | 328 | compressed.size(), |
michael@0 | 329 | string_as_array(output), |
michael@0 | 330 | output->size()); |
michael@0 | 331 | if (destlen == 0) { |
michael@0 | 332 | // This error probably means we had decided not to compress, |
michael@0 | 333 | // and thus have stored input in output directly. |
michael@0 | 334 | output->assign(compressed.data(), compressed.size()); |
michael@0 | 335 | destlen = compressed.size(); |
michael@0 | 336 | } |
michael@0 | 337 | CHECK_EQ(destlen, size); |
michael@0 | 338 | break; |
michael@0 | 339 | } |
michael@0 | 340 | #endif // LZF_VERSION |
michael@0 | 341 | |
michael@0 | 342 | #ifdef QLZ_VERSION_MAJOR |
michael@0 | 343 | case QUICKLZ: { |
michael@0 | 344 | output->resize(size); |
michael@0 | 345 | qlz_state_decompress *state_decompress = new qlz_state_decompress; |
michael@0 | 346 | int destlen = qlz_decompress(compressed.data(), |
michael@0 | 347 | string_as_array(output), |
michael@0 | 348 | state_decompress); |
michael@0 | 349 | delete state_decompress; |
michael@0 | 350 | CHECK_EQ(destlen, size); |
michael@0 | 351 | break; |
michael@0 | 352 | } |
michael@0 | 353 | #endif // QLZ_VERSION_MAJOR |
michael@0 | 354 | |
michael@0 | 355 | #ifdef FASTLZ_VERSION |
michael@0 | 356 | case FASTLZ: { |
michael@0 | 357 | output->resize(size); |
michael@0 | 358 | int destlen = fastlz_decompress(compressed.data(), |
michael@0 | 359 | compressed.length(), |
michael@0 | 360 | string_as_array(output), |
michael@0 | 361 | size); |
michael@0 | 362 | CHECK_EQ(destlen, size); |
michael@0 | 363 | break; |
michael@0 | 364 | } |
michael@0 | 365 | #endif // FASTLZ_VERSION |
michael@0 | 366 | |
michael@0 | 367 | case SNAPPY: { |
michael@0 | 368 | snappy::RawUncompress(compressed.data(), compressed.size(), |
michael@0 | 369 | string_as_array(output)); |
michael@0 | 370 | break; |
michael@0 | 371 | } |
michael@0 | 372 | |
michael@0 | 373 | |
michael@0 | 374 | default: { |
michael@0 | 375 | return false; // the asked-for library wasn't compiled in |
michael@0 | 376 | } |
michael@0 | 377 | } |
michael@0 | 378 | return true; |
michael@0 | 379 | } |
michael@0 | 380 | |
michael@0 | 381 | static void Measure(const char* data, |
michael@0 | 382 | size_t length, |
michael@0 | 383 | CompressorType comp, |
michael@0 | 384 | int repeats, |
michael@0 | 385 | int block_size) { |
michael@0 | 386 | // Run tests a few time and pick median running times |
michael@0 | 387 | static const int kRuns = 5; |
michael@0 | 388 | double ctime[kRuns]; |
michael@0 | 389 | double utime[kRuns]; |
michael@0 | 390 | int compressed_size = 0; |
michael@0 | 391 | |
michael@0 | 392 | { |
michael@0 | 393 | // Chop the input into blocks |
michael@0 | 394 | int num_blocks = (length + block_size - 1) / block_size; |
michael@0 | 395 | vector<const char*> input(num_blocks); |
michael@0 | 396 | vector<size_t> input_length(num_blocks); |
michael@0 | 397 | vector<string> compressed(num_blocks); |
michael@0 | 398 | vector<string> output(num_blocks); |
michael@0 | 399 | for (int b = 0; b < num_blocks; b++) { |
michael@0 | 400 | int input_start = b * block_size; |
michael@0 | 401 | int input_limit = min<int>((b+1)*block_size, length); |
michael@0 | 402 | input[b] = data+input_start; |
michael@0 | 403 | input_length[b] = input_limit-input_start; |
michael@0 | 404 | |
michael@0 | 405 | // Pre-grow the output buffer so we don't measure string append time. |
michael@0 | 406 | compressed[b].resize(MinimumRequiredOutputSpace(block_size, comp)); |
michael@0 | 407 | } |
michael@0 | 408 | |
michael@0 | 409 | // First, try one trial compression to make sure the code is compiled in |
michael@0 | 410 | if (!Compress(input[0], input_length[0], comp, &compressed[0], true)) { |
michael@0 | 411 | LOG(WARNING) << "Skipping " << names[comp] << ": " |
michael@0 | 412 | << "library not compiled in"; |
michael@0 | 413 | return; |
michael@0 | 414 | } |
michael@0 | 415 | |
michael@0 | 416 | for (int run = 0; run < kRuns; run++) { |
michael@0 | 417 | CycleTimer ctimer, utimer; |
michael@0 | 418 | |
michael@0 | 419 | for (int b = 0; b < num_blocks; b++) { |
michael@0 | 420 | // Pre-grow the output buffer so we don't measure string append time. |
michael@0 | 421 | compressed[b].resize(MinimumRequiredOutputSpace(block_size, comp)); |
michael@0 | 422 | } |
michael@0 | 423 | |
michael@0 | 424 | ctimer.Start(); |
michael@0 | 425 | for (int b = 0; b < num_blocks; b++) |
michael@0 | 426 | for (int i = 0; i < repeats; i++) |
michael@0 | 427 | Compress(input[b], input_length[b], comp, &compressed[b], true); |
michael@0 | 428 | ctimer.Stop(); |
michael@0 | 429 | |
michael@0 | 430 | // Compress once more, with resizing, so we don't leave junk |
michael@0 | 431 | // at the end that will confuse the decompressor. |
michael@0 | 432 | for (int b = 0; b < num_blocks; b++) { |
michael@0 | 433 | Compress(input[b], input_length[b], comp, &compressed[b], false); |
michael@0 | 434 | } |
michael@0 | 435 | |
michael@0 | 436 | for (int b = 0; b < num_blocks; b++) { |
michael@0 | 437 | output[b].resize(input_length[b]); |
michael@0 | 438 | } |
michael@0 | 439 | |
michael@0 | 440 | utimer.Start(); |
michael@0 | 441 | for (int i = 0; i < repeats; i++) |
michael@0 | 442 | for (int b = 0; b < num_blocks; b++) |
michael@0 | 443 | Uncompress(compressed[b], comp, input_length[b], &output[b]); |
michael@0 | 444 | utimer.Stop(); |
michael@0 | 445 | |
michael@0 | 446 | ctime[run] = ctimer.Get(); |
michael@0 | 447 | utime[run] = utimer.Get(); |
michael@0 | 448 | } |
michael@0 | 449 | |
michael@0 | 450 | compressed_size = 0; |
michael@0 | 451 | for (int i = 0; i < compressed.size(); i++) { |
michael@0 | 452 | compressed_size += compressed[i].size(); |
michael@0 | 453 | } |
michael@0 | 454 | } |
michael@0 | 455 | |
michael@0 | 456 | sort(ctime, ctime + kRuns); |
michael@0 | 457 | sort(utime, utime + kRuns); |
michael@0 | 458 | const int med = kRuns/2; |
michael@0 | 459 | |
michael@0 | 460 | float comp_rate = (length / ctime[med]) * repeats / 1048576.0; |
michael@0 | 461 | float uncomp_rate = (length / utime[med]) * repeats / 1048576.0; |
michael@0 | 462 | string x = names[comp]; |
michael@0 | 463 | x += ":"; |
michael@0 | 464 | string urate = (uncomp_rate >= 0) |
michael@0 | 465 | ? StringPrintf("%.1f", uncomp_rate) |
michael@0 | 466 | : string("?"); |
michael@0 | 467 | printf("%-7s [b %dM] bytes %6d -> %6d %4.1f%% " |
michael@0 | 468 | "comp %5.1f MB/s uncomp %5s MB/s\n", |
michael@0 | 469 | x.c_str(), |
michael@0 | 470 | block_size/(1<<20), |
michael@0 | 471 | static_cast<int>(length), static_cast<uint32>(compressed_size), |
michael@0 | 472 | (compressed_size * 100.0) / max<int>(1, length), |
michael@0 | 473 | comp_rate, |
michael@0 | 474 | urate.c_str()); |
michael@0 | 475 | } |
michael@0 | 476 | |
michael@0 | 477 | |
michael@0 | 478 | static int VerifyString(const string& input) { |
michael@0 | 479 | string compressed; |
michael@0 | 480 | DataEndingAtUnreadablePage i(input); |
michael@0 | 481 | const size_t written = snappy::Compress(i.data(), i.size(), &compressed); |
michael@0 | 482 | CHECK_EQ(written, compressed.size()); |
michael@0 | 483 | CHECK_LE(compressed.size(), |
michael@0 | 484 | snappy::MaxCompressedLength(input.size())); |
michael@0 | 485 | CHECK(snappy::IsValidCompressedBuffer(compressed.data(), compressed.size())); |
michael@0 | 486 | |
michael@0 | 487 | string uncompressed; |
michael@0 | 488 | DataEndingAtUnreadablePage c(compressed); |
michael@0 | 489 | CHECK(snappy::Uncompress(c.data(), c.size(), &uncompressed)); |
michael@0 | 490 | CHECK_EQ(uncompressed, input); |
michael@0 | 491 | return uncompressed.size(); |
michael@0 | 492 | } |
michael@0 | 493 | |
michael@0 | 494 | |
michael@0 | 495 | // Test that data compressed by a compressor that does not |
michael@0 | 496 | // obey block sizes is uncompressed properly. |
michael@0 | 497 | static void VerifyNonBlockedCompression(const string& input) { |
michael@0 | 498 | if (input.length() > snappy::kBlockSize) { |
michael@0 | 499 | // We cannot test larger blocks than the maximum block size, obviously. |
michael@0 | 500 | return; |
michael@0 | 501 | } |
michael@0 | 502 | |
michael@0 | 503 | string prefix; |
michael@0 | 504 | Varint::Append32(&prefix, input.size()); |
michael@0 | 505 | |
michael@0 | 506 | // Setup compression table |
michael@0 | 507 | snappy::internal::WorkingMemory wmem; |
michael@0 | 508 | int table_size; |
michael@0 | 509 | uint16* table = wmem.GetHashTable(input.size(), &table_size); |
michael@0 | 510 | |
michael@0 | 511 | // Compress entire input in one shot |
michael@0 | 512 | string compressed; |
michael@0 | 513 | compressed += prefix; |
michael@0 | 514 | compressed.resize(prefix.size()+snappy::MaxCompressedLength(input.size())); |
michael@0 | 515 | char* dest = string_as_array(&compressed) + prefix.size(); |
michael@0 | 516 | char* end = snappy::internal::CompressFragment(input.data(), input.size(), |
michael@0 | 517 | dest, table, table_size); |
michael@0 | 518 | compressed.resize(end - compressed.data()); |
michael@0 | 519 | |
michael@0 | 520 | // Uncompress into string |
michael@0 | 521 | string uncomp_str; |
michael@0 | 522 | CHECK(snappy::Uncompress(compressed.data(), compressed.size(), &uncomp_str)); |
michael@0 | 523 | CHECK_EQ(uncomp_str, input); |
michael@0 | 524 | |
michael@0 | 525 | } |
michael@0 | 526 | |
michael@0 | 527 | // Expand the input so that it is at least K times as big as block size |
michael@0 | 528 | static string Expand(const string& input) { |
michael@0 | 529 | static const int K = 3; |
michael@0 | 530 | string data = input; |
michael@0 | 531 | while (data.size() < K * snappy::kBlockSize) { |
michael@0 | 532 | data += input; |
michael@0 | 533 | } |
michael@0 | 534 | return data; |
michael@0 | 535 | } |
michael@0 | 536 | |
michael@0 | 537 | static int Verify(const string& input) { |
michael@0 | 538 | VLOG(1) << "Verifying input of size " << input.size(); |
michael@0 | 539 | |
michael@0 | 540 | // Compress using string based routines |
michael@0 | 541 | const int result = VerifyString(input); |
michael@0 | 542 | |
michael@0 | 543 | |
michael@0 | 544 | VerifyNonBlockedCompression(input); |
michael@0 | 545 | if (!input.empty()) { |
michael@0 | 546 | VerifyNonBlockedCompression(Expand(input)); |
michael@0 | 547 | } |
michael@0 | 548 | |
michael@0 | 549 | |
michael@0 | 550 | return result; |
michael@0 | 551 | } |
michael@0 | 552 | |
michael@0 | 553 | // This test checks to ensure that snappy doesn't coredump if it gets |
michael@0 | 554 | // corrupted data. |
michael@0 | 555 | |
michael@0 | 556 | static bool IsValidCompressedBuffer(const string& c) { |
michael@0 | 557 | return snappy::IsValidCompressedBuffer(c.data(), c.size()); |
michael@0 | 558 | } |
michael@0 | 559 | static bool Uncompress(const string& c, string* u) { |
michael@0 | 560 | return snappy::Uncompress(c.data(), c.size(), u); |
michael@0 | 561 | } |
michael@0 | 562 | |
michael@0 | 563 | TYPED_TEST(CorruptedTest, VerifyCorrupted) { |
michael@0 | 564 | string source = "making sure we don't crash with corrupted input"; |
michael@0 | 565 | VLOG(1) << source; |
michael@0 | 566 | string dest; |
michael@0 | 567 | TypeParam uncmp; |
michael@0 | 568 | snappy::Compress(source.data(), source.size(), &dest); |
michael@0 | 569 | |
michael@0 | 570 | // Mess around with the data. It's hard to simulate all possible |
michael@0 | 571 | // corruptions; this is just one example ... |
michael@0 | 572 | CHECK_GT(dest.size(), 3); |
michael@0 | 573 | dest[1]--; |
michael@0 | 574 | dest[3]++; |
michael@0 | 575 | // this really ought to fail. |
michael@0 | 576 | CHECK(!IsValidCompressedBuffer(TypeParam(dest))); |
michael@0 | 577 | CHECK(!Uncompress(TypeParam(dest), &uncmp)); |
michael@0 | 578 | |
michael@0 | 579 | // This is testing for a security bug - a buffer that decompresses to 100k |
michael@0 | 580 | // but we lie in the snappy header and only reserve 0 bytes of memory :) |
michael@0 | 581 | source.resize(100000); |
michael@0 | 582 | for (int i = 0; i < source.length(); ++i) { |
michael@0 | 583 | source[i] = 'A'; |
michael@0 | 584 | } |
michael@0 | 585 | snappy::Compress(source.data(), source.size(), &dest); |
michael@0 | 586 | dest[0] = dest[1] = dest[2] = dest[3] = 0; |
michael@0 | 587 | CHECK(!IsValidCompressedBuffer(TypeParam(dest))); |
michael@0 | 588 | CHECK(!Uncompress(TypeParam(dest), &uncmp)); |
michael@0 | 589 | |
michael@0 | 590 | if (sizeof(void *) == 4) { |
michael@0 | 591 | // Another security check; check a crazy big length can't DoS us with an |
michael@0 | 592 | // over-allocation. |
michael@0 | 593 | // Currently this is done only for 32-bit builds. On 64-bit builds, |
michael@0 | 594 | // where 3GBytes might be an acceptable allocation size, Uncompress() |
michael@0 | 595 | // attempts to decompress, and sometimes causes the test to run out of |
michael@0 | 596 | // memory. |
michael@0 | 597 | dest[0] = dest[1] = dest[2] = dest[3] = 0xff; |
michael@0 | 598 | // This decodes to a really large size, i.e., 3221225471 bytes |
michael@0 | 599 | dest[4] = 'k'; |
michael@0 | 600 | CHECK(!IsValidCompressedBuffer(TypeParam(dest))); |
michael@0 | 601 | CHECK(!Uncompress(TypeParam(dest), &uncmp)); |
michael@0 | 602 | dest[0] = dest[1] = dest[2] = 0xff; |
michael@0 | 603 | dest[3] = 0x7f; |
michael@0 | 604 | CHECK(!IsValidCompressedBuffer(TypeParam(dest))); |
michael@0 | 605 | CHECK(!Uncompress(TypeParam(dest), &uncmp)); |
michael@0 | 606 | } else { |
michael@0 | 607 | LOG(WARNING) << "Crazy decompression lengths not checked on 64-bit build"; |
michael@0 | 608 | } |
michael@0 | 609 | |
michael@0 | 610 | // try reading stuff in from a bad file. |
michael@0 | 611 | for (int i = 1; i <= 3; ++i) { |
michael@0 | 612 | string data = ReadTestDataFile(StringPrintf("baddata%d.snappy", i).c_str()); |
michael@0 | 613 | string uncmp; |
michael@0 | 614 | // check that we don't return a crazy length |
michael@0 | 615 | size_t ulen; |
michael@0 | 616 | CHECK(!snappy::GetUncompressedLength(data.data(), data.size(), &ulen) |
michael@0 | 617 | || (ulen < (1<<20))); |
michael@0 | 618 | uint32 ulen2; |
michael@0 | 619 | snappy::ByteArraySource source(data.data(), data.size()); |
michael@0 | 620 | CHECK(!snappy::GetUncompressedLength(&source, &ulen2) || |
michael@0 | 621 | (ulen2 < (1<<20))); |
michael@0 | 622 | CHECK(!IsValidCompressedBuffer(TypeParam(data))); |
michael@0 | 623 | CHECK(!Uncompress(TypeParam(data), &uncmp)); |
michael@0 | 624 | } |
michael@0 | 625 | } |
michael@0 | 626 | |
michael@0 | 627 | // Helper routines to construct arbitrary compressed strings. |
michael@0 | 628 | // These mirror the compression code in snappy.cc, but are copied |
michael@0 | 629 | // here so that we can bypass some limitations in the how snappy.cc |
michael@0 | 630 | // invokes these routines. |
michael@0 | 631 | static void AppendLiteral(string* dst, const string& literal) { |
michael@0 | 632 | if (literal.empty()) return; |
michael@0 | 633 | int n = literal.size() - 1; |
michael@0 | 634 | if (n < 60) { |
michael@0 | 635 | // Fit length in tag byte |
michael@0 | 636 | dst->push_back(0 | (n << 2)); |
michael@0 | 637 | } else { |
michael@0 | 638 | // Encode in upcoming bytes |
michael@0 | 639 | char number[4]; |
michael@0 | 640 | int count = 0; |
michael@0 | 641 | while (n > 0) { |
michael@0 | 642 | number[count++] = n & 0xff; |
michael@0 | 643 | n >>= 8; |
michael@0 | 644 | } |
michael@0 | 645 | dst->push_back(0 | ((59+count) << 2)); |
michael@0 | 646 | *dst += string(number, count); |
michael@0 | 647 | } |
michael@0 | 648 | *dst += literal; |
michael@0 | 649 | } |
michael@0 | 650 | |
michael@0 | 651 | static void AppendCopy(string* dst, int offset, int length) { |
michael@0 | 652 | while (length > 0) { |
michael@0 | 653 | // Figure out how much to copy in one shot |
michael@0 | 654 | int to_copy; |
michael@0 | 655 | if (length >= 68) { |
michael@0 | 656 | to_copy = 64; |
michael@0 | 657 | } else if (length > 64) { |
michael@0 | 658 | to_copy = 60; |
michael@0 | 659 | } else { |
michael@0 | 660 | to_copy = length; |
michael@0 | 661 | } |
michael@0 | 662 | length -= to_copy; |
michael@0 | 663 | |
michael@0 | 664 | if ((to_copy < 12) && (offset < 2048)) { |
michael@0 | 665 | assert(to_copy-4 < 8); // Must fit in 3 bits |
michael@0 | 666 | dst->push_back(1 | ((to_copy-4) << 2) | ((offset >> 8) << 5)); |
michael@0 | 667 | dst->push_back(offset & 0xff); |
michael@0 | 668 | } else if (offset < 65536) { |
michael@0 | 669 | dst->push_back(2 | ((to_copy-1) << 2)); |
michael@0 | 670 | dst->push_back(offset & 0xff); |
michael@0 | 671 | dst->push_back(offset >> 8); |
michael@0 | 672 | } else { |
michael@0 | 673 | dst->push_back(3 | ((to_copy-1) << 2)); |
michael@0 | 674 | dst->push_back(offset & 0xff); |
michael@0 | 675 | dst->push_back((offset >> 8) & 0xff); |
michael@0 | 676 | dst->push_back((offset >> 16) & 0xff); |
michael@0 | 677 | dst->push_back((offset >> 24) & 0xff); |
michael@0 | 678 | } |
michael@0 | 679 | } |
michael@0 | 680 | } |
michael@0 | 681 | |
michael@0 | 682 | TEST(Snappy, SimpleTests) { |
michael@0 | 683 | Verify(""); |
michael@0 | 684 | Verify("a"); |
michael@0 | 685 | Verify("ab"); |
michael@0 | 686 | Verify("abc"); |
michael@0 | 687 | |
michael@0 | 688 | Verify("aaaaaaa" + string(16, 'b') + string("aaaaa") + "abc"); |
michael@0 | 689 | Verify("aaaaaaa" + string(256, 'b') + string("aaaaa") + "abc"); |
michael@0 | 690 | Verify("aaaaaaa" + string(2047, 'b') + string("aaaaa") + "abc"); |
michael@0 | 691 | Verify("aaaaaaa" + string(65536, 'b') + string("aaaaa") + "abc"); |
michael@0 | 692 | Verify("abcaaaaaaa" + string(65536, 'b') + string("aaaaa") + "abc"); |
michael@0 | 693 | } |
michael@0 | 694 | |
michael@0 | 695 | // Verify max blowup (lots of four-byte copies) |
michael@0 | 696 | TEST(Snappy, MaxBlowup) { |
michael@0 | 697 | string input; |
michael@0 | 698 | for (int i = 0; i < 20000; i++) { |
michael@0 | 699 | ACMRandom rnd(i); |
michael@0 | 700 | uint32 bytes = static_cast<uint32>(rnd.Next()); |
michael@0 | 701 | input.append(reinterpret_cast<char*>(&bytes), sizeof(bytes)); |
michael@0 | 702 | } |
michael@0 | 703 | for (int i = 19999; i >= 0; i--) { |
michael@0 | 704 | ACMRandom rnd(i); |
michael@0 | 705 | uint32 bytes = static_cast<uint32>(rnd.Next()); |
michael@0 | 706 | input.append(reinterpret_cast<char*>(&bytes), sizeof(bytes)); |
michael@0 | 707 | } |
michael@0 | 708 | Verify(input); |
michael@0 | 709 | } |
michael@0 | 710 | |
michael@0 | 711 | TEST(Snappy, RandomData) { |
michael@0 | 712 | ACMRandom rnd(FLAGS_test_random_seed); |
michael@0 | 713 | |
michael@0 | 714 | const int num_ops = 20000; |
michael@0 | 715 | for (int i = 0; i < num_ops; i++) { |
michael@0 | 716 | if ((i % 1000) == 0) { |
michael@0 | 717 | VLOG(0) << "Random op " << i << " of " << num_ops; |
michael@0 | 718 | } |
michael@0 | 719 | |
michael@0 | 720 | string x; |
michael@0 | 721 | int len = rnd.Uniform(4096); |
michael@0 | 722 | if (i < 100) { |
michael@0 | 723 | len = 65536 + rnd.Uniform(65536); |
michael@0 | 724 | } |
michael@0 | 725 | while (x.size() < len) { |
michael@0 | 726 | int run_len = 1; |
michael@0 | 727 | if (rnd.OneIn(10)) { |
michael@0 | 728 | run_len = rnd.Skewed(8); |
michael@0 | 729 | } |
michael@0 | 730 | char c = (i < 100) ? rnd.Uniform(256) : rnd.Skewed(3); |
michael@0 | 731 | while (run_len-- > 0 && x.size() < len) { |
michael@0 | 732 | x += c; |
michael@0 | 733 | } |
michael@0 | 734 | } |
michael@0 | 735 | |
michael@0 | 736 | Verify(x); |
michael@0 | 737 | } |
michael@0 | 738 | } |
michael@0 | 739 | |
michael@0 | 740 | TEST(Snappy, FourByteOffset) { |
michael@0 | 741 | // The new compressor cannot generate four-byte offsets since |
michael@0 | 742 | // it chops up the input into 32KB pieces. So we hand-emit the |
michael@0 | 743 | // copy manually. |
michael@0 | 744 | |
michael@0 | 745 | // The two fragments that make up the input string. |
michael@0 | 746 | string fragment1 = "012345689abcdefghijklmnopqrstuvwxyz"; |
michael@0 | 747 | string fragment2 = "some other string"; |
michael@0 | 748 | |
michael@0 | 749 | // How many times each fragment is emitted. |
michael@0 | 750 | const int n1 = 2; |
michael@0 | 751 | const int n2 = 100000 / fragment2.size(); |
michael@0 | 752 | const int length = n1 * fragment1.size() + n2 * fragment2.size(); |
michael@0 | 753 | |
michael@0 | 754 | string compressed; |
michael@0 | 755 | Varint::Append32(&compressed, length); |
michael@0 | 756 | |
michael@0 | 757 | AppendLiteral(&compressed, fragment1); |
michael@0 | 758 | string src = fragment1; |
michael@0 | 759 | for (int i = 0; i < n2; i++) { |
michael@0 | 760 | AppendLiteral(&compressed, fragment2); |
michael@0 | 761 | src += fragment2; |
michael@0 | 762 | } |
michael@0 | 763 | AppendCopy(&compressed, src.size(), fragment1.size()); |
michael@0 | 764 | src += fragment1; |
michael@0 | 765 | CHECK_EQ(length, src.size()); |
michael@0 | 766 | |
michael@0 | 767 | string uncompressed; |
michael@0 | 768 | CHECK(snappy::IsValidCompressedBuffer(compressed.data(), compressed.size())); |
michael@0 | 769 | CHECK(snappy::Uncompress(compressed.data(), compressed.size(), &uncompressed)); |
michael@0 | 770 | CHECK_EQ(uncompressed, src); |
michael@0 | 771 | } |
michael@0 | 772 | |
michael@0 | 773 | |
michael@0 | 774 | static bool CheckUncompressedLength(const string& compressed, |
michael@0 | 775 | size_t* ulength) { |
michael@0 | 776 | const bool result1 = snappy::GetUncompressedLength(compressed.data(), |
michael@0 | 777 | compressed.size(), |
michael@0 | 778 | ulength); |
michael@0 | 779 | |
michael@0 | 780 | snappy::ByteArraySource source(compressed.data(), compressed.size()); |
michael@0 | 781 | uint32 length; |
michael@0 | 782 | const bool result2 = snappy::GetUncompressedLength(&source, &length); |
michael@0 | 783 | CHECK_EQ(result1, result2); |
michael@0 | 784 | return result1; |
michael@0 | 785 | } |
michael@0 | 786 | |
michael@0 | 787 | TEST(SnappyCorruption, TruncatedVarint) { |
michael@0 | 788 | string compressed, uncompressed; |
michael@0 | 789 | size_t ulength; |
michael@0 | 790 | compressed.push_back('\xf0'); |
michael@0 | 791 | CHECK(!CheckUncompressedLength(compressed, &ulength)); |
michael@0 | 792 | CHECK(!snappy::IsValidCompressedBuffer(compressed.data(), compressed.size())); |
michael@0 | 793 | CHECK(!snappy::Uncompress(compressed.data(), compressed.size(), |
michael@0 | 794 | &uncompressed)); |
michael@0 | 795 | } |
michael@0 | 796 | |
michael@0 | 797 | TEST(SnappyCorruption, UnterminatedVarint) { |
michael@0 | 798 | string compressed, uncompressed; |
michael@0 | 799 | size_t ulength; |
michael@0 | 800 | compressed.push_back(128); |
michael@0 | 801 | compressed.push_back(128); |
michael@0 | 802 | compressed.push_back(128); |
michael@0 | 803 | compressed.push_back(128); |
michael@0 | 804 | compressed.push_back(128); |
michael@0 | 805 | compressed.push_back(10); |
michael@0 | 806 | CHECK(!CheckUncompressedLength(compressed, &ulength)); |
michael@0 | 807 | CHECK(!snappy::IsValidCompressedBuffer(compressed.data(), compressed.size())); |
michael@0 | 808 | CHECK(!snappy::Uncompress(compressed.data(), compressed.size(), |
michael@0 | 809 | &uncompressed)); |
michael@0 | 810 | } |
michael@0 | 811 | |
michael@0 | 812 | TEST(Snappy, ReadPastEndOfBuffer) { |
michael@0 | 813 | // Check that we do not read past end of input |
michael@0 | 814 | |
michael@0 | 815 | // Make a compressed string that ends with a single-byte literal |
michael@0 | 816 | string compressed; |
michael@0 | 817 | Varint::Append32(&compressed, 1); |
michael@0 | 818 | AppendLiteral(&compressed, "x"); |
michael@0 | 819 | |
michael@0 | 820 | string uncompressed; |
michael@0 | 821 | DataEndingAtUnreadablePage c(compressed); |
michael@0 | 822 | CHECK(snappy::Uncompress(c.data(), c.size(), &uncompressed)); |
michael@0 | 823 | CHECK_EQ(uncompressed, string("x")); |
michael@0 | 824 | } |
michael@0 | 825 | |
michael@0 | 826 | // Check for an infinite loop caused by a copy with offset==0 |
michael@0 | 827 | TEST(Snappy, ZeroOffsetCopy) { |
michael@0 | 828 | const char* compressed = "\x40\x12\x00\x00"; |
michael@0 | 829 | // \x40 Length (must be > kMaxIncrementCopyOverflow) |
michael@0 | 830 | // \x12\x00\x00 Copy with offset==0, length==5 |
michael@0 | 831 | char uncompressed[100]; |
michael@0 | 832 | EXPECT_FALSE(snappy::RawUncompress(compressed, 4, uncompressed)); |
michael@0 | 833 | } |
michael@0 | 834 | |
michael@0 | 835 | TEST(Snappy, ZeroOffsetCopyValidation) { |
michael@0 | 836 | const char* compressed = "\x05\x12\x00\x00"; |
michael@0 | 837 | // \x05 Length |
michael@0 | 838 | // \x12\x00\x00 Copy with offset==0, length==5 |
michael@0 | 839 | EXPECT_FALSE(snappy::IsValidCompressedBuffer(compressed, 4)); |
michael@0 | 840 | } |
michael@0 | 841 | |
michael@0 | 842 | |
michael@0 | 843 | namespace { |
michael@0 | 844 | |
michael@0 | 845 | int TestFindMatchLength(const char* s1, const char *s2, unsigned length) { |
michael@0 | 846 | return snappy::internal::FindMatchLength(s1, s2, s2 + length); |
michael@0 | 847 | } |
michael@0 | 848 | |
michael@0 | 849 | } // namespace |
michael@0 | 850 | |
michael@0 | 851 | TEST(Snappy, FindMatchLength) { |
michael@0 | 852 | // Exercise all different code paths through the function. |
michael@0 | 853 | // 64-bit version: |
michael@0 | 854 | |
michael@0 | 855 | // Hit s1_limit in 64-bit loop, hit s1_limit in single-character loop. |
michael@0 | 856 | EXPECT_EQ(6, TestFindMatchLength("012345", "012345", 6)); |
michael@0 | 857 | EXPECT_EQ(11, TestFindMatchLength("01234567abc", "01234567abc", 11)); |
michael@0 | 858 | |
michael@0 | 859 | // Hit s1_limit in 64-bit loop, find a non-match in single-character loop. |
michael@0 | 860 | EXPECT_EQ(9, TestFindMatchLength("01234567abc", "01234567axc", 9)); |
michael@0 | 861 | |
michael@0 | 862 | // Same, but edge cases. |
michael@0 | 863 | EXPECT_EQ(11, TestFindMatchLength("01234567abc!", "01234567abc!", 11)); |
michael@0 | 864 | EXPECT_EQ(11, TestFindMatchLength("01234567abc!", "01234567abc?", 11)); |
michael@0 | 865 | |
michael@0 | 866 | // Find non-match at once in first loop. |
michael@0 | 867 | EXPECT_EQ(0, TestFindMatchLength("01234567xxxxxxxx", "?1234567xxxxxxxx", 16)); |
michael@0 | 868 | EXPECT_EQ(1, TestFindMatchLength("01234567xxxxxxxx", "0?234567xxxxxxxx", 16)); |
michael@0 | 869 | EXPECT_EQ(4, TestFindMatchLength("01234567xxxxxxxx", "01237654xxxxxxxx", 16)); |
michael@0 | 870 | EXPECT_EQ(7, TestFindMatchLength("01234567xxxxxxxx", "0123456?xxxxxxxx", 16)); |
michael@0 | 871 | |
michael@0 | 872 | // Find non-match in first loop after one block. |
michael@0 | 873 | EXPECT_EQ(8, TestFindMatchLength("abcdefgh01234567xxxxxxxx", |
michael@0 | 874 | "abcdefgh?1234567xxxxxxxx", 24)); |
michael@0 | 875 | EXPECT_EQ(9, TestFindMatchLength("abcdefgh01234567xxxxxxxx", |
michael@0 | 876 | "abcdefgh0?234567xxxxxxxx", 24)); |
michael@0 | 877 | EXPECT_EQ(12, TestFindMatchLength("abcdefgh01234567xxxxxxxx", |
michael@0 | 878 | "abcdefgh01237654xxxxxxxx", 24)); |
michael@0 | 879 | EXPECT_EQ(15, TestFindMatchLength("abcdefgh01234567xxxxxxxx", |
michael@0 | 880 | "abcdefgh0123456?xxxxxxxx", 24)); |
michael@0 | 881 | |
michael@0 | 882 | // 32-bit version: |
michael@0 | 883 | |
michael@0 | 884 | // Short matches. |
michael@0 | 885 | EXPECT_EQ(0, TestFindMatchLength("01234567", "?1234567", 8)); |
michael@0 | 886 | EXPECT_EQ(1, TestFindMatchLength("01234567", "0?234567", 8)); |
michael@0 | 887 | EXPECT_EQ(2, TestFindMatchLength("01234567", "01?34567", 8)); |
michael@0 | 888 | EXPECT_EQ(3, TestFindMatchLength("01234567", "012?4567", 8)); |
michael@0 | 889 | EXPECT_EQ(4, TestFindMatchLength("01234567", "0123?567", 8)); |
michael@0 | 890 | EXPECT_EQ(5, TestFindMatchLength("01234567", "01234?67", 8)); |
michael@0 | 891 | EXPECT_EQ(6, TestFindMatchLength("01234567", "012345?7", 8)); |
michael@0 | 892 | EXPECT_EQ(7, TestFindMatchLength("01234567", "0123456?", 8)); |
michael@0 | 893 | EXPECT_EQ(7, TestFindMatchLength("01234567", "0123456?", 7)); |
michael@0 | 894 | EXPECT_EQ(7, TestFindMatchLength("01234567!", "0123456??", 7)); |
michael@0 | 895 | |
michael@0 | 896 | // Hit s1_limit in 32-bit loop, hit s1_limit in single-character loop. |
michael@0 | 897 | EXPECT_EQ(10, TestFindMatchLength("xxxxxxabcd", "xxxxxxabcd", 10)); |
michael@0 | 898 | EXPECT_EQ(10, TestFindMatchLength("xxxxxxabcd?", "xxxxxxabcd?", 10)); |
michael@0 | 899 | EXPECT_EQ(13, TestFindMatchLength("xxxxxxabcdef", "xxxxxxabcdef", 13)); |
michael@0 | 900 | |
michael@0 | 901 | // Same, but edge cases. |
michael@0 | 902 | EXPECT_EQ(12, TestFindMatchLength("xxxxxx0123abc!", "xxxxxx0123abc!", 12)); |
michael@0 | 903 | EXPECT_EQ(12, TestFindMatchLength("xxxxxx0123abc!", "xxxxxx0123abc?", 12)); |
michael@0 | 904 | |
michael@0 | 905 | // Hit s1_limit in 32-bit loop, find a non-match in single-character loop. |
michael@0 | 906 | EXPECT_EQ(11, TestFindMatchLength("xxxxxx0123abc", "xxxxxx0123axc", 13)); |
michael@0 | 907 | |
michael@0 | 908 | // Find non-match at once in first loop. |
michael@0 | 909 | EXPECT_EQ(6, TestFindMatchLength("xxxxxx0123xxxxxxxx", |
michael@0 | 910 | "xxxxxx?123xxxxxxxx", 18)); |
michael@0 | 911 | EXPECT_EQ(7, TestFindMatchLength("xxxxxx0123xxxxxxxx", |
michael@0 | 912 | "xxxxxx0?23xxxxxxxx", 18)); |
michael@0 | 913 | EXPECT_EQ(8, TestFindMatchLength("xxxxxx0123xxxxxxxx", |
michael@0 | 914 | "xxxxxx0132xxxxxxxx", 18)); |
michael@0 | 915 | EXPECT_EQ(9, TestFindMatchLength("xxxxxx0123xxxxxxxx", |
michael@0 | 916 | "xxxxxx012?xxxxxxxx", 18)); |
michael@0 | 917 | |
michael@0 | 918 | // Same, but edge cases. |
michael@0 | 919 | EXPECT_EQ(6, TestFindMatchLength("xxxxxx0123", "xxxxxx?123", 10)); |
michael@0 | 920 | EXPECT_EQ(7, TestFindMatchLength("xxxxxx0123", "xxxxxx0?23", 10)); |
michael@0 | 921 | EXPECT_EQ(8, TestFindMatchLength("xxxxxx0123", "xxxxxx0132", 10)); |
michael@0 | 922 | EXPECT_EQ(9, TestFindMatchLength("xxxxxx0123", "xxxxxx012?", 10)); |
michael@0 | 923 | |
michael@0 | 924 | // Find non-match in first loop after one block. |
michael@0 | 925 | EXPECT_EQ(10, TestFindMatchLength("xxxxxxabcd0123xx", |
michael@0 | 926 | "xxxxxxabcd?123xx", 16)); |
michael@0 | 927 | EXPECT_EQ(11, TestFindMatchLength("xxxxxxabcd0123xx", |
michael@0 | 928 | "xxxxxxabcd0?23xx", 16)); |
michael@0 | 929 | EXPECT_EQ(12, TestFindMatchLength("xxxxxxabcd0123xx", |
michael@0 | 930 | "xxxxxxabcd0132xx", 16)); |
michael@0 | 931 | EXPECT_EQ(13, TestFindMatchLength("xxxxxxabcd0123xx", |
michael@0 | 932 | "xxxxxxabcd012?xx", 16)); |
michael@0 | 933 | |
michael@0 | 934 | // Same, but edge cases. |
michael@0 | 935 | EXPECT_EQ(10, TestFindMatchLength("xxxxxxabcd0123", "xxxxxxabcd?123", 14)); |
michael@0 | 936 | EXPECT_EQ(11, TestFindMatchLength("xxxxxxabcd0123", "xxxxxxabcd0?23", 14)); |
michael@0 | 937 | EXPECT_EQ(12, TestFindMatchLength("xxxxxxabcd0123", "xxxxxxabcd0132", 14)); |
michael@0 | 938 | EXPECT_EQ(13, TestFindMatchLength("xxxxxxabcd0123", "xxxxxxabcd012?", 14)); |
michael@0 | 939 | } |
michael@0 | 940 | |
michael@0 | 941 | TEST(Snappy, FindMatchLengthRandom) { |
michael@0 | 942 | const int kNumTrials = 10000; |
michael@0 | 943 | const int kTypicalLength = 10; |
michael@0 | 944 | ACMRandom rnd(FLAGS_test_random_seed); |
michael@0 | 945 | |
michael@0 | 946 | for (int i = 0; i < kNumTrials; i++) { |
michael@0 | 947 | string s, t; |
michael@0 | 948 | char a = rnd.Rand8(); |
michael@0 | 949 | char b = rnd.Rand8(); |
michael@0 | 950 | while (!rnd.OneIn(kTypicalLength)) { |
michael@0 | 951 | s.push_back(rnd.OneIn(2) ? a : b); |
michael@0 | 952 | t.push_back(rnd.OneIn(2) ? a : b); |
michael@0 | 953 | } |
michael@0 | 954 | DataEndingAtUnreadablePage u(s); |
michael@0 | 955 | DataEndingAtUnreadablePage v(t); |
michael@0 | 956 | int matched = snappy::internal::FindMatchLength( |
michael@0 | 957 | u.data(), v.data(), v.data() + t.size()); |
michael@0 | 958 | if (matched == t.size()) { |
michael@0 | 959 | EXPECT_EQ(s, t); |
michael@0 | 960 | } else { |
michael@0 | 961 | EXPECT_NE(s[matched], t[matched]); |
michael@0 | 962 | for (int j = 0; j < matched; j++) { |
michael@0 | 963 | EXPECT_EQ(s[j], t[j]); |
michael@0 | 964 | } |
michael@0 | 965 | } |
michael@0 | 966 | } |
michael@0 | 967 | } |
michael@0 | 968 | |
michael@0 | 969 | |
michael@0 | 970 | static void CompressFile(const char* fname) { |
michael@0 | 971 | string fullinput; |
michael@0 | 972 | File::ReadFileToStringOrDie(fname, &fullinput); |
michael@0 | 973 | |
michael@0 | 974 | string compressed; |
michael@0 | 975 | Compress(fullinput.data(), fullinput.size(), SNAPPY, &compressed, false); |
michael@0 | 976 | |
michael@0 | 977 | File::WriteStringToFileOrDie(compressed, |
michael@0 | 978 | string(fname).append(".comp").c_str()); |
michael@0 | 979 | } |
michael@0 | 980 | |
michael@0 | 981 | static void UncompressFile(const char* fname) { |
michael@0 | 982 | string fullinput; |
michael@0 | 983 | File::ReadFileToStringOrDie(fname, &fullinput); |
michael@0 | 984 | |
michael@0 | 985 | size_t uncompLength; |
michael@0 | 986 | CHECK(CheckUncompressedLength(fullinput, &uncompLength)); |
michael@0 | 987 | |
michael@0 | 988 | string uncompressed; |
michael@0 | 989 | uncompressed.resize(uncompLength); |
michael@0 | 990 | CHECK(snappy::Uncompress(fullinput.data(), fullinput.size(), &uncompressed)); |
michael@0 | 991 | |
michael@0 | 992 | File::WriteStringToFileOrDie(uncompressed, |
michael@0 | 993 | string(fname).append(".uncomp").c_str()); |
michael@0 | 994 | } |
michael@0 | 995 | |
michael@0 | 996 | static void MeasureFile(const char* fname) { |
michael@0 | 997 | string fullinput; |
michael@0 | 998 | File::ReadFileToStringOrDie(fname, &fullinput); |
michael@0 | 999 | printf("%-40s :\n", fname); |
michael@0 | 1000 | |
michael@0 | 1001 | int start_len = (FLAGS_start_len < 0) ? fullinput.size() : FLAGS_start_len; |
michael@0 | 1002 | int end_len = fullinput.size(); |
michael@0 | 1003 | if (FLAGS_end_len >= 0) { |
michael@0 | 1004 | end_len = min<int>(fullinput.size(), FLAGS_end_len); |
michael@0 | 1005 | } |
michael@0 | 1006 | for (int len = start_len; len <= end_len; len++) { |
michael@0 | 1007 | const char* const input = fullinput.data(); |
michael@0 | 1008 | int repeats = (FLAGS_bytes + len) / (len + 1); |
michael@0 | 1009 | if (FLAGS_zlib) Measure(input, len, ZLIB, repeats, 1024<<10); |
michael@0 | 1010 | if (FLAGS_lzo) Measure(input, len, LZO, repeats, 1024<<10); |
michael@0 | 1011 | if (FLAGS_liblzf) Measure(input, len, LIBLZF, repeats, 1024<<10); |
michael@0 | 1012 | if (FLAGS_quicklz) Measure(input, len, QUICKLZ, repeats, 1024<<10); |
michael@0 | 1013 | if (FLAGS_fastlz) Measure(input, len, FASTLZ, repeats, 1024<<10); |
michael@0 | 1014 | if (FLAGS_snappy) Measure(input, len, SNAPPY, repeats, 4096<<10); |
michael@0 | 1015 | |
michael@0 | 1016 | // For block-size based measurements |
michael@0 | 1017 | if (0 && FLAGS_snappy) { |
michael@0 | 1018 | Measure(input, len, SNAPPY, repeats, 8<<10); |
michael@0 | 1019 | Measure(input, len, SNAPPY, repeats, 16<<10); |
michael@0 | 1020 | Measure(input, len, SNAPPY, repeats, 32<<10); |
michael@0 | 1021 | Measure(input, len, SNAPPY, repeats, 64<<10); |
michael@0 | 1022 | Measure(input, len, SNAPPY, repeats, 256<<10); |
michael@0 | 1023 | Measure(input, len, SNAPPY, repeats, 1024<<10); |
michael@0 | 1024 | } |
michael@0 | 1025 | } |
michael@0 | 1026 | } |
michael@0 | 1027 | |
michael@0 | 1028 | static struct { |
michael@0 | 1029 | const char* label; |
michael@0 | 1030 | const char* filename; |
michael@0 | 1031 | } files[] = { |
michael@0 | 1032 | { "html", "html" }, |
michael@0 | 1033 | { "urls", "urls.10K" }, |
michael@0 | 1034 | { "jpg", "house.jpg" }, |
michael@0 | 1035 | { "pdf", "mapreduce-osdi-1.pdf" }, |
michael@0 | 1036 | { "html4", "html_x_4" }, |
michael@0 | 1037 | { "cp", "cp.html" }, |
michael@0 | 1038 | { "c", "fields.c" }, |
michael@0 | 1039 | { "lsp", "grammar.lsp" }, |
michael@0 | 1040 | { "xls", "kennedy.xls" }, |
michael@0 | 1041 | { "txt1", "alice29.txt" }, |
michael@0 | 1042 | { "txt2", "asyoulik.txt" }, |
michael@0 | 1043 | { "txt3", "lcet10.txt" }, |
michael@0 | 1044 | { "txt4", "plrabn12.txt" }, |
michael@0 | 1045 | { "bin", "ptt5" }, |
michael@0 | 1046 | { "sum", "sum" }, |
michael@0 | 1047 | { "man", "xargs.1" }, |
michael@0 | 1048 | { "pb", "geo.protodata" }, |
michael@0 | 1049 | { "gaviota", "kppkn.gtb" }, |
michael@0 | 1050 | }; |
michael@0 | 1051 | |
michael@0 | 1052 | static void BM_UFlat(int iters, int arg) { |
michael@0 | 1053 | StopBenchmarkTiming(); |
michael@0 | 1054 | |
michael@0 | 1055 | // Pick file to process based on "arg" |
michael@0 | 1056 | CHECK_GE(arg, 0); |
michael@0 | 1057 | CHECK_LT(arg, ARRAYSIZE(files)); |
michael@0 | 1058 | string contents = ReadTestDataFile(files[arg].filename); |
michael@0 | 1059 | |
michael@0 | 1060 | string zcontents; |
michael@0 | 1061 | snappy::Compress(contents.data(), contents.size(), &zcontents); |
michael@0 | 1062 | char* dst = new char[contents.size()]; |
michael@0 | 1063 | |
michael@0 | 1064 | SetBenchmarkBytesProcessed(static_cast<int64>(iters) * |
michael@0 | 1065 | static_cast<int64>(contents.size())); |
michael@0 | 1066 | SetBenchmarkLabel(files[arg].label); |
michael@0 | 1067 | StartBenchmarkTiming(); |
michael@0 | 1068 | while (iters-- > 0) { |
michael@0 | 1069 | CHECK(snappy::RawUncompress(zcontents.data(), zcontents.size(), dst)); |
michael@0 | 1070 | } |
michael@0 | 1071 | StopBenchmarkTiming(); |
michael@0 | 1072 | |
michael@0 | 1073 | delete[] dst; |
michael@0 | 1074 | } |
michael@0 | 1075 | BENCHMARK(BM_UFlat)->DenseRange(0, 17); |
michael@0 | 1076 | |
michael@0 | 1077 | static void BM_UValidate(int iters, int arg) { |
michael@0 | 1078 | StopBenchmarkTiming(); |
michael@0 | 1079 | |
michael@0 | 1080 | // Pick file to process based on "arg" |
michael@0 | 1081 | CHECK_GE(arg, 0); |
michael@0 | 1082 | CHECK_LT(arg, ARRAYSIZE(files)); |
michael@0 | 1083 | string contents = ReadTestDataFile(files[arg].filename); |
michael@0 | 1084 | |
michael@0 | 1085 | string zcontents; |
michael@0 | 1086 | snappy::Compress(contents.data(), contents.size(), &zcontents); |
michael@0 | 1087 | |
michael@0 | 1088 | SetBenchmarkBytesProcessed(static_cast<int64>(iters) * |
michael@0 | 1089 | static_cast<int64>(contents.size())); |
michael@0 | 1090 | SetBenchmarkLabel(files[arg].label); |
michael@0 | 1091 | StartBenchmarkTiming(); |
michael@0 | 1092 | while (iters-- > 0) { |
michael@0 | 1093 | CHECK(snappy::IsValidCompressedBuffer(zcontents.data(), zcontents.size())); |
michael@0 | 1094 | } |
michael@0 | 1095 | StopBenchmarkTiming(); |
michael@0 | 1096 | } |
michael@0 | 1097 | BENCHMARK(BM_UValidate)->DenseRange(0, 4); |
michael@0 | 1098 | |
michael@0 | 1099 | |
michael@0 | 1100 | static void BM_ZFlat(int iters, int arg) { |
michael@0 | 1101 | StopBenchmarkTiming(); |
michael@0 | 1102 | |
michael@0 | 1103 | // Pick file to process based on "arg" |
michael@0 | 1104 | CHECK_GE(arg, 0); |
michael@0 | 1105 | CHECK_LT(arg, ARRAYSIZE(files)); |
michael@0 | 1106 | string contents = ReadTestDataFile(files[arg].filename); |
michael@0 | 1107 | |
michael@0 | 1108 | char* dst = new char[snappy::MaxCompressedLength(contents.size())]; |
michael@0 | 1109 | |
michael@0 | 1110 | SetBenchmarkBytesProcessed(static_cast<int64>(iters) * |
michael@0 | 1111 | static_cast<int64>(contents.size())); |
michael@0 | 1112 | StartBenchmarkTiming(); |
michael@0 | 1113 | |
michael@0 | 1114 | size_t zsize = 0; |
michael@0 | 1115 | while (iters-- > 0) { |
michael@0 | 1116 | snappy::RawCompress(contents.data(), contents.size(), dst, &zsize); |
michael@0 | 1117 | } |
michael@0 | 1118 | StopBenchmarkTiming(); |
michael@0 | 1119 | const double compression_ratio = |
michael@0 | 1120 | static_cast<double>(zsize) / std::max<size_t>(1, contents.size()); |
michael@0 | 1121 | SetBenchmarkLabel(StringPrintf("%s (%.2f %%)", |
michael@0 | 1122 | files[arg].label, 100.0 * compression_ratio)); |
michael@0 | 1123 | VLOG(0) << StringPrintf("compression for %s: %zd -> %zd bytes", |
michael@0 | 1124 | files[arg].label, contents.size(), zsize); |
michael@0 | 1125 | delete[] dst; |
michael@0 | 1126 | } |
michael@0 | 1127 | BENCHMARK(BM_ZFlat)->DenseRange(0, 17); |
michael@0 | 1128 | |
michael@0 | 1129 | |
michael@0 | 1130 | } // namespace snappy |
michael@0 | 1131 | |
michael@0 | 1132 | |
michael@0 | 1133 | int main(int argc, char** argv) { |
michael@0 | 1134 | InitGoogle(argv[0], &argc, &argv, true); |
michael@0 | 1135 | File::Init(); |
michael@0 | 1136 | RunSpecifiedBenchmarks(); |
michael@0 | 1137 | |
michael@0 | 1138 | |
michael@0 | 1139 | if (argc >= 2) { |
michael@0 | 1140 | for (int arg = 1; arg < argc; arg++) { |
michael@0 | 1141 | if (FLAGS_write_compressed) { |
michael@0 | 1142 | CompressFile(argv[arg]); |
michael@0 | 1143 | } else if (FLAGS_write_uncompressed) { |
michael@0 | 1144 | UncompressFile(argv[arg]); |
michael@0 | 1145 | } else { |
michael@0 | 1146 | MeasureFile(argv[arg]); |
michael@0 | 1147 | } |
michael@0 | 1148 | } |
michael@0 | 1149 | return 0; |
michael@0 | 1150 | } |
michael@0 | 1151 | |
michael@0 | 1152 | return RUN_ALL_TESTS(); |
michael@0 | 1153 | } |