├── testdata ├── invalid_signature ├── small ├── testdata1 ├── testdata2 └── testdata3 ├── src ├── checksum.h ├── encode.h ├── flags.cc ├── encode_main.cc ├── decode_main.cc ├── common_test.cc ├── compressed_graph.h ├── common.cc ├── bit_reader.cc ├── uncompressed_graph_test.cc ├── bit_writer.h ├── roundtrip_test.cc ├── bit_writer.cc ├── uncompressed_graph.cc ├── huffman_test.cc ├── entropy_coder_common_test.cc ├── ans_test.cc ├── huffman.h ├── common.h ├── bits_test.cc ├── bit_reader.h ├── context_model.h ├── traversal_main_compressed.cc ├── traversal_main_uncompressed.cc ├── uncompressed_graph.h ├── ans.h ├── integer_coder.h ├── huffman.cc ├── compressed_graph.cc ├── decode.h ├── ans.cc └── encode.cc ├── README.md ├── CMakeLists.txt └── LICENSE /testdata/invalid_signature: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/small: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/zuckerli/HEAD/testdata/small -------------------------------------------------------------------------------- /testdata/testdata1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/zuckerli/HEAD/testdata/testdata1 -------------------------------------------------------------------------------- /testdata/testdata2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/zuckerli/HEAD/testdata/testdata2 -------------------------------------------------------------------------------- /testdata/testdata3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/zuckerli/HEAD/testdata/testdata3 -------------------------------------------------------------------------------- /src/checksum.h: -------------------------------------------------------------------------------- 1 | #ifndef ZUCKERLI_CHECKSUM_H 2 | #define ZUCKERLI_CHECKSUM_H 3 | #include 4 | 5 | #include "common.h" 6 | 7 | namespace zuckerli { 8 | ZKR_INLINE size_t Checksum(size_t chk, size_t a, size_t b) { 9 | return chk + (a ^ b) + (((chk << 23) | (chk >> 41)) ^ ~b); 10 | } 11 | } // namespace zuckerli 12 | 13 | #endif // ZUCKERLI_CHECKSUM_H 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zuckerli 2 | Compression format and data structures for large graphs. 3 | 4 | ## Cloning and compiling 5 | 6 | ``` shell 7 | git clone https://github.com/google/zuckerli 8 | cd zuckerli 9 | git clone https://github.com/abseil/abseil-cpp.git 10 | mkdir build && cd build 11 | cmake -DCMAKE_BUILD_TYPE=Release .. 12 | make -j 12 13 | ``` 14 | 15 | Note that to compile and run tests `googletest` should be installed on your 16 | system. 17 | -------------------------------------------------------------------------------- /src/encode.h: -------------------------------------------------------------------------------- 1 | #ifndef ZUCKERLI_ENCODE_H 2 | #define ZUCKERLI_ENCODE_H 3 | #include 4 | 5 | #include "absl/flags/declare.h" 6 | #include "absl/flags/flag.h" 7 | #include "uncompressed_graph.h" 8 | 9 | ABSL_DECLARE_FLAG(int32_t, num_rounds); 10 | ABSL_DECLARE_FLAG(bool, allow_random_access); 11 | ABSL_DECLARE_FLAG(bool, greedy_random_access); 12 | 13 | namespace zuckerli { 14 | std::vector EncodeGraph(const UncompressedGraph& g, 15 | bool allow_random_access, 16 | size_t* checksum = nullptr); 17 | } 18 | 19 | #endif // ZUCKERLI_ENCODE_H 20 | -------------------------------------------------------------------------------- /src/flags.cc: -------------------------------------------------------------------------------- 1 | #include "context_model.h" 2 | #include "encode.h" 3 | #include "integer_coder.h" 4 | #include "absl/flags/flag.h" 5 | 6 | ABSL_FLAG(int32_t, log2_num_explicit, 4, 7 | "Number of direct-coded tokens (pow2)"); 8 | ABSL_FLAG(int32_t, num_token_bits, 1, "Number of MSBs in token"); 9 | ABSL_FLAG(int32_t, ref_block, 32, 10 | "Number of previous lists to try to copy from"); 11 | 12 | ABSL_FLAG(int32_t, num_rounds, 1, "Number of rounds for reference finding"); 13 | ABSL_FLAG(bool, allow_random_access, false, "Allow random access"); 14 | ABSL_FLAG(bool, greedy_random_access, false, 15 | "Greedy heuristic for random access"); 16 | -------------------------------------------------------------------------------- /src/encode_main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "encode.h" 4 | #include "absl/flags/flag.h" 5 | #include "absl/flags/parse.h" 6 | #include "uncompressed_graph.h" 7 | 8 | ABSL_FLAG(std::string, input_path, "", "Input file path"); 9 | ABSL_FLAG(std::string, output_path, "", "Output file path"); 10 | 11 | int main(int argc, char** argv) { 12 | absl::ParseCommandLine(argc, argv); 13 | FILE* out = fopen(absl::GetFlag(FLAGS_output_path).c_str(), "w"); 14 | if (out == nullptr) { 15 | fprintf(stderr, "Invalid output file %s\n", argv[2]); 16 | } 17 | 18 | zuckerli::UncompressedGraph g(absl::GetFlag(FLAGS_input_path)); 19 | auto data = 20 | zuckerli::EncodeGraph(g, absl::GetFlag(FLAGS_allow_random_access)); 21 | fwrite(data.data(), 1, data.size(), out); 22 | fclose(out); 23 | } 24 | -------------------------------------------------------------------------------- /src/decode_main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | #include "decode.h" 5 | #include "encode.h" 6 | #include "absl/flags/flag.h" 7 | #include "absl/flags/parse.h" 8 | 9 | ABSL_FLAG(std::string, input_path, "", "Input file path"); 10 | 11 | int main(int argc, char** argv) { 12 | absl::ParseCommandLine(argc, argv); 13 | // Ensure that encoder-only flags are recognized by the decoder too. 14 | (void)absl::GetFlag(FLAGS_allow_random_access); 15 | (void)absl::GetFlag(FLAGS_greedy_random_access); 16 | FILE* in = fopen(absl::GetFlag(FLAGS_input_path).c_str(), "r"); 17 | ZKR_ASSERT(in); 18 | 19 | fseek(in, 0, SEEK_END); 20 | size_t len = ftell(in); 21 | fseek(in, 0, SEEK_SET); 22 | 23 | std::vector data(len); 24 | ZKR_ASSERT(fread(data.data(), 1, len, in) == len); 25 | 26 | if (!zuckerli::DecodeGraph(data)) { 27 | fprintf(stderr, "Invalid graph\n"); 28 | return EXIT_FAILURE; 29 | } 30 | return EXIT_SUCCESS; 31 | } 32 | -------------------------------------------------------------------------------- /src/common_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "common.h" 15 | 16 | #include "gtest/gtest.h" 17 | 18 | namespace zuckerli { 19 | namespace { 20 | TEST(CommonDeathTest, TestAbort) { EXPECT_DEATH(ZKR_ABORT("error"), "error"); } 21 | TEST(CommonDeathTest, TestAssert) { 22 | EXPECT_DEATH(ZKR_ASSERT(0 != 0), "0 != 0"); 23 | } 24 | } // namespace 25 | } // namespace zuckerli 26 | -------------------------------------------------------------------------------- /src/compressed_graph.h: -------------------------------------------------------------------------------- 1 | #ifndef THIRD_PARTY_ZUCKERLI_SRC_COMPRESSED_GRAPH_H_ 2 | #define THIRD_PARTY_ZUCKERLI_SRC_COMPRESSED_GRAPH_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "ans.h" 10 | #include "bit_reader.h" 11 | #include "checksum.h" 12 | #include "common.h" 13 | #include "context_model.h" 14 | #include "huffman.h" 15 | #include "integer_coder.h" 16 | 17 | namespace zuckerli { 18 | 19 | class CompressedGraph { 20 | public: 21 | CompressedGraph(const std::string &file); 22 | ZKR_INLINE size_t size() { return num_nodes_; } 23 | uint32_t Degree(size_t node_id); 24 | std::vector Neighbours(size_t node_id); 25 | 26 | private: 27 | size_t num_nodes_; 28 | std::vector compressed_; 29 | std::vector node_start_indices_; 30 | HuffmanReader huff_reader_; 31 | 32 | uint32_t ReadDegreeBits(uint32_t node_id, size_t context); 33 | std::pair ReadDegreeAndRefBits( 34 | uint32_t node_id, size_t context, size_t last_reference_offset); 35 | }; 36 | 37 | } // namespace zuckerli 38 | 39 | #endif // THIRD_PARTY_ZUCKERLI_SRC_COMPRESSED_GRAPH_H_ 40 | -------------------------------------------------------------------------------- /src/common.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "common.h" 15 | #include 16 | #include 17 | #include 18 | 19 | namespace zuckerli { 20 | __attribute__((noreturn, __format__(__printf__, 3, 4))) void 21 | Abort(const char *file, int line, const char *format, ...) { 22 | // 23 | char buf[2000]; 24 | va_list args; 25 | va_start(args, format); 26 | vsnprintf(buf, sizeof(buf), format, args); 27 | va_end(args); 28 | 29 | fprintf(stderr, "Abort at %s:%d: %s\n", file, line, buf); 30 | abort(); 31 | } 32 | } // namespace zuckerli 33 | -------------------------------------------------------------------------------- /src/bit_reader.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "bit_reader.h" 15 | 16 | namespace zuckerli { 17 | BitReader::BitReader(const uint8_t *ZKR_RESTRICT data, size_t size) 18 | : next_byte_(data), end_minus_8_(data + size - 8) {} 19 | 20 | BitReader::BitReader(const uint8_t *ZKR_RESTRICT data, size_t bit_offset, 21 | size_t size) 22 | : BitReader(data + bit_offset / 8, size - bit_offset / 8) { 23 | ZKR_ASSERT(bit_offset <= size * 8); 24 | Refill(); 25 | Advance(bit_offset % 8); 26 | } 27 | 28 | void BitReader::BoundsCheckedRefill() { 29 | const uint8_t *end = end_minus_8_ + 8; 30 | for (; bits_in_buf_ < 56; bits_in_buf_ += 8) { 31 | if (next_byte_ >= end) 32 | break; 33 | buf_ |= static_cast(*next_byte_++) << bits_in_buf_; 34 | } 35 | size_t extra_bytes = (63 - bits_in_buf_) / 8; 36 | bits_in_buf_ += extra_bytes * 8; 37 | } 38 | } // namespace zuckerli 39 | -------------------------------------------------------------------------------- /src/uncompressed_graph_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "uncompressed_graph.h" 15 | 16 | #include "gtest/gtest.h" 17 | 18 | namespace zuckerli { 19 | namespace { 20 | 21 | TEST(UncompressedGraphTest, TestInvalidSignature) { 22 | EXPECT_DEATH(UncompressedGraph g( 23 | 24 | TESTDATA "/invalid_signature"), 25 | "invalid fingerprint"); 26 | } 27 | 28 | TEST(UncompressedGraphTest, TestSmallGraph) { 29 | UncompressedGraph g( 30 | TESTDATA "/small"); 31 | 32 | ASSERT_EQ(g.size(), 3); 33 | 34 | ASSERT_EQ(g.Degree(0), 2); 35 | ASSERT_EQ(g.Degree(1), 2); 36 | ASSERT_EQ(g.Degree(2), 1); 37 | 38 | EXPECT_EQ(g.Neighbours(0)[0], 0); 39 | EXPECT_EQ(g.Neighbours(0)[1], 1); 40 | 41 | EXPECT_EQ(g.Neighbours(1)[0], 1); 42 | EXPECT_EQ(g.Neighbours(1)[1], 2); 43 | 44 | EXPECT_EQ(g.Neighbours(2)[0], 0); 45 | } 46 | 47 | } // namespace 48 | } // namespace zuckerli 49 | -------------------------------------------------------------------------------- /src/bit_writer.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef ZUCKERLI_BIT_WRITER_H 15 | #define ZUCKERLI_BIT_WRITER_H 16 | #include 17 | 18 | #include 19 | 20 | namespace zuckerli { 21 | // Simple bit writer that can handle up to 56 bits per call. Inspired by JPEG 22 | // XL's bit writer. Simple implementation that can only handle little endian 23 | // systems. 24 | class BitWriter { 25 | public: 26 | static constexpr std::size_t kMaxBitsPerCall = 56; 27 | 28 | void Write(std::size_t nbits, std::size_t bits); 29 | 30 | std::size_t NumBitsWritten() { return bits_written_; } 31 | 32 | // Required before calls to write. 33 | void Reserve(std::size_t nbits); 34 | 35 | void AppendAligned(const uint8_t *ptr, std::size_t cnt); 36 | 37 | void ZeroPad() { bits_written_ = (bits_written_ + 7) / 8 * 8; } 38 | 39 | std::vector GetData() &&; 40 | 41 | private: 42 | std::vector data_; 43 | std::size_t bits_written_ = 0; 44 | }; 45 | } // namespace zuckerli 46 | 47 | #endif // ZUCKERLI_BIT_WRITER_H 48 | -------------------------------------------------------------------------------- /src/roundtrip_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "decode.h" 15 | #include "encode.h" 16 | #include "gtest/gtest.h" 17 | #include "uncompressed_graph.h" 18 | 19 | namespace zuckerli { 20 | namespace { 21 | 22 | TEST(RoundtripTest, TestSmallGraphSequential) { 23 | UncompressedGraph g( 24 | TESTDATA "/small"); 25 | 26 | size_t checksum = 0, decoder_checksum = 0; 27 | std::vector compresssed = 28 | EncodeGraph(g, /*allow_random_access=*/false, &checksum); 29 | EXPECT_TRUE(DecodeGraph(compresssed, &decoder_checksum)); 30 | EXPECT_EQ(checksum, decoder_checksum); 31 | } 32 | 33 | TEST(RoundtripTest, TestSmallGraphRandomAccess) { 34 | UncompressedGraph g( 35 | TESTDATA "/small"); 36 | size_t checksum = 0, decoder_checksum = 0; 37 | std::vector compresssed = 38 | EncodeGraph(g, /*allow_random_access=*/true, &checksum); 39 | EXPECT_TRUE(DecodeGraph(compresssed, &decoder_checksum)); 40 | EXPECT_EQ(checksum, decoder_checksum); 41 | } 42 | 43 | } // namespace 44 | } // namespace zuckerli 45 | -------------------------------------------------------------------------------- /src/bit_writer.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "bit_writer.h" 15 | 16 | #include 17 | 18 | #include "common.h" 19 | 20 | namespace zuckerli { 21 | void BitWriter::Write(size_t nbits, size_t bits) { 22 | ZKR_DASSERT(nbits >= 0); 23 | ZKR_DASSERT(bits >> nbits == 0); 24 | ZKR_DASSERT(nbits <= kMaxBitsPerCall); 25 | 26 | uint8_t *ptr = &data_[bits_written_ / 8]; 27 | size_t used_bits = bits_written_ % 8; 28 | bits <<= used_bits; 29 | bits |= *ptr; 30 | memcpy(ptr, &bits, sizeof(bits)); 31 | bits_written_ += nbits; 32 | } 33 | 34 | void BitWriter::AppendAligned(const uint8_t *ptr, size_t cnt) { 35 | ZKR_ASSERT(bits_written_ % 8 == 0); 36 | data_.resize(bits_written_ / 8); 37 | data_.insert(data_.end(), ptr, ptr + cnt); 38 | } 39 | 40 | std::vector BitWriter::GetData() && { 41 | data_.resize((bits_written_ + 7) / 8); 42 | return std::move(data_); 43 | } 44 | 45 | void BitWriter::Reserve(size_t nbits) { 46 | // Add padding to ensure memcpy does not write out of bounds. 47 | size_t required_size = (bits_written_ + nbits + 7) / 8 + sizeof(size_t); 48 | if (required_size > data_.size()) { 49 | data_.resize(required_size); 50 | } 51 | } 52 | } // namespace zuckerli 53 | -------------------------------------------------------------------------------- /src/uncompressed_graph.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "uncompressed_graph.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "common.h" 22 | 23 | namespace zuckerli { 24 | 25 | MemoryMappedFile::MemoryMappedFile(const std::string &filename) { 26 | struct stat st; 27 | int ret = stat(filename.c_str(), &st); 28 | ZKR_ASSERT(ret == 0); 29 | size_ = st.st_size; 30 | ZKR_ASSERT(size_ % sizeof(uint32_t) == 0); 31 | size_ /= sizeof(uint32_t); 32 | fd_ = open(filename.c_str(), O_RDONLY, 0); 33 | auto flags = MAP_SHARED; 34 | #ifdef __linux__ 35 | flags |= MAP_POPULATE; 36 | #endif 37 | data_ = (const uint32_t *)mmap(NULL, size_ * sizeof(uint32_t), PROT_READ, 38 | flags, fd_, 0); 39 | ZKR_ASSERT(data_ != MAP_FAILED); 40 | } 41 | 42 | MemoryMappedFile::~MemoryMappedFile() { 43 | munmap((void *)data_, size_ * sizeof(uint32_t)); 44 | close(fd_); 45 | } 46 | 47 | UncompressedGraph::UncompressedGraph(const std::string &file) : f_(file) { 48 | const uint32_t *data = f_.data(); 49 | if (kFingerprint != *(uint64_t *)data) { 50 | fprintf(stderr, "ERROR: invalid fingerprint\n"); 51 | exit(1); 52 | } 53 | N = data[2]; 54 | neigh_start_ = (uint64_t *)(data + 3); 55 | neighs_ = data + 2 * (N + 1) + 3; 56 | } 57 | 58 | } // namespace zuckerli 59 | -------------------------------------------------------------------------------- /src/huffman_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "huffman.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include "bit_reader.h" 22 | #include "integer_coder.h" 23 | 24 | namespace zuckerli { 25 | namespace { 26 | 27 | TEST(HuffmanTest, TestRoundtrip) { 28 | constexpr size_t kNumIntegers = 1 << 24; 29 | constexpr size_t kNumContexts = 128; 30 | 31 | IntegerData data; 32 | 33 | std::mt19937 rng; 34 | std::uniform_int_distribution dist( 35 | 0, std::numeric_limits::max()); 36 | std::uniform_int_distribution ctx_dist(0, kNumContexts - 1); 37 | 38 | for (size_t i = 0; i < kNumIntegers; i++) { 39 | size_t ctx = ctx_dist(rng); 40 | size_t integer = dist(rng); 41 | data.Add(ctx, integer); 42 | } 43 | 44 | BitWriter writer; 45 | std::vector unused_bits_per_ctx; 46 | HuffmanEncode(data, kNumContexts, &writer, {}, &unused_bits_per_ctx); 47 | 48 | std::vector encoded = std::move(writer).GetData(); 49 | BitReader reader(encoded.data(), encoded.size()); 50 | HuffmanReader symbol_reader; 51 | ASSERT_TRUE(symbol_reader.Init(kNumContexts, &reader)); 52 | 53 | for (size_t i = 0; i < kNumIntegers; i++) { 54 | EXPECT_EQ(IntegerCoder::Read(data.Context(i), &reader, &symbol_reader), 55 | data.Value(i)); 56 | } 57 | } 58 | 59 | } // namespace 60 | } // namespace zuckerli 61 | -------------------------------------------------------------------------------- /src/entropy_coder_common_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "bit_writer.h" 15 | #include "integer_coder.h" 16 | #include "gtest/gtest.h" 17 | #include "absl/flags/flag.h" 18 | 19 | namespace zuckerli { 20 | namespace { 21 | 22 | struct ByteCoder { 23 | size_t Read(size_t ctx, BitReader *ZKR_RESTRICT br) { 24 | return br->ReadBits(8); 25 | } 26 | }; 27 | 28 | void TestIntegerCoder(int k, int h) { 29 | absl::SetFlag(&FLAGS_log2_num_explicit, k); 30 | absl::SetFlag(&FLAGS_num_token_bits, h); 31 | for (size_t i = 0; i < (1 << 14); i++) { 32 | BitWriter writer; 33 | writer.Reserve(256); 34 | size_t token, nbits, bits; 35 | IntegerCoder::Encode(i, &token, &nbits, &bits); 36 | writer.Write(8, token); 37 | writer.Write(nbits, bits); 38 | std::vector data = std::move(writer).GetData(); 39 | BitReader reader(data.data(), data.size()); 40 | ByteCoder coder; 41 | EXPECT_EQ(i, IntegerCoder::Read(0, &reader, &coder)); 42 | } 43 | } 44 | 45 | TEST(IntegerCoderTest, Test00) { TestIntegerCoder(0, 0); } 46 | TEST(IntegerCoderTest, Test40) { TestIntegerCoder(4, 0); } 47 | TEST(IntegerCoderTest, Test41) { TestIntegerCoder(4, 1); } 48 | TEST(IntegerCoderTest, Test42) { TestIntegerCoder(4, 2); } 49 | TEST(IntegerCoderTest, Test43) { TestIntegerCoder(4, 3); } 50 | TEST(IntegerCoderTest, Test44) { TestIntegerCoder(4, 4); } 51 | } // namespace 52 | } // namespace zuckerli 53 | -------------------------------------------------------------------------------- /src/ans_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "ans.h" 16 | 17 | #include 18 | 19 | #include "bit_reader.h" 20 | #include "integer_coder.h" 21 | #include "gtest/gtest.h" 22 | 23 | namespace zuckerli { 24 | namespace { 25 | 26 | TEST(ANSTest, TestRoundtrip) { 27 | constexpr size_t kNumIntegers = 1 << 24; 28 | constexpr size_t kNumContexts = 128; 29 | 30 | IntegerData data; 31 | 32 | std::mt19937 rng; 33 | std::uniform_int_distribution dist( 34 | 0, std::numeric_limits::max()); 35 | std::uniform_int_distribution ctx_dist(0, kNumContexts - 1); 36 | 37 | for (size_t i = 0; i < kNumIntegers; i++) { 38 | size_t ctx = ctx_dist(rng); 39 | size_t integer = dist(rng); 40 | data.Add(ctx, integer); 41 | } 42 | 43 | BitWriter writer; 44 | std::vector unused_bits_per_ctx; 45 | ANSEncode(data, kNumContexts, &writer, &unused_bits_per_ctx); 46 | 47 | std::vector encoded = std::move(writer).GetData(); 48 | BitReader reader(encoded.data(), encoded.size()); 49 | ANSReader symbol_reader; 50 | ASSERT_TRUE(symbol_reader.Init(kNumContexts, &reader)); 51 | 52 | for (size_t i = 0; i < kNumIntegers; i++) { 53 | EXPECT_EQ(IntegerCoder::Read(data.Context(i), &reader, &symbol_reader), 54 | data.Value(i)); 55 | } 56 | EXPECT_TRUE(symbol_reader.CheckFinalState()); 57 | } 58 | 59 | } // namespace 60 | } // namespace zuckerli 61 | -------------------------------------------------------------------------------- /src/huffman.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef ZUCKERLI_HUFFMAN_H 15 | #define ZUCKERLI_HUFFMAN_H 16 | 17 | #include 18 | 19 | #include "bit_writer.h" 20 | #include "integer_coder.h" 21 | 22 | namespace zuckerli { 23 | 24 | static constexpr size_t kMaxHuffmanBits = 8; 25 | 26 | struct HuffmanDecoderInfo { 27 | uint8_t nbits; 28 | uint8_t symbol; 29 | }; 30 | 31 | // Encodes the given sequence of integers into a BitWriter. The context id 32 | // for each integer must be in the range [0, num_contexts). 33 | // Returns a vector of sorted indices of bits where nodes start. 34 | std::vector HuffmanEncode( 35 | const IntegerData& integers, size_t num_contexts, BitWriter* writer, 36 | const std::vector& node_degree_indices, 37 | std::vector* bits_per_ctx, 38 | std::vector* extra_bits_per_ctx = nullptr); 39 | 40 | // Class to read Huffman-encoded symbols from a stream. 41 | class HuffmanReader { 42 | public: 43 | // Decodes the specified number of distributions from the reader and creates 44 | // the corresponding decoding tables. 45 | bool Init(size_t num_contexts, BitReader* ZKR_RESTRICT br); 46 | 47 | // Decodes a single symbol from the bitstream, using distribution of index 48 | // `ctx`. 49 | size_t Read(size_t ctx, BitReader* ZKR_RESTRICT br); 50 | 51 | // For interface compatibilty with ANS reader. 52 | bool CheckFinalState() const { return true; } 53 | 54 | private: 55 | // For each context, maps the next kMaxHuffmanBits in the bitstream into a 56 | // symbol and the number of bits that should actually be consumed from the 57 | // bitstream. 58 | HuffmanDecoderInfo info_[kMaxNumContexts][1 << kMaxHuffmanBits]; 59 | }; 60 | 61 | }; // namespace zuckerli 62 | 63 | #endif // ZUCKERLI_HUFFMAN_H 64 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef ZUCKERLI_COMMON_H 15 | #define ZUCKERLI_COMMON_H 16 | 17 | #include 18 | 19 | #define ZKR_ASSERT(cond) \ 20 | do { \ 21 | if (!(cond)) { \ 22 | ::zuckerli::Abort(__FILE__, __LINE__, "Assertion failure: %s\n", #cond); \ 23 | } \ 24 | } while (0) 25 | 26 | // Enable DASSERTs in debug build, and if specifically requested. 27 | #ifndef NDEBUG 28 | #define ZKR_ENABLE_DEBUG 29 | #endif 30 | 31 | #ifdef ZKR_ENABLE_DEBUG 32 | #define ZKR_DASSERT(cond) 33 | #else 34 | #define ZKR_DASSERT(cond) ZKR_ASSERT(cond) 35 | #endif 36 | 37 | #define ZKR_ABORT(...) ::zuckerli::Abort(__FILE__, __LINE__, __VA_ARGS__) 38 | 39 | #ifdef ZKR_CRASH_ON_ERROR 40 | #define ZKR_FAILURE(...) \ 41 | ::zuckerli::Abort(__FILE__, __LINE__, __VA_ARGS__), false 42 | #else 43 | #define ZKR_FAILURE(...) false 44 | #endif 45 | 46 | #define ZKR_RESTRICT __restrict__ 47 | 48 | #define ZKR_INLINE inline __attribute__((always_inline)) 49 | 50 | #define ZKR_RETURN_IF_ERROR(cond) \ 51 | if (!(cond)) return false; 52 | 53 | #if !defined(__BYTE_ORDER__) || __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ 54 | #error "bit_writer and reader assumes a little endian system" 55 | #endif 56 | 57 | namespace zuckerli { 58 | __attribute__((noreturn, __format__(__printf__, 3, 4))) void Abort( 59 | const char *file, int line, const char *format, ...); 60 | 61 | template 62 | constexpr ZKR_INLINE T DivCeil(T a, U b) { 63 | return (a + b - 1) / b; 64 | } 65 | 66 | ZKR_INLINE int FloorLog2Nonzero(uint64_t value) { 67 | return 63 - __builtin_clzll(value); 68 | } 69 | 70 | #define ZKR_HONOR_FLAGS 0 71 | 72 | } // namespace zuckerli 73 | 74 | #endif // ZUCKERLI_COMMON_H 75 | -------------------------------------------------------------------------------- /src/bits_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | 17 | #include "bit_reader.h" 18 | #include "bit_writer.h" 19 | #include "common.h" 20 | #include "gtest/gtest.h" 21 | 22 | namespace zuckerli { 23 | namespace { 24 | constexpr size_t kTestSize = 1 << 24; 25 | TEST(BitsTest, TestWriteNumBits) { 26 | std::mt19937 rng; 27 | std::uniform_int_distribution dist(0, BitWriter::kMaxBitsPerCall); 28 | size_t total_bits = 0; 29 | BitWriter writer; 30 | writer.Reserve(kTestSize * BitWriter::kMaxBitsPerCall); 31 | for (size_t i = 0; i < kTestSize; i++) { 32 | size_t nbits = dist(rng); 33 | size_t bits = rng() & ((1 << nbits) - 1); 34 | writer.Write(nbits, bits); 35 | total_bits += nbits; 36 | } 37 | std::vector data = std::move(writer).GetData(); 38 | EXPECT_EQ(data.size(), DivCeil(total_bits, 8)); 39 | } 40 | TEST(BitsTest, TestWriteNibbles) { 41 | BitWriter writer; 42 | writer.Reserve(8); 43 | writer.Write(4, 0xf); 44 | writer.Write(4, 0xa); 45 | writer.Write(4, 0x9); 46 | writer.Write(4, 0x8); 47 | std::vector data = std::move(writer).GetData(); 48 | EXPECT_EQ(data.size(), 2); 49 | EXPECT_EQ(data[0], uint8_t(0xaf)); 50 | EXPECT_EQ(data[1], uint8_t(0x89)); 51 | } 52 | TEST(BitsTest, TestWriteRead) { 53 | std::mt19937 rng; 54 | std::uniform_int_distribution dist(0, BitWriter::kMaxBitsPerCall); 55 | std::vector> all_bits; 56 | all_bits.reserve(kTestSize); 57 | BitWriter writer; 58 | writer.Reserve(kTestSize * BitWriter::kMaxBitsPerCall); 59 | for (size_t i = 0; i < kTestSize; i++) { 60 | size_t nbits = dist(rng); 61 | size_t bits = rng() & ((1ULL << nbits) - 1); 62 | writer.Write(nbits, bits); 63 | all_bits.emplace_back(nbits, bits); 64 | } 65 | std::vector data = std::move(writer).GetData(); 66 | BitReader reader(data.data(), data.size()); 67 | for (size_t i = 0; i < kTestSize; i++) { 68 | EXPECT_EQ(reader.ReadBits(all_bits[i].first), all_bits[i].second); 69 | } 70 | } 71 | } // namespace 72 | } // namespace zuckerli 73 | -------------------------------------------------------------------------------- /src/bit_reader.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef ZUCKERLI_BIT_READER_H 15 | #define ZUCKERLI_BIT_READER_H 16 | #include 17 | #include 18 | #include 19 | 20 | #include "common.h" 21 | 22 | namespace zuckerli { 23 | 24 | // Simple bit reader (based on JPEG XL's bit reader and variant 4 of 25 | // fgiesen.wordpress.com/2018/02/20/reading-bits-in-far-too-many-ways-part-2/) 26 | // that can handle 56 bits per call. Simple implementation that only handles 27 | // little endian systems. 28 | // TODO: does not handle reads past the end of the stream (assumed to be 29 | // 0 bits). 30 | class BitReader { 31 | public: 32 | static constexpr size_t kMaxBitsPerCall = 56; 33 | 34 | BitReader(const uint8_t *ZKR_RESTRICT data, size_t size); 35 | BitReader(const uint8_t *ZKR_RESTRICT data, size_t bit_offset, size_t size); 36 | 37 | BitReader(const BitReader &other) = delete; 38 | BitReader(BitReader &&other) = delete; 39 | BitReader &operator=(const BitReader &other) = delete; 40 | BitReader &operator=(BitReader &&other) = delete; 41 | 42 | uint64_t PeekBits(size_t nbits) { return buf_ & ((1ULL << nbits) - 1); } 43 | 44 | ZKR_INLINE uint64_t ReadBits(size_t nbits) { 45 | Refill(); 46 | const uint64_t bits = PeekBits(nbits); 47 | Advance(nbits); 48 | return bits; 49 | } 50 | 51 | ZKR_INLINE void Refill() { 52 | if (next_byte_ > end_minus_8_) { 53 | BoundsCheckedRefill(); 54 | } else { 55 | uint64_t bits; 56 | memcpy(&bits, next_byte_, sizeof(bits)); 57 | buf_ |= bits << bits_in_buf_; 58 | next_byte_ += (63 - bits_in_buf_) >> 3; 59 | bits_in_buf_ |= 56; 60 | } 61 | } 62 | 63 | ZKR_INLINE void Advance(size_t nbits) { 64 | bits_in_buf_ -= nbits; 65 | buf_ >>= nbits; 66 | nbits_read_ += nbits; 67 | } 68 | 69 | ZKR_INLINE size_t NumBitsRead() { return nbits_read_; } 70 | 71 | private: 72 | uint64_t buf_{0}; 73 | size_t bits_in_buf_{0}; 74 | const uint8_t *ZKR_RESTRICT next_byte_; 75 | const uint8_t *ZKR_RESTRICT end_minus_8_; 76 | size_t nbits_read_ = 0; 77 | void BoundsCheckedRefill(); 78 | }; 79 | 80 | } // namespace zuckerli 81 | 82 | #endif // ZUCKERLI_BIT_READER_H 83 | -------------------------------------------------------------------------------- /src/context_model.h: -------------------------------------------------------------------------------- 1 | #ifndef ZUCKERLI_CONTEXT_MODEL_H 2 | #define ZUCKERLI_CONTEXT_MODEL_H 3 | #include 4 | #include 5 | 6 | #include "common.h" 7 | #include "integer_coder.h" 8 | #include "absl/flags/declare.h" 9 | #include "absl/flags/flag.h" 10 | 11 | ABSL_DECLARE_FLAG(int32_t, ref_block); 12 | 13 | namespace zuckerli { 14 | 15 | ZKR_INLINE size_t SearchNum() { 16 | #if ZKR_HONOR_FLAGS 17 | ZKR_ASSERT(absl::GetFlag(FLAGS_ref_block) <= 64); 18 | return absl::GetFlag(FLAGS_ref_block); 19 | #else 20 | return 32; 21 | #endif 22 | }; 23 | ZKR_INLINE size_t MaxNodesBackwards() { return SearchNum() + 1; }; 24 | 25 | static constexpr size_t kFirstDegreeContext = 0; 26 | static constexpr size_t kDegreeBaseContext = 1; 27 | static constexpr size_t kNumDegreeContexts = 32; 28 | static constexpr size_t kReferenceContextBase = 29 | kDegreeBaseContext + kNumDegreeContexts; 30 | static constexpr size_t kNumReferenceContexts = 64; // At most 64. 31 | static constexpr size_t kBlockCountContext = 32 | kReferenceContextBase + kNumReferenceContexts; 33 | static constexpr size_t kBlockContext = kBlockCountContext + 1; 34 | static constexpr size_t kBlockContextEven = kBlockContext + 1; 35 | static constexpr size_t kBlockContextOdd = kBlockContextEven + 1; 36 | static constexpr size_t kFirstResidualBaseContext = kBlockContextOdd + 1; 37 | static constexpr size_t kFirstResidualNumContexts = 32; 38 | static constexpr size_t kResidualBaseContext = 39 | kFirstResidualBaseContext + kFirstResidualNumContexts; 40 | static constexpr size_t kNumResidualContexts = 80; // Slightly lax bound 41 | // TODO: use number of remaining residuals as a ctx? 42 | static constexpr size_t kRleContext = 43 | kResidualBaseContext + kNumResidualContexts; 44 | 45 | ZKR_INLINE size_t DegreeContext(size_t last_residual) { 46 | uint32_t token = IntegerCoder::Token(last_residual); 47 | return kDegreeBaseContext + std::min(token, kNumDegreeContexts - 1); 48 | } 49 | 50 | ZKR_INLINE size_t ReferenceContext(size_t last_reference) { 51 | return kReferenceContextBase + last_reference; 52 | } 53 | 54 | ZKR_INLINE size_t FirstResidualContext(size_t edges_left) { 55 | uint32_t token = IntegerCoder::Token(edges_left); 56 | size_t ctx = kFirstResidualBaseContext; 57 | ctx += std::min(kFirstResidualNumContexts - 1, token); 58 | return ctx; 59 | } 60 | 61 | ZKR_INLINE size_t ResidualContext(size_t last_residual) { 62 | uint32_t token = IntegerCoder::Token(last_residual); 63 | return kResidualBaseContext + 64 | std::min(token, kNumResidualContexts - 1); 65 | } 66 | 67 | static constexpr size_t kNumContexts = kRleContext + 1; 68 | 69 | // Random access only parameters: minimum length for RLE and size of chunk of 70 | // nodes for which residuals and references are delta-coded. 71 | static constexpr size_t kDegreeReferenceChunkSize = 32; 72 | 73 | static constexpr size_t kRleMin = 3; 74 | 75 | } // namespace zuckerli 76 | 77 | #endif // ZUCKERLI_CONTEXT_MODEL_H 78 | -------------------------------------------------------------------------------- /src/traversal_main_compressed.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "compressed_graph.h" 7 | #include "absl/flags/flag.h" 8 | #include "absl/flags/parse.h" 9 | #include "uncompressed_graph.h" 10 | 11 | ABSL_FLAG(std::string, input_path, "", "Input file path."); 12 | ABSL_FLAG(bool, dfs, false, "Run DFS (as opposed to BFS)?"); 13 | ABSL_FLAG(bool, print, false, "Print node indices during traversal?"); 14 | 15 | void TimedBFS(zuckerli::CompressedGraph graph, bool print) { 16 | std::queue nodes; 17 | std::vector visited(graph.size(), false); 18 | int num_visited = 0; 19 | 20 | std::cout << "BFS..." << std::endl; 21 | auto t_start = std::chrono::high_resolution_clock::now(); 22 | for (uint32_t root = 0; root < graph.size(); root++) { 23 | if (visited[root]) continue; 24 | nodes.push(root); 25 | visited[root] = true; 26 | ++num_visited; 27 | while (!nodes.empty()) { 28 | uint32_t current_node = nodes.front(); 29 | nodes.pop(); 30 | if (print) std::cout << current_node << " "; 31 | for (uint32_t neighbour : graph.Neighbours(current_node)) { 32 | if (!visited[neighbour]) { 33 | nodes.push(neighbour); 34 | visited[neighbour] = true; 35 | ++num_visited; 36 | } 37 | } 38 | } 39 | } 40 | auto t_stop = std::chrono::high_resolution_clock::now(); 41 | if (print) std::cout << std::endl; 42 | std::cout 43 | << "Wall time elapsed: " 44 | << std::chrono::duration(t_stop - t_start).count() 45 | << " ms" << std::endl; 46 | } 47 | 48 | void TimedDFS(zuckerli::CompressedGraph graph, bool print) { 49 | std::stack nodes; 50 | std::vector visited(graph.size(), false); 51 | int num_visited = 0; 52 | 53 | std::cout << "DFS..." << std::endl; 54 | auto t_start = std::chrono::high_resolution_clock::now(); 55 | size_t count = 0; 56 | for (uint32_t root = 0; root < graph.size(); root++) { 57 | if (visited[root]) continue; 58 | count++; 59 | nodes.push(root); 60 | visited[root] = true; 61 | ++num_visited; 62 | while (!nodes.empty()) { 63 | uint32_t current_node = nodes.top(); 64 | nodes.pop(); 65 | if (print) std::cout << current_node << " "; 66 | for (uint32_t neighbour : graph.Neighbours(current_node)) { 67 | if (!visited[neighbour]) { 68 | nodes.push(neighbour); 69 | visited[neighbour] = true; 70 | ++num_visited; 71 | } 72 | } 73 | } 74 | } 75 | auto t_stop = std::chrono::high_resolution_clock::now(); 76 | if (print) std::cout << std::endl; 77 | std::cout 78 | << "Wall time elapsed: " 79 | << std::chrono::duration(t_stop - t_start).count() 80 | << " ms" << std::endl; 81 | } 82 | 83 | int main(int argc, char* argv[]) { 84 | absl::ParseCommandLine(argc, argv); 85 | zuckerli::CompressedGraph graph(absl::GetFlag(FLAGS_input_path)); 86 | std::cout << "This graph has " << graph.size() << " nodes." << std::endl; 87 | if (absl::GetFlag(FLAGS_dfs)) { 88 | TimedDFS(graph, absl::GetFlag(FLAGS_print)); 89 | } else { 90 | TimedBFS(graph, absl::GetFlag(FLAGS_print)); 91 | } 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /src/traversal_main_uncompressed.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "compressed_graph.h" 7 | #include "absl/flags/flag.h" 8 | #include "absl/flags/parse.h" 9 | #include "uncompressed_graph.h" 10 | 11 | ABSL_FLAG(std::string, input_path, "", "Input file path."); 12 | ABSL_FLAG(bool, dfs, false, "Run DFS (as opposed to BFS)?"); 13 | ABSL_FLAG(bool, print, false, "Print node indices during traversal?"); 14 | 15 | void TimedBFS(const zuckerli::UncompressedGraph& graph, bool print) { 16 | std::queue nodes; 17 | std::vector visited(graph.size(), false); 18 | int num_visited = 0; 19 | 20 | std::cout << "BFS..." << std::endl; 21 | auto t_start = std::chrono::high_resolution_clock::now(); 22 | for (uint32_t root = 0; root < graph.size(); root++) { 23 | if (visited[root]) continue; 24 | nodes.push(root); 25 | visited[root] = true; 26 | ++num_visited; 27 | while (!nodes.empty()) { 28 | uint32_t current_node = nodes.front(); 29 | nodes.pop(); 30 | if (print) std::cout << current_node << " "; 31 | for (uint32_t neighbour : graph.Neighbours(current_node)) { 32 | if (!visited[neighbour]) { 33 | nodes.push(neighbour); 34 | visited[neighbour] = true; 35 | ++num_visited; 36 | } 37 | } 38 | } 39 | } 40 | auto t_stop = std::chrono::high_resolution_clock::now(); 41 | if (print) std::cout << std::endl; 42 | std::cout 43 | << "Wall time elapsed: " 44 | << std::chrono::duration(t_stop - t_start).count() 45 | << " ms" << std::endl; 46 | } 47 | 48 | void TimedDFS(const zuckerli::UncompressedGraph& graph, bool print) { 49 | std::stack nodes; 50 | std::vector visited(graph.size(), false); 51 | int num_visited = 0; 52 | 53 | std::cout << "DFS..." << std::endl; 54 | auto t_start = std::chrono::high_resolution_clock::now(); 55 | size_t count = 0; 56 | for (uint32_t root = 0; root < graph.size(); root++) { 57 | if (visited[root]) continue; 58 | count++; 59 | nodes.push(root); 60 | visited[root] = true; 61 | ++num_visited; 62 | while (!nodes.empty()) { 63 | uint32_t current_node = nodes.top(); 64 | nodes.pop(); 65 | if (print) std::cout << current_node << " "; 66 | for (uint32_t neighbour : graph.Neighbours(current_node)) { 67 | if (!visited[neighbour]) { 68 | nodes.push(neighbour); 69 | visited[neighbour] = true; 70 | ++num_visited; 71 | } 72 | } 73 | } 74 | } 75 | auto t_stop = std::chrono::high_resolution_clock::now(); 76 | if (print) std::cout << std::endl; 77 | std::cout 78 | << "Wall time elapsed: " 79 | << std::chrono::duration(t_stop - t_start).count() 80 | << " ms" << std::endl; 81 | } 82 | 83 | int main(int argc, char* argv[]) { 84 | absl::ParseCommandLine(argc, argv); 85 | zuckerli::UncompressedGraph graph(absl::GetFlag(FLAGS_input_path)); 86 | std::cout << "This graph has " << graph.size() << " nodes." << std::endl; 87 | if (absl::GetFlag(FLAGS_dfs)) { 88 | TimedDFS(graph, absl::GetFlag(FLAGS_print)); 89 | } else { 90 | TimedBFS(graph, absl::GetFlag(FLAGS_print)); 91 | } 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /src/uncompressed_graph.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef ZUCKERLI_UNCOMPRESSED_GRAPH_H 15 | #define ZUCKERLI_UNCOMPRESSED_GRAPH_H 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | #include "common.h" 23 | 24 | namespace zuckerli { 25 | 26 | template 27 | class span { 28 | public: 29 | using iterator = const T *; 30 | span(const T *data, size_t size) : data_(data), size_(size) {} 31 | iterator begin() const { return data_; } 32 | iterator end() const { return data_ + size_; } 33 | const T &operator[](size_t pos) const { return data_[pos]; } 34 | ZKR_INLINE T &at(size_t pos) { 35 | ZKR_DASSERT(pos < size_); 36 | return data_[pos]; 37 | } 38 | ZKR_INLINE const T &at(size_t pos) const { 39 | ZKR_DASSERT(pos < size_); 40 | return data_[pos]; 41 | } 42 | ZKR_INLINE size_t size() const { return size_; } 43 | 44 | private: 45 | const T *data_; 46 | size_t size_; 47 | }; 48 | 49 | class MemoryMappedFile { 50 | public: 51 | MemoryMappedFile(const std::string &filename); 52 | ~MemoryMappedFile(); 53 | MemoryMappedFile(const MemoryMappedFile &) = delete; 54 | void operator=(const MemoryMappedFile &) = delete; 55 | MemoryMappedFile(MemoryMappedFile &&) = default; 56 | MemoryMappedFile &operator=(MemoryMappedFile &&) = default; 57 | ZKR_INLINE const uint32_t *data() const { return data_; } 58 | ZKR_INLINE size_t size() const { return size_; } 59 | 60 | private: 61 | size_t size_; 62 | const uint32_t *ZKR_RESTRICT data_; 63 | int fd_; 64 | }; 65 | 66 | // Simple on-disk representation of a graph that can directly mapped into memory 67 | // (allowing reduced memory usage). 68 | // Format description: 69 | // - 8 bytes of fingerprint 70 | // - 4 bytes to represent the number of nodes N 71 | // - N+1 8-byte integers that represent the index of the first edge of the i-th 72 | // adjacency list. The last of these integers is the total number of edges, M. 73 | // - M 4-byte integers that represent the destination node of each graph edge. 74 | class UncompressedGraph { 75 | public: 76 | // Fingerprint of the simple uncompressed graph format: number of bytes to 77 | // represent the number of edges followed by number of bytes to represent the 78 | // number of nodes. 79 | static constexpr uint64_t kFingerprint = 80 | (sizeof(uint64_t) << 4) | sizeof(uint32_t); 81 | UncompressedGraph(const std::string &file); 82 | ZKR_INLINE uint32_t size() const { return N; } 83 | ZKR_INLINE uint32_t Degree(size_t i) const { 84 | ZKR_DASSERT(i < size()); 85 | return uint32_t(neigh_start_[i + 1] - neigh_start_[i]); 86 | } 87 | ZKR_INLINE span Neighbours(size_t i) const { 88 | return span(neighs_ + neigh_start_[i], Degree(i)); 89 | } 90 | 91 | private: 92 | MemoryMappedFile f_; 93 | uint32_t N; 94 | const uint64_t *ZKR_RESTRICT neigh_start_; 95 | const uint32_t *ZKR_RESTRICT neighs_; 96 | }; 97 | 98 | } // namespace zuckerli 99 | #endif // ZUCKERLI_UNCOMPRESSED_GRAPH_H 100 | -------------------------------------------------------------------------------- /src/ans.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // Copyright 2019 the JPEG XL Project 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | #ifndef ZUCKERLI_ANS_H 16 | #define ZUCKERLI_ANS_H 17 | 18 | #include "bit_writer.h" 19 | #include "integer_coder.h" 20 | 21 | namespace zuckerli { 22 | 23 | // ANS implementation adapted from JPEG XL's. 24 | 25 | static constexpr size_t kANSNumBits = 12; 26 | static constexpr size_t kANSSignature = 0x13 << 16; 27 | 28 | // An alias table implements a mapping from the [0, 1<= cutoff) side decremented by cutoff. 55 | #pragma pack(push, 1) 56 | struct Entry { 57 | uint8_t cutoff; // < kEntrySizeMinus1 when used by ANS. 58 | uint8_t right_value; // < alphabet size. 59 | uint16_t freq0; 60 | 61 | // Only used if `greater` (see Lookup) 62 | uint16_t offsets1; // <= ANS_TAB_SIZE 63 | uint16_t freq1_xor_freq0; // for branchless ternary in Lookup 64 | }; 65 | #pragma pack(pop) 66 | 67 | // Dividing `value` by `entry_size` determines `i`, the entry which is 68 | // responsible for the input. If the remainder is below `cutoff`, then the 69 | // mapped symbol is `i`; since `offsets[0]` stores the number of occurences of 70 | // `i` "before" the start of this entry, the offset of the input will be 71 | // `offsets[0] + remainder`. If the remainder is above cutoff, the mapped 72 | // symbol is `right_value`; since `offsets[1]` stores the number of occurences 73 | // of `right_value` "before" this entry, minus the `cutoff` value, the input 74 | // offset is then `remainder + offsets[1]`. 75 | static ZKR_INLINE Symbol Lookup(const Entry* ZKR_RESTRICT table, 76 | size_t value); 77 | }; 78 | 79 | // Encodes the given sequence of integers into a BitWriter. The context id 80 | // for each integer must be in the range [0, num_contexts). 81 | void ANSEncode(const IntegerData& integers, size_t num_contexts, 82 | BitWriter* writer, std::vector* bits_per_ctx); 83 | 84 | // Class to read ANS-encoded symbols from a stream. 85 | class ANSReader { 86 | public: 87 | // Decodes the specified number of distributions from the reader and creates 88 | // the corresponding alias tables. 89 | bool Init(size_t num_contexts, BitReader* ZKR_RESTRICT br); 90 | 91 | // Decodes a single symbol from the bitstream, using distribution of index 92 | // `ctx`. 93 | size_t Read(size_t ctx, BitReader* ZKR_RESTRICT br); 94 | 95 | // Checks that the final state has its expected value. To be called after 96 | // decoding all the symbols. 97 | bool CheckFinalState() const { return state_ == kANSSignature; } 98 | 99 | private: 100 | // Alias tables for decoding symbols from each context. 101 | AliasTable::Entry entries_[kMaxNumContexts][kNumSymbols]; 102 | uint32_t state_ = kANSSignature; 103 | }; 104 | 105 | }; // namespace zuckerli 106 | 107 | #endif // ZUCKERLI_ANS_H 108 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | # Set compilers to default to Clang is not set. 4 | if ("$ENV{CC}" STREQUAL "") 5 | set(ENV{CC} clang) 6 | endif() 7 | if ("$ENV{CXX}" STREQUAL "") 8 | set(ENV{CXX} clang++) 9 | endif() 10 | 11 | 12 | project(zuckerli) 13 | 14 | set(CMAKE_CXX_STANDARD 14) 15 | set(CMAKE_CXX_EXTENSIONS OFF) 16 | set(CMAKE_CXX_STANDARD_REQUIRED True) 17 | 18 | add_compile_options(-fcolor-diagnostics -Wall) 19 | 20 | find_package(Threads REQUIRED) 21 | 22 | add_subdirectory(abseil-cpp) 23 | 24 | include(CheckIPOSupported) 25 | check_ipo_supported(RESULT supported OUTPUT error) 26 | if( supported ) 27 | message(STATUS "IPO / LTO enabled") 28 | set_property(GLOBAL PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) 29 | else() 30 | message(STATUS "IPO / LTO not supported: <${error}>") 31 | endif() 32 | 33 | enable_testing() 34 | include(CTest) 35 | cmake_policy(SET CMP0057 NEW) # https://gitlab.kitware.com/cmake/cmake/issues/18198 36 | include(GoogleTest) 37 | 38 | add_library( 39 | common 40 | src/common.cc 41 | src/flags.cc 42 | src/common.h 43 | ) 44 | 45 | target_link_libraries(common absl::flags absl::flags_parse) 46 | 47 | add_executable(common_test src/common_test.cc) 48 | target_link_libraries(common_test common gmock gtest_main gtest Threads::Threads) 49 | gtest_discover_tests(common_test) 50 | 51 | add_library( 52 | bit_reader 53 | src/bit_reader.cc 54 | src/bit_reader.h 55 | ) 56 | target_link_libraries(bit_reader common) 57 | 58 | add_library( 59 | bit_writer 60 | src/bit_writer.cc 61 | src/bit_writer.h 62 | ) 63 | target_link_libraries(bit_writer common) 64 | 65 | add_executable(bits_test src/bits_test.cc) 66 | target_link_libraries(bits_test bit_writer bit_reader gmock gtest_main gtest Threads::Threads) 67 | gtest_discover_tests(bits_test) 68 | 69 | # A library cannot contain just a headerfile. 70 | #add_library( 71 | # entropy_coder_common 72 | # src/integer_coder.h 73 | #) 74 | add_library(entropy_coder_common INTERFACE) 75 | target_include_directories(entropy_coder_common INTERFACE 76 | $ 77 | $/src) 78 | 79 | target_link_libraries(entropy_coder_common INTERFACE bit_reader bit_writer) 80 | 81 | add_executable(entropy_coder_common_test src/entropy_coder_common_test.cc) 82 | target_link_libraries(entropy_coder_common_test entropy_coder_common gmock gtest_main gtest Threads::Threads) 83 | gtest_discover_tests(entropy_coder_common_test) 84 | 85 | add_library( 86 | huffman 87 | src/huffman.cc 88 | src/huffman.h 89 | ) 90 | target_link_libraries(huffman entropy_coder_common) 91 | 92 | add_executable(huffman_test src/huffman_test.cc) 93 | target_link_libraries(huffman_test huffman gmock gtest_main gtest Threads::Threads) 94 | gtest_discover_tests(huffman_test) 95 | 96 | add_library( 97 | ans 98 | src/ans.cc 99 | src/ans.h 100 | ) 101 | target_link_libraries(ans entropy_coder_common) 102 | 103 | add_executable(ans_test src/ans_test.cc) 104 | target_link_libraries(ans_test ans gmock gtest_main gtest Threads::Threads) 105 | gtest_discover_tests(ans_test) 106 | 107 | add_library( 108 | uncompressed_graph 109 | src/uncompressed_graph.cc 110 | src/uncompressed_graph.h 111 | ) 112 | target_link_libraries(uncompressed_graph entropy_coder_common) 113 | 114 | add_executable(uncompressed_graph_test src/uncompressed_graph_test.cc) 115 | target_link_libraries(uncompressed_graph_test uncompressed_graph gmock gtest_main gtest Threads::Threads) 116 | gtest_discover_tests(uncompressed_graph_test) 117 | 118 | target_compile_definitions(uncompressed_graph_test PRIVATE 119 | -DTESTDATA="${CMAKE_CURRENT_SOURCE_DIR}/testdata") 120 | 121 | add_executable(traversal_main_uncompressed src/traversal_main_uncompressed.cc) 122 | target_link_libraries(traversal_main_uncompressed uncompressed_graph Threads::Threads) 123 | 124 | add_library(encode src/encode.h src/encode.cc src/context_model.h src/checksum.h) 125 | target_link_libraries(encode ans huffman uncompressed_graph) 126 | 127 | 128 | # A library cannot contain just headerfiles. 129 | #add_library(decode src/decode.h src/context_model.h src/checksum.h) 130 | 131 | add_library(decode INTERFACE) 132 | target_include_directories(decode INTERFACE 133 | $ 134 | $/src) 135 | 136 | target_link_libraries(decode INTERFACE ans huffman) 137 | 138 | 139 | add_library( 140 | compressed_graph 141 | src/compressed_graph.cc 142 | src/compressed_graph.h 143 | ) 144 | target_link_libraries(compressed_graph decode) 145 | 146 | add_executable(traversal_main_compressed src/traversal_main_compressed.cc) 147 | target_link_libraries(traversal_main_compressed compressed_graph Threads::Threads) 148 | 149 | 150 | add_executable(roundtrip_test src/roundtrip_test.cc) 151 | target_link_libraries(roundtrip_test encode decode uncompressed_graph gmock gtest_main gtest Threads::Threads) 152 | 153 | target_compile_definitions(roundtrip_test PRIVATE 154 | -DTESTDATA="${CMAKE_CURRENT_SOURCE_DIR}/testdata") 155 | 156 | add_executable(encoder src/encode_main.cc) 157 | target_link_libraries(encoder encode) 158 | 159 | add_executable(decoder src/decode_main.cc) 160 | target_link_libraries(decoder decode) 161 | -------------------------------------------------------------------------------- /src/integer_coder.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef ZUCKERLI_INTEGER_CODER_H 15 | #define ZUCKERLI_INTEGER_CODER_H 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "bit_reader.h" 22 | #include "common.h" 23 | #include "absl/flags/declare.h" 24 | #include "absl/flags/flag.h" 25 | 26 | ABSL_DECLARE_FLAG(int32_t, log2_num_explicit); 27 | ABSL_DECLARE_FLAG(int32_t, num_token_bits); 28 | 29 | namespace zuckerli { 30 | 31 | // Only entropy-coded symbols smaller than this value are supported. 32 | static constexpr size_t kLogNumSymbols = 8; 33 | static constexpr size_t kNumSymbols = 1 << kLogNumSymbols; 34 | 35 | // Only context ids smaller than this value are supported. 36 | static constexpr size_t kMaxNumContexts = 256; 37 | 38 | // Variable integer encoding scheme that puts bits either in an entropy-coded 39 | // symbol or as raw bits, depending on the specified configuration. 40 | // TODO: The behavior of IntegerCoder<0, 0> is a bit weird - both 0 41 | // and 1 get their own symbol. 42 | class IntegerCoder { 43 | public: 44 | static ZKR_INLINE size_t Log2NumExplicit() { 45 | #if ZKR_HONOR_FLAGS 46 | return absl::GetFlag(FLAGS_log2_num_explicit); 47 | #else 48 | return 4; 49 | #endif 50 | } 51 | static ZKR_INLINE size_t NumTokenMSB() { 52 | #if ZKR_HONOR_FLAGS 53 | return absl::GetFlag(FLAGS_num_token_bits); 54 | #else 55 | return 2; 56 | #endif 57 | } 58 | static ZKR_INLINE size_t NumTokenLSB() { 59 | #if ZKR_HONOR_FLAGS 60 | return absl::GetFlag(FLAGS_num_token_lsb); 61 | #else 62 | return 1; 63 | #endif 64 | } 65 | static ZKR_INLINE void Encode(uint64_t value, size_t *ZKR_RESTRICT token, 66 | size_t *ZKR_RESTRICT nbits, 67 | size_t *ZKR_RESTRICT bits) { 68 | uint32_t split_exponent = Log2NumExplicit(); 69 | uint32_t split_token = 1 << split_exponent; 70 | uint32_t msb_in_token = NumTokenMSB(); 71 | uint32_t lsb_in_token = NumTokenLSB(); 72 | ZKR_ASSERT(split_exponent >= lsb_in_token + msb_in_token); 73 | if (value < split_token) { 74 | *token = value; 75 | *nbits = 0; 76 | *bits = 0; 77 | } else { 78 | uint32_t n = FloorLog2Nonzero(value); 79 | uint32_t m = value - (1 << n); 80 | *token = split_token + 81 | ((n - split_exponent) << (msb_in_token + lsb_in_token)) + 82 | ((m >> (n - msb_in_token)) << lsb_in_token) + 83 | (m & ((1 << lsb_in_token) - 1)); 84 | *nbits = n - msb_in_token - lsb_in_token; 85 | *bits = (value >> lsb_in_token) & ((1 << *nbits) - 1); 86 | } 87 | } 88 | template 89 | static ZKR_INLINE size_t Read(size_t ctx, BitReader *ZKR_RESTRICT reader, 90 | EntropyCoder *ZKR_RESTRICT entropy_coder) { 91 | uint32_t split_exponent = Log2NumExplicit(); 92 | uint32_t split_token = 1 << split_exponent; 93 | uint32_t msb_in_token = NumTokenMSB(); 94 | uint32_t lsb_in_token = NumTokenLSB(); 95 | reader->Refill(); 96 | size_t token = entropy_coder->Read(ctx, reader); 97 | if (token < split_token) return token; 98 | uint32_t nbits = split_exponent - (msb_in_token + lsb_in_token) + 99 | ((token - split_token) >> (msb_in_token + lsb_in_token)); 100 | uint32_t low = token & ((1 << lsb_in_token) - 1); 101 | token >>= lsb_in_token; 102 | const size_t bits = reader->ReadBits(nbits); 103 | size_t ret = (((((1 << msb_in_token) | (token & ((1 << msb_in_token) - 1))) 104 | << nbits) | 105 | bits) 106 | << lsb_in_token) | 107 | low; 108 | return ret; 109 | } 110 | // sym_cost is such that position `ctx*kNumSymbols+token` holds the cost of 111 | // encoding `token` in the context `ctx`. 112 | static ZKR_INLINE float Cost(size_t ctx, uint64_t value, 113 | const float *ZKR_RESTRICT sym_cost) { 114 | size_t token, nbits, bits; 115 | Encode(value, &token, &nbits, &bits); 116 | ZKR_DASSERT(token < kNumSymbols); 117 | return sym_cost[ctx * kNumSymbols + token] + nbits; 118 | } 119 | static ZKR_INLINE size_t Token(uint64_t value) { 120 | size_t token, nbits, bits; 121 | Encode(value, &token, &nbits, &bits); 122 | return token; 123 | } 124 | }; 125 | 126 | class IntegerData { 127 | public: 128 | size_t Size() { 129 | ZKR_ASSERT(ctxs_.size() == values_.size()); 130 | return values_.size(); 131 | } 132 | void Add(uint32_t ctx, uint32_t val) { 133 | ZKR_DASSERT(ctx < kMaxNumContexts); 134 | ctxs_.push_back(ctx); 135 | values_.push_back(val); 136 | } 137 | void RemoveLast() { 138 | ZKR_DASSERT(!ctxs_.empty()); 139 | ctxs_.pop_back(); 140 | values_.pop_back(); 141 | } 142 | 143 | void TotalCost(const uint8_t *ZKR_RESTRICT ctx_group, 144 | const float *ZKR_RESTRICT sym_cost, 145 | float *ZKR_RESTRICT group_cost) { 146 | for (size_t i = 0; i < values_.size(); i++) { 147 | group_cost[ctx_group[ctxs_[i]]] = 0; 148 | } 149 | for (size_t i = 0; i < values_.size(); i++) { 150 | group_cost[ctx_group[ctxs_[i]]] += 151 | IntegerCoder::Cost(ctxs_[i], values_[i], sym_cost); 152 | } 153 | } 154 | 155 | template 156 | void ForEach(const CB &cb) const { 157 | for (size_t i = 0; i < values_.size(); i++) { 158 | size_t token, nbits, bits; 159 | IntegerCoder::Encode(values_[i], &token, &nbits, &bits); 160 | cb(ctxs_[i], token, nbits, bits, i); 161 | } 162 | } 163 | 164 | template 165 | void ForEachReversed(const CB &cb) const { 166 | for (size_t i = values_.size(); i > 0; i--) { 167 | size_t token, nbits, bits; 168 | IntegerCoder::Encode(values_[i - 1], &token, &nbits, &bits); 169 | cb(ctxs_[i - 1], token, nbits, bits, i - 1); 170 | } 171 | } 172 | 173 | void Histograms(std::vector> *histo) const { 174 | ForEach([histo](size_t ctx, size_t token, size_t nbits, size_t bits, 175 | size_t idx) { 176 | ZKR_ASSERT(token < kNumSymbols); 177 | if (histo->size() <= ctx) { 178 | histo->resize(ctx + 1); 179 | } 180 | (*histo)[ctx].resize(kNumSymbols); 181 | (*histo)[ctx][token]++; 182 | }); 183 | } 184 | 185 | uint32_t Context(size_t i) const { return ctxs_[i]; } 186 | uint32_t Value(size_t i) const { return values_[i]; } 187 | 188 | private: 189 | std::vector values_; 190 | std::vector ctxs_; 191 | }; 192 | 193 | ZKR_INLINE uint64_t PackSigned(int64_t s) { return s < 0 ? 2 * -s - 1 : 2 * s; } 194 | 195 | ZKR_INLINE int64_t UnpackSigned(uint64_t s) { 196 | return s & 1 ? -((s + 1) >> 1) : s >> 1; 197 | } 198 | 199 | } // namespace zuckerli 200 | 201 | #endif // ZUCKERLI_INTEGER_CODER_H 202 | -------------------------------------------------------------------------------- /src/huffman.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "huffman.h" 16 | 17 | #include 18 | 19 | #include "bit_reader.h" 20 | #include "common.h" 21 | #include "integer_coder.h" 22 | 23 | namespace zuckerli { 24 | namespace { 25 | struct HuffmanSymbolInfo { 26 | uint8_t present; 27 | uint8_t nbits; 28 | uint8_t bits; 29 | }; 30 | 31 | // Reverses bit order. 32 | static ZKR_INLINE uint8_t FlipByte(const uint8_t x) { 33 | static constexpr uint8_t kNibbleLut[16] = { 34 | 0b0000, 0b1000, 0b0100, 0b1100, 0b0010, 0b1010, 0b0110, 0b1110, 35 | 0b0001, 0b1001, 0b0101, 0b1101, 0b0011, 0b1011, 0b0111, 0b1111, 36 | }; 37 | return (kNibbleLut[x & 0xF] << 4) | kNibbleLut[x >> 4]; 38 | } 39 | 40 | // Very simple encoding: number of symbols (8 bits) followed by, for each 41 | // symbol, 1 bit for presence/absence, and 3 bits for symbol length if present. 42 | // TODO: short encoding for empty ctxs, RLE for missing symbols. 43 | void EncodeSymbolNBits(const HuffmanSymbolInfo* ZKR_RESTRICT info, 44 | BitWriter* ZKR_RESTRICT writer) { 45 | size_t ms = 0; 46 | for (size_t i = 0; i < kNumSymbols; i++) { 47 | if (info[i].present) { 48 | ms = i; 49 | } 50 | } 51 | writer->Write(8, ms); 52 | for (size_t i = 0; i < ms + 1; i++) { 53 | if (info[i].present) { 54 | writer->Write(1, 1); 55 | writer->Write(3, info[i].nbits - 1); 56 | } else { 57 | writer->Write(1, 0); 58 | } 59 | } 60 | } 61 | 62 | void DecodeSymbolNBits(HuffmanSymbolInfo* ZKR_RESTRICT info, 63 | BitReader* ZKR_RESTRICT reader) { 64 | size_t ms = reader->ReadBits(8); 65 | for (size_t i = 0; i < ms + 1; i++) { 66 | info[i].present = reader->ReadBits(1); 67 | if (info[i].present) { 68 | info[i].nbits = reader->ReadBits(3) + 1; 69 | } 70 | } 71 | for (size_t i = ms + 1; i < kNumSymbols; i++) { 72 | info[i].present = 0; 73 | } 74 | } 75 | 76 | // For a given array of HuffmanSymbolInfo, where only the `present` and `nbits` 77 | // fields are set, fill up the `bits` field by building a Canonical Huffman code 78 | // (https://en.wikipedia.org/wiki/Canonical_Huffman_code). 79 | bool ComputeSymbolBits(HuffmanSymbolInfo* ZKR_RESTRICT info) { 80 | std::pair syms[kNumSymbols]; 81 | size_t present_symbols = 0; 82 | for (size_t i = 0; i < kNumSymbols; i++) { 83 | if (info[i].present == 0) continue; 84 | syms[present_symbols++] = {info[i].nbits, static_cast(i)}; 85 | } 86 | std::sort(syms, syms + present_symbols); 87 | size_t x = 0; 88 | for (size_t s = 0; s < present_symbols; s++) { 89 | info[syms[s].second].bits = 90 | FlipByte(x) >> (kMaxHuffmanBits - info[syms[s].second].nbits); 91 | x++; 92 | if (s + 1 != present_symbols) { 93 | x <<= syms[s + 1].first - syms[s].first; 94 | } 95 | } 96 | return true; 97 | } 98 | 99 | // Computes the lookup table from bitstream bits to decoded symbol for the 100 | // decoder. 101 | bool ComputeDecoderTable(const HuffmanSymbolInfo* sym_info, 102 | HuffmanDecoderInfo* decoder_info) { 103 | size_t cnt = 0; 104 | size_t s = 0; 105 | for (size_t sym = 0; sym < kNumSymbols; sym++) { 106 | if (sym_info[sym].present == 0) continue; 107 | cnt++; 108 | s = sym; 109 | } 110 | if (cnt <= 1) { 111 | for (size_t i = 0; i < (1 << kMaxHuffmanBits); i++) { 112 | decoder_info[i].nbits = sym_info[s].nbits; 113 | decoder_info[i].symbol = s; 114 | } 115 | return true; 116 | } 117 | for (size_t i = 0; i < (1 << kMaxHuffmanBits); i++) { 118 | size_t s = kNumSymbols; 119 | for (size_t sym = 0; sym < kNumSymbols; sym++) { 120 | if (sym_info[sym].present == 0) continue; 121 | if ((i & ((1U << sym_info[sym].nbits) - 1)) == sym_info[sym].bits) { 122 | s = sym; 123 | break; 124 | } 125 | } 126 | if (s == kNumSymbols) return ZKR_FAILURE("Invalid table"); 127 | decoder_info[i].nbits = sym_info[s].nbits; 128 | decoder_info[i].symbol = s; 129 | } 130 | return true; 131 | } 132 | 133 | // Compute the optimal number of bits for each symbol given the input 134 | // distribution. Uses a (quadratic version) of the package-merge/coin-collector 135 | // algorithm. 136 | void ComputeSymbolNumBits(const std::vector& histogram, 137 | HuffmanSymbolInfo* ZKR_RESTRICT info) { 138 | // Mark the present/missing symbols. 139 | size_t nzsym = 0; 140 | for (size_t i = 0; i < histogram.size(); i++) { 141 | if (histogram[i] == 0) continue; 142 | info[i].present = 1; 143 | nzsym++; 144 | } 145 | if (nzsym <= 1) { 146 | for (size_t i = 0; i < kNumSymbols; i++) { 147 | if (info[i].present) { 148 | info[i].nbits = 1; 149 | } 150 | } 151 | return; 152 | } 153 | 154 | // Create a list of symbols for any given cost. 155 | std::vector>> bags[kMaxHuffmanBits]; 156 | for (size_t i = 0; i < kMaxHuffmanBits; i++) { 157 | for (size_t s = 0; s < kNumSymbols; s++) { 158 | if (info[s].present == 0) continue; 159 | std::vector sym(1, s); 160 | bags[i].emplace_back(histogram[s], sym); 161 | } 162 | } 163 | 164 | // Pair up symbols (or groups of symbols) of a given bit-length to create 165 | // symbols of the following bit-length, creating pairs by merging (groups of) 166 | // symbols consecutively in increasing order of cost. 167 | for (size_t i = 0; i < kMaxHuffmanBits - 1; i++) { 168 | std::sort(bags[i].begin(), bags[i].end()); 169 | for (size_t j = 0; j + 1 < bags[i].size(); j += 2) { 170 | size_t nf = bags[i][j].first + bags[i][j + 1].first; 171 | std::vector nsym = std::move(bags[i][j].second); 172 | nsym.insert(nsym.end(), bags[i][j + 1].second.begin(), 173 | bags[i][j + 1].second.end()); 174 | bags[i + 1].emplace_back(nf, std::move(nsym)); 175 | } 176 | } 177 | std::sort(bags[kMaxHuffmanBits - 1].begin(), bags[kMaxHuffmanBits - 1].end()); 178 | 179 | // In the groups of symbols for the highest bit length we need to select the 180 | // last 2*num_symbols-2 groups, and assign to each symbol one bit of cost for 181 | // each of its occurrences in these groups. 182 | for (size_t i = 0; i < 2 * nzsym - 2; i++) { 183 | const auto& b = bags[kMaxHuffmanBits - 1][i]; 184 | for (const uint8_t x : b.second) { 185 | info[x].nbits++; 186 | } 187 | } 188 | 189 | // In a properly-constructed set of lengths for a set of symbols, the sum 190 | // across the symbols of 2^-sym_length equals 1. 191 | size_t cost_check = 0; 192 | for (size_t i = 0; i < kNumSymbols; i++) { 193 | if (info[i].present == 0) continue; 194 | cost_check += 1U << (kMaxHuffmanBits - info[i].nbits); 195 | } 196 | ZKR_ASSERT(cost_check == 1 << kMaxHuffmanBits); 197 | } 198 | 199 | } // namespace 200 | 201 | std::vector HuffmanEncode( 202 | const IntegerData& integers, size_t num_contexts, BitWriter* writer, 203 | const std::vector& node_degree_indices, 204 | std::vector* bits_per_ctx, 205 | std::vector* extra_bits_per_ctx) { 206 | std::vector node_degree_bit_pos; 207 | node_degree_bit_pos.reserve(node_degree_indices.size()); 208 | size_t current_node = 0; 209 | 210 | // Compute histograms. 211 | std::vector> histograms; 212 | histograms.resize(num_contexts); 213 | integers.Histograms(&histograms); 214 | 215 | writer->Reserve(num_contexts * kNumSymbols * 4); 216 | bits_per_ctx->resize(num_contexts); 217 | if (extra_bits_per_ctx) { 218 | extra_bits_per_ctx->resize(num_contexts); 219 | } 220 | 221 | // Compute and encode symbol length and bits for each symbol. 222 | ZKR_ASSERT(histograms.size() == num_contexts); 223 | HuffmanSymbolInfo info[kMaxNumContexts][kNumSymbols] = {}; 224 | for (size_t i = 0; i < histograms.size(); i++) { 225 | ComputeSymbolNumBits(histograms[i], &info[i][0]); 226 | ZKR_ASSERT(ComputeSymbolBits(&info[i][0])); 227 | EncodeSymbolNBits(&info[i][0], writer); 228 | } 229 | 230 | // Pre-compute the number of bits needed. 231 | size_t nbits_histo = writer->NumBitsWritten(); 232 | size_t total_nbits = 0; 233 | integers.ForEach([&](size_t ctx, size_t token, size_t nextrabits, 234 | size_t extrabits, size_t i) { 235 | ZKR_ASSERT(token < kNumSymbols); 236 | if (current_node < node_degree_indices.size() && 237 | i == node_degree_indices[current_node]) { 238 | node_degree_bit_pos.push_back(total_nbits + nbits_histo); 239 | ++current_node; 240 | } 241 | total_nbits += info[ctx][token].nbits; 242 | total_nbits += nextrabits; 243 | }); 244 | 245 | writer->Reserve(total_nbits); 246 | 247 | // Encode the actual data. 248 | integers.ForEach([&](size_t ctx, size_t token, size_t nextrabits, 249 | size_t extrabits, size_t i) { 250 | writer->Write(info[ctx][token].nbits, info[ctx][token].bits); 251 | writer->Write(nextrabits, extrabits); 252 | (*bits_per_ctx)[ctx] += nextrabits + info[ctx][token].nbits; 253 | if (extra_bits_per_ctx) { 254 | (*extra_bits_per_ctx)[ctx] += nextrabits; 255 | } 256 | }); 257 | 258 | return node_degree_bit_pos; 259 | } 260 | 261 | bool HuffmanReader::Init(size_t num_contexts, BitReader* ZKR_RESTRICT br) { 262 | ZKR_ASSERT(num_contexts <= kMaxNumContexts); 263 | for (size_t i = 0; i < num_contexts; i++) { 264 | HuffmanSymbolInfo symbol_info[kNumSymbols] = {}; 265 | DecodeSymbolNBits(&symbol_info[0], br); 266 | ZKR_RETURN_IF_ERROR(ComputeSymbolBits(&symbol_info[0])); 267 | ZKR_RETURN_IF_ERROR(ComputeDecoderTable(&symbol_info[0], &info_[i][0])); 268 | } 269 | return true; 270 | } 271 | 272 | size_t HuffmanReader::Read(size_t ctx, BitReader* ZKR_RESTRICT br) { 273 | const uint32_t bits = br->PeekBits(kMaxHuffmanBits); 274 | br->Advance(info_[ctx][bits].nbits); 275 | return info_[ctx][bits].symbol; 276 | } 277 | } // namespace zuckerli 278 | -------------------------------------------------------------------------------- /src/compressed_graph.cc: -------------------------------------------------------------------------------- 1 | #include "compressed_graph.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "common.h" 9 | #include "context_model.h" 10 | #include "decode.h" 11 | #include "integer_coder.h" 12 | 13 | namespace zuckerli { 14 | 15 | CompressedGraph::CompressedGraph(const std::string& file) { 16 | FILE* in = std::fopen(file.c_str(), "r"); 17 | ZKR_ASSERT(in); 18 | 19 | fseek(in, 0, SEEK_END); 20 | size_t len = ftell(in); 21 | fseek(in, 0, SEEK_SET); 22 | 23 | compressed_.resize(len); 24 | ZKR_ASSERT(fread(compressed_.data(), 1, len, in) == len); 25 | if (compressed_.empty()) ZKR_ABORT("Empty file"); 26 | 27 | BitReader reader(compressed_.data(), compressed_.size()); 28 | num_nodes_ = reader.ReadBits(48); 29 | bool allow_random_access = reader.ReadBits(1); 30 | if (!allow_random_access) { 31 | ZKR_ABORT("No random access allowed"); 32 | } 33 | 34 | huff_reader_.Init(kNumContexts, &reader); 35 | 36 | node_start_indices_.clear(); 37 | node_start_indices_.reserve(num_nodes_); 38 | if (!DecodeGraph(compressed_, nullptr, &node_start_indices_)) { 39 | ZKR_ABORT("Invalid graph"); 40 | } 41 | } 42 | 43 | uint32_t CompressedGraph::ReadDegreeBits(uint32_t node_id, size_t context) { 44 | BitReader bit_reader(compressed_.data() + node_start_indices_[node_id] / 8, 45 | compressed_.size()); 46 | bit_reader.ReadBits(node_start_indices_[node_id] % 8); 47 | return zuckerli::IntegerCoder::Read(context, &bit_reader, &huff_reader_); 48 | } 49 | 50 | std::pair CompressedGraph::ReadDegreeAndRefBits( 51 | uint32_t node_id, size_t context, size_t last_reference_offset) { 52 | BitReader bit_reader(compressed_.data() + node_start_indices_[node_id] / 8, 53 | compressed_.size()); 54 | bit_reader.ReadBits(node_start_indices_[node_id] % 8); 55 | uint32_t degree = 56 | zuckerli::IntegerCoder::Read(context, &bit_reader, &huff_reader_); 57 | // If this is not the first node, read the offset of the list to be used as 58 | // a reference. 59 | size_t reference_offset = 0; 60 | if (node_id != 0) { 61 | reference_offset = IntegerCoder::Read( 62 | ReferenceContext(last_reference_offset), &bit_reader, &huff_reader_); 63 | } 64 | return std::make_pair(degree, reference_offset); 65 | } 66 | 67 | uint32_t CompressedGraph::Degree(size_t node_id) { 68 | uint32_t first_node_in_chunk = node_id - node_id % kDegreeReferenceChunkSize; 69 | uint32_t reconstructed_degree = 70 | ReadDegreeBits(first_node_in_chunk, kFirstDegreeContext); 71 | size_t context; 72 | size_t last_degree_delta = reconstructed_degree; 73 | for (int node = first_node_in_chunk + 1; node <= node_id; ++node) { 74 | context = DegreeContext(last_degree_delta); 75 | last_degree_delta = ReadDegreeBits(node, context); 76 | reconstructed_degree += UnpackSigned(last_degree_delta); 77 | } 78 | if (reconstructed_degree > num_nodes_) ZKR_ABORT("Invalid degree"); 79 | return reconstructed_degree; 80 | } 81 | 82 | std::vector CompressedGraph::Neighbours(size_t node_id) { 83 | BitReader bit_reader(compressed_.data() + node_start_indices_[node_id] / 8, 84 | compressed_.size()); 85 | bit_reader.ReadBits(node_start_indices_[node_id] % 8); 86 | std::vector neighbours; 87 | 88 | uint32_t first_node_in_chunk = node_id - node_id % kDegreeReferenceChunkSize; 89 | uint32_t reconstructed_degree; 90 | size_t reference_offset = 0; 91 | size_t last_reference_offset = 0; 92 | size_t last_degree_delta = 0; 93 | if (first_node_in_chunk != node_id) { 94 | size_t context; 95 | std::tie(reconstructed_degree, reference_offset) = ReadDegreeAndRefBits( 96 | first_node_in_chunk, kFirstDegreeContext, last_reference_offset); 97 | if (reconstructed_degree != 0) { 98 | last_reference_offset = reference_offset; 99 | } 100 | last_degree_delta = reconstructed_degree; 101 | for (int node = first_node_in_chunk + 1; node < node_id; ++node) { 102 | context = DegreeContext(last_degree_delta); 103 | std::tie(last_degree_delta, reference_offset) = 104 | ReadDegreeAndRefBits(node, context, last_reference_offset); 105 | reconstructed_degree += UnpackSigned(last_degree_delta); 106 | if (reconstructed_degree != 0) { 107 | last_reference_offset = reference_offset; 108 | } 109 | } 110 | context = DegreeContext(last_degree_delta); 111 | last_degree_delta = IntegerCoder::Read(context, &bit_reader, &huff_reader_); 112 | reconstructed_degree += UnpackSigned(last_degree_delta); 113 | } else { 114 | reconstructed_degree = 115 | IntegerCoder::Read(kFirstDegreeContext, &bit_reader, &huff_reader_); 116 | } 117 | 118 | if (reconstructed_degree == 0) return {}; 119 | 120 | if (node_id != 0) { 121 | reference_offset = IntegerCoder::Read( 122 | ReferenceContext(last_reference_offset), &bit_reader, &huff_reader_); 123 | } 124 | 125 | if (reconstructed_degree > num_nodes_) ZKR_ABORT("Invalid degree"); 126 | if (reference_offset > node_id) ZKR_ABORT("Invalid reference_offset"); 127 | 128 | std::vector ref_list; 129 | std::vector block_lengths; 130 | // If a reference_offset is used, read the list of blocks of (alternating) 131 | // copied and skipped edges. 132 | size_t num_to_copy = 0; 133 | if (reference_offset != 0) { 134 | size_t ref_id = node_id - reference_offset; 135 | ref_list = Neighbours(ref_id); 136 | size_t block_count = 137 | IntegerCoder::Read(kBlockCountContext, &bit_reader, &huff_reader_); 138 | size_t block_end = 0; // end of current block 139 | for (size_t j = 0; j < block_count; j++) { 140 | size_t ctx = j == 0 ? kBlockContext 141 | : (j % 2 == 0 ? kBlockContextEven : kBlockContextOdd); 142 | size_t block_len; 143 | if (j == 0) { 144 | block_len = IntegerCoder::Read(ctx, &bit_reader, &huff_reader_); 145 | } else { 146 | block_len = IntegerCoder::Read(ctx, &bit_reader, &huff_reader_) + 1; 147 | } 148 | block_end += block_len; 149 | block_lengths.push_back(block_len); 150 | } 151 | if (ref_list.size() < block_end) { 152 | ZKR_ABORT("Invalid block copy pattern"); 153 | } 154 | // Last block is implicit and goes to the end of the reference list. 155 | block_lengths.push_back(ref_list.size() - block_end); 156 | // Blocks in even positions are to be copied. 157 | for (size_t i = 0; i < block_lengths.size(); i += 2) { 158 | num_to_copy += block_lengths[i]; 159 | } 160 | } 161 | 162 | // reference_offset node for delta-coding of neighbours. 163 | size_t last_dest_plus_one = 0; // will not be used 164 | // Number of edges to read. 165 | size_t num_residuals = reconstructed_degree - num_to_copy; 166 | // Last delta for the residual edges, used for context modeling. 167 | size_t last_residual_delta = 0; 168 | // Current position in the reference list (because we are making a sorted 169 | // merged list). 170 | size_t ref_pos = 0; 171 | // Number of nodes of the current block that should still be copied. 172 | size_t num_to_copy_from_current_block = 173 | block_lengths.empty() ? 0 : block_lengths[0]; 174 | // Index of the next block. 175 | size_t next_block = 1; 176 | // If we don't need to copy anything from the first block, and we have at 177 | // least another even-positioned block, advance the position in the 178 | // reference_offset list accordingly. 179 | if (num_to_copy_from_current_block == 0 && block_lengths.size() > 2) { 180 | ref_pos = block_lengths[1]; 181 | num_to_copy_from_current_block = block_lengths[2]; 182 | next_block = 3; 183 | } 184 | 185 | // Number of consecutive zeros that have been decoded last. 186 | // Delta encoding with -1. 187 | size_t contiguous_zeroes_len = 0; 188 | // Number of further zeros that should not be read from the bitstream. 189 | size_t num_zeros_to_skip = 0; 190 | const auto append = [&](size_t destination) { 191 | if (destination >= num_nodes_) return ZKR_FAILURE("Invalid residual"); 192 | neighbours.push_back(destination); 193 | return true; 194 | }; 195 | for (size_t j = 0; j < num_residuals; j++) { 196 | size_t destination_node; 197 | if (j == 0) { 198 | last_residual_delta = IntegerCoder::Read( 199 | FirstResidualContext(num_residuals), &bit_reader, &huff_reader_); 200 | destination_node = node_id + UnpackSigned(last_residual_delta); 201 | } else if (num_zeros_to_skip > 202 | 0) { // If in a zero run, don't read anything. 203 | last_residual_delta = 0; 204 | destination_node = last_dest_plus_one; 205 | } else { 206 | last_residual_delta = IntegerCoder::Read( 207 | ResidualContext(last_residual_delta), &bit_reader, &huff_reader_); 208 | destination_node = last_dest_plus_one + last_residual_delta; 209 | } 210 | // Compute run of zeros if we read a zero and we are not already in one. 211 | if (last_residual_delta == 0 && num_zeros_to_skip == 0) { 212 | contiguous_zeroes_len++; 213 | } else { 214 | contiguous_zeroes_len = 0; 215 | } 216 | // If we are in a run of zeros, decrease its length. 217 | if (num_zeros_to_skip > 0) { 218 | num_zeros_to_skip--; 219 | } 220 | // Merge the edges copied from the reference_offset list with the ones 221 | // read from the bitstream. 222 | while (num_to_copy_from_current_block > 0 && 223 | ref_list[ref_pos] <= destination_node) { 224 | num_to_copy_from_current_block--; 225 | if (!append(ref_list[ref_pos])) ZKR_ABORT("Invalid residual"); 226 | // If our delta coding would produce an edge to destination_node, but y 227 | // with y<=destination_node is copied from the reference_offset list, we 228 | // increase destination_node. In other words, it's delta coding with 229 | // respect to both lists (ref_list and residuals). 230 | if (j != 0 && ref_list[ref_pos] >= last_dest_plus_one) { 231 | destination_node++; 232 | } 233 | ref_pos++; 234 | if (num_to_copy_from_current_block == 0 && 235 | next_block + 1 < block_lengths.size()) { 236 | ref_pos += block_lengths[next_block]; 237 | num_to_copy_from_current_block = block_lengths[next_block + 1]; 238 | next_block += 2; 239 | } 240 | } 241 | // If the current run of zeros is large enough, read how many further 242 | // zeros to decode from the bitstream. 243 | if (contiguous_zeroes_len >= kRleMin) { 244 | num_zeros_to_skip = 245 | IntegerCoder::Read(kRleContext, &bit_reader, &huff_reader_); 246 | contiguous_zeroes_len = 0; 247 | } 248 | if (!append(destination_node)) ZKR_ABORT("Invalid residual"); 249 | last_dest_plus_one = destination_node + 1; 250 | } 251 | ZKR_ASSERT(ref_pos + num_to_copy_from_current_block <= ref_list.size()); 252 | // Process the rest of the block-copy list. 253 | while (num_to_copy_from_current_block > 0) { 254 | num_to_copy_from_current_block--; 255 | if (!append(ref_list[ref_pos])) ZKR_ABORT("Invalid residual"); 256 | ref_pos++; 257 | if (num_to_copy_from_current_block == 0 && 258 | next_block + 1 < block_lengths.size()) { 259 | ref_pos += block_lengths[next_block]; 260 | num_to_copy_from_current_block = block_lengths[next_block + 1]; 261 | next_block += 2; 262 | } 263 | } 264 | return neighbours; 265 | } // namespace zuckerli 266 | 267 | } // namespace zuckerli 268 | -------------------------------------------------------------------------------- /src/decode.h: -------------------------------------------------------------------------------- 1 | #ifndef ZUCKERLI_DECODE_H 2 | #define ZUCKERLI_DECODE_H 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ans.h" 8 | #include "bit_reader.h" 9 | #include "checksum.h" 10 | #include "common.h" 11 | #include "context_model.h" 12 | #include "huffman.h" 13 | #include "integer_coder.h" 14 | 15 | namespace zuckerli { 16 | namespace detail { 17 | 18 | template 19 | bool DecodeGraphImpl(size_t N, bool allow_random_access, Reader* reader, 20 | BitReader* br, const CB& cb, 21 | std::vector* node_start_indices) { 22 | using IntegerCoder = zuckerli::IntegerCoder; 23 | // Storage for the previous up-to-MaxNodesBackwards() lists to be used as a 24 | // reference. 25 | std::vector> prev_lists( 26 | std::min(MaxNodesBackwards(), N)); 27 | std::vector residuals; 28 | std::vector block_lengths; 29 | for (size_t i = 0; i < prev_lists.size(); i++) prev_lists[i].clear(); 30 | size_t rle_min = 31 | allow_random_access ? kRleMin : std::numeric_limits::max(); 32 | // The three quantities below get reset to after kDegreeReferenceChunkSize 33 | // adjacency lists if in random-access mode. 34 | // 35 | // Reference degree for degree delta coding. 36 | size_t last_degree = 0; 37 | // Last degree delta for context modeling. 38 | size_t last_degree_delta = 0; 39 | // Last reference offset for context modeling. 40 | size_t last_reference_offset = 0; 41 | for (size_t current_node = 0; current_node < N; current_node++) { 42 | size_t i_mod = current_node % MaxNodesBackwards(); 43 | prev_lists[i_mod].clear(); 44 | block_lengths.clear(); 45 | size_t degree; 46 | if (node_start_indices) node_start_indices->push_back(br->NumBitsRead()); 47 | if ((allow_random_access && 48 | current_node % kDegreeReferenceChunkSize == 0) || 49 | current_node == 0) { 50 | degree = IntegerCoder::Read(kFirstDegreeContext, br, reader); 51 | last_degree_delta = 52 | degree; // special case: we assume a node -1 with degree 0 53 | last_reference_offset = 0; 54 | } else { 55 | size_t ctx = DegreeContext(last_degree_delta); 56 | last_degree_delta = IntegerCoder::Read(ctx, br, reader); 57 | degree = 58 | last_degree + 59 | UnpackSigned( 60 | last_degree_delta); // this can be negative, hence calling this 61 | } 62 | last_degree = degree; 63 | if (degree > N) return ZKR_FAILURE("Invalid degree"); 64 | if (degree == 0) continue; 65 | 66 | // If this is not the first node, read the offset of the list to be used as 67 | // a reference. 68 | size_t reference_offset = 0; 69 | if (current_node != 0) { 70 | reference_offset = IntegerCoder::Read( 71 | ReferenceContext(last_reference_offset), br, reader); 72 | last_reference_offset = reference_offset; 73 | } 74 | if (reference_offset > current_node) 75 | return ZKR_FAILURE("Invalid reference_offset"); 76 | 77 | // If a reference_offset is used, read the list of blocks of (alternating) 78 | // copied and skipped edges. 79 | size_t num_to_copy = 0; 80 | if (reference_offset != 0) { 81 | size_t block_count = IntegerCoder::Read(kBlockCountContext, br, reader); 82 | size_t block_end = 0; // end of current block 83 | for (size_t j = 0; j < block_count; j++) { 84 | size_t ctx = j == 0 85 | ? kBlockContext 86 | : (j % 2 == 0 ? kBlockContextEven : kBlockContextOdd); 87 | size_t block_len; 88 | if (j == 0) { 89 | block_len = IntegerCoder::Read(ctx, br, reader); 90 | } else { 91 | block_len = IntegerCoder::Read(ctx, br, reader) + 1; 92 | } 93 | block_end += block_len; 94 | block_lengths.push_back(block_len); 95 | } 96 | if (prev_lists[(current_node - reference_offset) % MaxNodesBackwards()] 97 | .size() < block_end) { 98 | return ZKR_FAILURE("Invalid block copy pattern"); 99 | } 100 | // Last block is implicit and goes to the end of the reference list. 101 | block_lengths.push_back( 102 | prev_lists[(current_node - reference_offset) % MaxNodesBackwards()] 103 | .size() - 104 | block_end); 105 | // Blocks in even positions are to be copied. 106 | for (size_t i = 0; i < block_lengths.size(); i += 2) { 107 | num_to_copy += block_lengths[i]; 108 | } 109 | } 110 | 111 | // Read all the edges that are not copied. 112 | 113 | // reference_offset node for delta-coding of neighbours. 114 | size_t last_dest_plus_one = 0; // will not be used 115 | // Number of edges to read. 116 | size_t num_residuals = degree - num_to_copy; 117 | // Last delta for the residual edges, used for context modeling. 118 | size_t last_residual_delta = 0; 119 | // Current position in the reference list (because we are making a sorted 120 | // merged list). 121 | size_t ref_pos = 0; 122 | // Number of nodes of the current block that should still be copied. 123 | size_t num_to_copy_from_current_block = 124 | block_lengths.empty() ? 0 : block_lengths[0]; 125 | // Index of the next block. 126 | size_t next_block = 1; 127 | // If we don't need to copy anything from the first block, and we have at 128 | // least another even-positioned block, advance the position in the 129 | // reference_offset list accordingly. 130 | if (num_to_copy_from_current_block == 0 && block_lengths.size() > 2) { 131 | ref_pos = block_lengths[1]; 132 | num_to_copy_from_current_block = block_lengths[2]; 133 | next_block = 3; 134 | } 135 | // ID of reference list. 136 | size_t ref_id = (current_node - reference_offset) % MaxNodesBackwards(); 137 | // Number of consecutive zeros that have been decoded last. 138 | // Delta encoding with -1. 139 | size_t contiguous_zeroes_len = 0; 140 | // Number of further zeros that should not be read from the bitstream. 141 | size_t num_zeros_to_skip = 0; 142 | const auto append = [&](size_t x) { 143 | if (x >= N) return ZKR_FAILURE("Invalid residual"); 144 | prev_lists[i_mod].push_back(x); 145 | cb(current_node, x); 146 | return true; 147 | }; 148 | for (size_t j = 0; j < num_residuals; j++) { 149 | size_t destination_node; 150 | if (j == 0) { 151 | last_residual_delta = 152 | IntegerCoder::Read(FirstResidualContext(num_residuals), br, reader); 153 | destination_node = current_node + UnpackSigned(last_residual_delta); 154 | } else if (num_zeros_to_skip > 155 | 0) { // If in a zero run, don't read anything. 156 | last_residual_delta = 0; 157 | destination_node = last_dest_plus_one; 158 | } else { 159 | last_residual_delta = IntegerCoder::Read( 160 | ResidualContext(last_residual_delta), br, reader); 161 | destination_node = last_dest_plus_one + last_residual_delta; 162 | } 163 | // Compute run of zeros if we read a zero and we are not already in one. 164 | if (last_residual_delta == 0 && num_zeros_to_skip == 0) { 165 | contiguous_zeroes_len++; 166 | } else { 167 | contiguous_zeroes_len = 0; 168 | } 169 | // If we are in a run of zeros, decrease its length. 170 | if (num_zeros_to_skip > 0) { 171 | num_zeros_to_skip--; 172 | } 173 | // Merge the edges copied from the reference_offset list with the ones 174 | // read from the bitstream. 175 | while (num_to_copy_from_current_block > 0 && 176 | prev_lists[ref_id][ref_pos] <= destination_node) { 177 | num_to_copy_from_current_block--; 178 | ZKR_RETURN_IF_ERROR(append(prev_lists[ref_id][ref_pos])); 179 | // If our delta coding would produce an edge to destination_node, but y 180 | // with y<=destination_node is copied from the reference_offset list, we 181 | // increase destination_node. In other words, it's delta coding with 182 | // respect to both lists (prev_lists and residuals). 183 | if (j != 0 && prev_lists[ref_id][ref_pos] >= last_dest_plus_one) { 184 | destination_node++; 185 | } 186 | ref_pos++; 187 | if (num_to_copy_from_current_block == 0 && 188 | next_block + 1 < block_lengths.size()) { 189 | ref_pos += block_lengths[next_block]; 190 | num_to_copy_from_current_block = block_lengths[next_block + 1]; 191 | next_block += 2; 192 | } 193 | } 194 | // If the current run of zeros is large enough, read how many further 195 | // zeros to decode from the bitstream. 196 | if (contiguous_zeroes_len >= rle_min) { 197 | num_zeros_to_skip = IntegerCoder::Read(kRleContext, br, reader); 198 | contiguous_zeroes_len = 0; 199 | } 200 | 201 | ZKR_RETURN_IF_ERROR(append(destination_node)); 202 | last_dest_plus_one = destination_node + 1; 203 | } 204 | ZKR_ASSERT(ref_pos + num_to_copy_from_current_block <= 205 | prev_lists[ref_id].size()); 206 | // Process the rest of the block-copy list. 207 | while (num_to_copy_from_current_block > 0) { 208 | num_to_copy_from_current_block--; 209 | ZKR_RETURN_IF_ERROR(append(prev_lists[ref_id][ref_pos])); 210 | ref_pos++; 211 | if (num_to_copy_from_current_block == 0 && 212 | next_block + 1 < block_lengths.size()) { 213 | ref_pos += block_lengths[next_block]; 214 | num_to_copy_from_current_block = block_lengths[next_block + 1]; 215 | next_block += 2; 216 | } 217 | } 218 | } 219 | if (!reader->CheckFinalState()) { 220 | return ZKR_FAILURE("Invalid stream"); 221 | } 222 | return true; 223 | } 224 | 225 | } // namespace detail 226 | 227 | bool DecodeGraph(const std::vector& compressed, 228 | size_t* checksum = nullptr, 229 | std::vector* node_start_indices = nullptr) { 230 | if (compressed.empty()) return ZKR_FAILURE("Empty file"); 231 | auto start = std::chrono::high_resolution_clock::now(); 232 | BitReader reader(compressed.data(), compressed.size()); 233 | size_t N = reader.ReadBits(48); 234 | bool allow_random_access = reader.ReadBits(1); 235 | size_t edges = 0, chksum = 0; 236 | auto edge_callback = [&](size_t a, size_t b) { 237 | edges++; 238 | chksum = Checksum(chksum, a, b); 239 | }; 240 | if (allow_random_access) { 241 | HuffmanReader huff_reader; 242 | huff_reader.Init(kNumContexts, &reader); 243 | ZKR_RETURN_IF_ERROR( 244 | detail::DecodeGraphImpl(N, allow_random_access, &huff_reader, &reader, 245 | edge_callback, node_start_indices)); 246 | } else { 247 | ANSReader ans_reader; 248 | ans_reader.Init(kNumContexts, &reader); 249 | ZKR_RETURN_IF_ERROR( 250 | detail::DecodeGraphImpl(N, allow_random_access, &ans_reader, &reader, 251 | edge_callback, node_start_indices)); 252 | } 253 | auto stop = std::chrono::high_resolution_clock::now(); 254 | 255 | float elapsed = 256 | std::chrono::duration_cast(stop - start) 257 | .count(); 258 | 259 | fprintf(stderr, "Decompressed %.2f ME/s (%zu) from %.2f BPE. Checksum: %lx\n", 260 | edges / elapsed, edges, 8.0 * compressed.size() / edges, chksum); 261 | if (checksum) *checksum = chksum; 262 | return true; 263 | } 264 | } // namespace zuckerli 265 | 266 | #endif // ZUCKERLI_DECODE_H 267 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/ans.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // Copyright 2019 the JPEG XL Project 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | #include "ans.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "bit_reader.h" 22 | #include "integer_coder.h" 23 | 24 | namespace zuckerli { 25 | 26 | namespace { 27 | 28 | // Ensure that each histogram sums to 1<* histogram) { 30 | int64_t sum = std::accumulate(histogram->begin(), histogram->end(), 0); 31 | if (sum == 0) { 32 | (*histogram)[0] = 1 << kANSNumBits; 33 | return; 34 | } 35 | std::vector> symbols_with_freq; 36 | for (size_t i = 0; i < histogram->size(); i++) { 37 | if ((*histogram)[i] != 0) { 38 | symbols_with_freq.emplace_back((*histogram)[i], i); 39 | } 40 | } 41 | std::sort(symbols_with_freq.begin(), symbols_with_freq.end()); 42 | for (size_t i = 0; i < symbols_with_freq.size(); i++) { 43 | size_t sym = symbols_with_freq[i].second; 44 | int64_t freq = (*histogram)[sym]; 45 | int64_t normalized_freq = freq * (1 << kANSNumBits) / sum; 46 | if (normalized_freq <= 0) normalized_freq = 1; 47 | (*histogram)[sym] = normalized_freq; 48 | } 49 | 50 | // Adjust sum by assigning all the extra (or missing) weight to the 51 | // highest-weight symbol. 52 | int64_t new_sum = std::accumulate(histogram->begin(), histogram->end(), 0); 53 | (*histogram)[symbols_with_freq.back().second] += (1 << kANSNumBits) - new_sum; 54 | ZKR_ASSERT(std::accumulate(histogram->begin(), histogram->end(), 0) == 55 | (1 << kANSNumBits)); 56 | } 57 | 58 | // First, all trailing non-occuring symbols are removed from the distribution; 59 | // if this leaves the distribution empty, a dummy symbol with max weight is 60 | // added. This ensures that the resulting distribution sums to total table size. 61 | // Then, `entry_size` is chosen to be the largest power of two so that 62 | // `table_size` = ANS_TAB_SIZE/`entry_size` is at least as big as the 63 | // distribution size. 64 | // Note that each entry will only ever contain two different symbols, and 65 | // consecutive ranges of offsets, which allows us to use a compact 66 | // representation. 67 | // Each entry is initialized with only the (symbol=i, offset) pairs; then 68 | // positions for which the entry overflows (i.e. distribution[i] > entry_size) 69 | // or is not full are computed, and put into a stack in increasing order. 70 | // Missing symbols in the distribution are padded with 0 (because `table_size` 71 | // >= number of symbols). The `cutoff` value for each entry is initialized to 72 | // the number of occupied slots in that entry (i.e. `distributions[i]`). While 73 | // the overflowing-symbol stack is not empty (which implies that the 74 | // underflowing-symbol stack also is not), the top overfull and underfull 75 | // positions are popped from the stack; the empty slots in the underfull entry 76 | // are then filled with as many slots as needed from the overfull entry; such 77 | // slots are placed after the slots in the overfull entry, and `offsets[1]` is 78 | // computed accordingly. The formerly underfull entry is thus now neither 79 | // underfull nor overfull, and represents exactly two symbols. The overfull 80 | // entry might be either overfull or underfull, and is pushed into the 81 | // corresponding stack. 82 | void InitAliasTable(std::vector distribution, 83 | AliasTable::Entry* ZKR_RESTRICT a) { 84 | while (!distribution.empty() && distribution.back() == 0) { 85 | distribution.pop_back(); 86 | } 87 | // Ensure that a valid table is always returned, even for an empty 88 | // alphabet. Otherwise, a specially-crafted stream might crash the 89 | // decoder. 90 | if (distribution.empty()) { 91 | distribution.emplace_back(1 << kANSNumBits); 92 | } 93 | const int kTableSizeLog = kLogNumSymbols; 94 | const int kTableSize = kNumSymbols; 95 | ZKR_ASSERT(distribution.size() <= (1 << kTableSizeLog)); 96 | ZKR_ASSERT(kTableSize >= distribution.size()); 97 | constexpr int kEntrySize = 1 << (kANSNumBits - kTableSizeLog); 98 | static_assert(kEntrySize <= 256, 99 | "kEntrySize-sized integers will be stored in uint8_t"); 100 | std::vector underfull_posn; 101 | std::vector overfull_posn; 102 | size_t cutoffs[kTableSize]; 103 | // Initialize entries. 104 | for (size_t i = 0; i < distribution.size(); i++) { 105 | cutoffs[i] = distribution[i]; 106 | if (cutoffs[i] > kEntrySize) { 107 | overfull_posn.push_back(i); 108 | } else if (cutoffs[i] < kEntrySize) { 109 | underfull_posn.push_back(i); 110 | } 111 | } 112 | for (int i = distribution.size(); i < kTableSize; i++) { 113 | cutoffs[i] = 0; 114 | underfull_posn.push_back(i); 115 | } 116 | // Reassign overflow/underflow values. 117 | while (!overfull_posn.empty()) { 118 | int overfull_i = overfull_posn.back(); 119 | overfull_posn.pop_back(); 120 | ZKR_ASSERT(!underfull_posn.empty()); 121 | int underfull_i = underfull_posn.back(); 122 | underfull_posn.pop_back(); 123 | int underfull_by = kEntrySize - cutoffs[underfull_i]; 124 | cutoffs[overfull_i] -= underfull_by; 125 | // overfull positions have their original symbols 126 | a[underfull_i].right_value = overfull_i; 127 | a[underfull_i].offsets1 = cutoffs[overfull_i]; 128 | // Slots in the right part of entry underfull_i were taken from the end 129 | // of the symbols in entry overfull_i. 130 | if (cutoffs[overfull_i] < kEntrySize) { 131 | underfull_posn.push_back(overfull_i); 132 | } else if (cutoffs[overfull_i] > kEntrySize) { 133 | overfull_posn.push_back(overfull_i); 134 | } 135 | } 136 | for (int i = 0; i < kTableSize; i++) { 137 | // cutoffs[i] is properly initialized but the clang-analyzer doesn't infer 138 | // it since it is partially initialized across two for-loops. 139 | // NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) 140 | if (cutoffs[i] == kEntrySize) { 141 | a[i].right_value = i; 142 | a[i].offsets1 = 0; 143 | a[i].cutoff = 0; 144 | } else { 145 | // Note that, if cutoff is not equal to entry_size, 146 | // a[i].offsets1 was initialized with (overfull cutoff) - 147 | // (entry_size - a[i].cutoff). Thus, subtracting 148 | // a[i].cutoff cannot make it negative. 149 | a[i].offsets1 -= cutoffs[i]; 150 | a[i].cutoff = cutoffs[i]; 151 | } 152 | const size_t freq0 = i < distribution.size() ? distribution[i] : 0; 153 | const size_t i1 = a[i].right_value; 154 | const size_t freq1 = i1 < distribution.size() ? distribution[i1] : 0; 155 | a[i].freq0 = static_cast(freq0); 156 | a[i].freq1_xor_freq0 = static_cast(freq1 ^ freq0); 157 | } 158 | } 159 | 160 | // Very simple encoding: for each symbol, 1 bit for presence/absence, and 161 | // kANSNumBits bits for symbol probability if present. 162 | void EncodeSymbolProbabilities(const std::vector& histogram, 163 | BitWriter* ZKR_RESTRICT writer) { 164 | for (size_t i = 0; i < kNumSymbols; i++) { 165 | if (histogram.size() > i && histogram[i] != 0) { 166 | writer->Write(1, 1); 167 | writer->Write(kANSNumBits, histogram[i] - 1); 168 | } else { 169 | writer->Write(1, 0); 170 | } 171 | } 172 | } 173 | 174 | void DecodeSymbolProbabilities(std::vector* histogram, 175 | BitReader* ZKR_RESTRICT reader) { 176 | histogram->resize(kNumSymbols); 177 | for (size_t i = 0; i < kNumSymbols; i++) { 178 | if (reader->ReadBits(1)) { 179 | (*histogram)[i] = reader->ReadBits(kANSNumBits) + 1; 180 | } else { 181 | (*histogram)[i] = 0; 182 | } 183 | } 184 | } 185 | 186 | // precision must be equal to: #bits(state_) + #bits(freq) 187 | size_t kReciprocalPrecision = 32 + kANSNumBits; 188 | 189 | struct ANSEncSymbolInfo { 190 | uint16_t freq; 191 | std::vector reverse_map; 192 | // Value such that (state_ * ifreq) >> kReciprocalPrecision == state_ / freq. 193 | uint64_t ifreq; 194 | }; 195 | 196 | } // namespace 197 | 198 | void ANSEncode(const IntegerData& integers, size_t num_contexts, 199 | BitWriter* writer, std::vector* bits_per_ctx) { 200 | // Compute histograms. 201 | std::vector> histograms; 202 | histograms.resize(num_contexts); 203 | integers.Histograms(&histograms); 204 | 205 | writer->Reserve(num_contexts * kNumSymbols * (1 + kANSNumBits)); 206 | bits_per_ctx->resize(num_contexts); 207 | 208 | // Normalize and encode histograms and compute alias tables. 209 | ZKR_ASSERT(histograms.size() == num_contexts); 210 | ANSEncSymbolInfo enc_symbol_info[kMaxNumContexts][kNumSymbols] = {}; 211 | for (size_t i = 0; i < histograms.size(); i++) { 212 | AliasTable::Entry entries[1 << kANSNumBits] = {}; 213 | // Ensure consistent size on decoder and encoder side. 214 | histograms[i].resize(kNumSymbols); 215 | NormalizeHistogram(&histograms[i]); 216 | EncodeSymbolProbabilities(histograms[i], writer); 217 | InitAliasTable(histograms[i], &entries[0]); 218 | 219 | // Compute encoding information. 220 | for (size_t sym = 0; sym < std::max(histograms[i].size(), 1); 221 | sym++) { 222 | size_t freq = 223 | histograms[i].empty() ? (1 << kANSNumBits) : histograms[i][sym]; 224 | enc_symbol_info[i][sym].freq = freq; 225 | if (freq != 0) { 226 | enc_symbol_info[i][sym].ifreq = 227 | ((1ull << kReciprocalPrecision) + freq - 1) / freq; 228 | } 229 | enc_symbol_info[i][sym].reverse_map.resize(freq); 230 | } 231 | for (size_t t = 0; t < (1 << kANSNumBits); t++) { 232 | AliasTable::Symbol s = AliasTable::Lookup(entries, t); 233 | if (s.freq == 0) continue; 234 | enc_symbol_info[i][s.value].reverse_map[s.offset] = t; 235 | } 236 | } 237 | 238 | float kProbBits[(1 << kANSNumBits) + 1]; 239 | for (size_t i = 1; i <= (1 << kANSNumBits); i++) { 240 | kProbBits[i] = -std::log2(i * (1.0f / (1 << kANSNumBits))); 241 | } 242 | 243 | // The decoder should output ans_output_bits[i] when reaching index 244 | // output_idx[i]. 245 | std::vector ans_output_bits; 246 | std::vector output_idx; 247 | 248 | size_t extra_bits = 0; 249 | 250 | size_t ans_state = kANSSignature; 251 | 252 | // Iterate through tokens **in reverse order** to compute state updates. 253 | integers.ForEachReversed([&](size_t ctx, size_t token, size_t nbits, 254 | size_t bits, size_t i) { 255 | (*bits_per_ctx)[ctx] += kProbBits[enc_symbol_info[ctx][token].freq] + nbits; 256 | extra_bits += nbits; 257 | const ANSEncSymbolInfo& info = enc_symbol_info[ctx][token]; 258 | // Flush state. 259 | if ((ans_state >> (32 - kANSNumBits)) >= info.freq) { 260 | ans_output_bits.push_back(ans_state & 0xFFFF); 261 | output_idx.push_back(i); 262 | ans_state >>= 16; 263 | } 264 | uint32_t v = (ans_state * info.ifreq) >> kReciprocalPrecision; 265 | uint32_t offset = info.reverse_map[ans_state - v * info.freq]; 266 | ans_state = (v << kANSNumBits) + offset; 267 | }); 268 | 269 | writer->Reserve(extra_bits + ans_output_bits.size() * 16 + 32); 270 | writer->Write(32, ans_state); 271 | 272 | size_t output_idx_pos = output_idx.size(); 273 | // Iterate through tokens in forward order to produce output. 274 | integers.ForEach( 275 | [&](size_t ctx, size_t token, size_t nbits, size_t bits, size_t i) { 276 | if (output_idx_pos > 0 && i == output_idx[output_idx_pos - 1]) { 277 | writer->Write(16, ans_output_bits[output_idx_pos - 1]); 278 | output_idx_pos--; 279 | } 280 | writer->Write(nbits, bits); 281 | }); 282 | } 283 | 284 | AliasTable::Symbol AliasTable::Lookup(const Entry* ZKR_RESTRICT table, 285 | size_t value) { 286 | const size_t i = value >> kLogEntrySize; 287 | const size_t pos = value & kEntrySizeMinus1; 288 | 289 | uint64_t entry; 290 | memcpy(&entry, &table[i].cutoff, sizeof(entry)); 291 | const size_t cutoff = entry & 0xFF; // = MOVZX 292 | const size_t right_value = (entry >> 8) & 0xFF; // = MOVZX 293 | const size_t freq0 = (entry >> 16) & 0xFFFF; 294 | 295 | const bool greater = pos >= cutoff; 296 | 297 | const uint64_t conditional = greater ? entry : 0; // = CMOV 298 | const size_t offsets1_or_0 = (conditional >> 32) & 0xFFFF; 299 | const size_t freq1_xor_freq0_or_0 = conditional >> 48; 300 | 301 | // WARNING: moving this code may interfere with CMOV heuristics. 302 | Symbol s; 303 | s.value = greater ? right_value : i; 304 | s.offset = offsets1_or_0 + pos; 305 | s.freq = freq0 ^ freq1_xor_freq0_or_0; // = greater ? freq1 : freq0 306 | // XOR avoids implementation-defined conversion from unsigned to signed. 307 | // Alternatives considered: BEXTR is 2 cycles on HSW, SET+shift causes 308 | // spills, simple ternary has a long dependency chain. 309 | 310 | return s; 311 | } 312 | 313 | bool ANSReader::Init(size_t num_contexts, BitReader* ZKR_RESTRICT br) { 314 | ZKR_ASSERT(num_contexts <= kMaxNumContexts); 315 | std::vector histogram; 316 | for (size_t i = 0; i < num_contexts; i++) { 317 | DecodeSymbolProbabilities(&histogram, br); 318 | size_t total_probability = 319 | std::accumulate(histogram.begin(), histogram.end(), 0); 320 | if (total_probability != 0 && total_probability != 1 << kANSNumBits) { 321 | return ZKR_FAILURE("Invalid histogram"); 322 | } 323 | InitAliasTable(histogram, &entries_[i][0]); 324 | } 325 | state_ = br->ReadBits(32); 326 | return true; 327 | } 328 | 329 | size_t ANSReader::Read(size_t ctx, BitReader* reader) { 330 | const uint32_t res = state_ & ((1 << kANSNumBits) - 1); 331 | const AliasTable::Entry* table = &entries_[ctx][0]; 332 | const AliasTable::Symbol symbol = AliasTable::Lookup(table, res); 333 | state_ = symbol.freq * (state_ >> kANSNumBits) + symbol.offset; 334 | const uint32_t new_state = 335 | (state_ << 16u) | static_cast(reader->PeekBits(16)); 336 | const bool normalize = state_ < (1u << 16u); 337 | state_ = normalize ? new_state : state_; 338 | reader->Advance(normalize ? 16 : 0); 339 | if (state_ < (1u << 16u)) { 340 | state_ = (state_ << 16u) | reader->PeekBits(16); 341 | reader->Advance(16); 342 | } 343 | return symbol.value; 344 | } 345 | 346 | } // namespace zuckerli 347 | -------------------------------------------------------------------------------- /src/encode.cc: -------------------------------------------------------------------------------- 1 | #include "encode.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "ans.h" 10 | #include "checksum.h" 11 | #include "common.h" 12 | #include "context_model.h" 13 | #include "huffman.h" 14 | #include "integer_coder.h" 15 | #include "absl/flags/flag.h" 16 | #include "uncompressed_graph.h" 17 | 18 | ABSL_FLAG(bool, print_bits_breakdown, false, 19 | "Print a breakdown of where bits are spent"); 20 | 21 | namespace zuckerli { 22 | 23 | namespace { 24 | // TODO: consider discarding short "copy" runs. 25 | void ComputeBlocksAndResiduals(const UncompressedGraph &g, size_t i, size_t ref, 26 | std::vector *blocks, 27 | std::vector *residuals) { 28 | blocks->clear(); 29 | residuals->clear(); 30 | constexpr size_t kMinBlockLen = 0; 31 | size_t ipos = 0; 32 | size_t rpos = 0; 33 | bool is_same = true; 34 | blocks->push_back(0); 35 | while (ipos < g.Degree(i) && rpos < g.Degree(i - ref)) { 36 | size_t a = g.Neighbours(i)[ipos]; 37 | size_t b = g.Neighbours(i - ref)[rpos]; 38 | if (a == b) { 39 | ipos++; 40 | rpos++; 41 | if (!is_same) { 42 | blocks->emplace_back(0); 43 | } 44 | blocks->back()++; 45 | is_same = true; 46 | } else if (a < b) { 47 | ipos++; 48 | residuals->push_back(a); 49 | } else { // a > b 50 | if (is_same) { 51 | blocks->emplace_back(0); 52 | } 53 | blocks->back()++; 54 | is_same = false; 55 | rpos++; 56 | } 57 | } 58 | if (ipos != g.Degree(i)) { 59 | for (size_t j = ipos; j < g.Degree(i); j++) { 60 | residuals->push_back(g.Neighbours(i)[j]); 61 | } 62 | } 63 | size_t pos = 0; 64 | size_t cur = 1; 65 | bool include = false; 66 | for (size_t k = 1; k < blocks->size(); k++) { 67 | if (include && (*blocks)[k] < kMinBlockLen && k + 1 < blocks->size()) { 68 | size_t add = (*blocks)[k]; 69 | size_t skip = (*blocks)[k + 1]; 70 | (*blocks)[cur - 1] += add + skip; 71 | for (size_t j = 0; j < add; j++) { 72 | residuals->push_back(g.Neighbours(i)[pos + j]); 73 | } 74 | pos += add + skip; 75 | k++; 76 | } else { 77 | (*blocks)[cur++] = (*blocks)[k]; 78 | pos += (*blocks)[k]; 79 | include = !include; 80 | } 81 | } 82 | std::sort(residuals->begin(), residuals->end()); 83 | if (rpos == g.Degree(i - ref) || !is_same) { 84 | blocks->pop_back(); 85 | } 86 | } 87 | 88 | template 89 | void ProcessBlocks(const std::vector &blocks, 90 | const UncompressedGraph &g, size_t i, size_t reference, 91 | CB1 copy_cb, CB2 cb) { 92 | // TODO: more ctx modeling. 93 | cb(kBlockCountContext, blocks.size()); 94 | bool copy = true; 95 | size_t pos = 0; 96 | for (size_t j = 0; j < blocks.size(); j++) { 97 | size_t b = blocks[j]; 98 | if (j) { 99 | b--; 100 | } 101 | size_t ctx = j == 0 ? kBlockContext 102 | : (j % 2 == 0 ? kBlockContextEven : kBlockContextOdd); 103 | cb(ctx, b); 104 | if (copy) { 105 | for (size_t k = 0; k < blocks[j]; k++) { 106 | copy_cb(g.Neighbours(i - reference)[pos++]); 107 | } 108 | } else { 109 | pos += blocks[j]; 110 | } 111 | copy = !copy; 112 | } 113 | if (copy) { 114 | for (size_t k = pos; k < g.Neighbours(i - reference).size(); k++) { 115 | copy_cb(g.Neighbours(i - reference)[pos++]); 116 | } 117 | } 118 | } 119 | 120 | template 121 | void ProcessResiduals(const std::vector &residuals, size_t i, 122 | const std::vector &adj_block, 123 | bool allow_random_access, CB1 undo_cb, CB2 cb) { 124 | size_t ref = i; 125 | size_t last_delta = 0; 126 | size_t adj_pos = 0; 127 | size_t adj_lim = adj_block.size(); 128 | size_t zero_run = 0; 129 | for (size_t j = 0; j < residuals.size(); j++) { 130 | size_t ctx = 0; 131 | if (j == 0) { 132 | ctx = FirstResidualContext(residuals.size()); 133 | last_delta = PackSigned(int64_t(residuals[j]) - ref); 134 | } else { 135 | ctx = ResidualContext(last_delta); 136 | last_delta = residuals[j] - ref; 137 | while (adj_pos < adj_lim && adj_block[adj_pos] < ref) { 138 | adj_pos++; 139 | } 140 | while (adj_pos < adj_lim && adj_block[adj_pos] < residuals[j]) { 141 | ZKR_DASSERT(last_delta > 0); 142 | last_delta--; 143 | adj_pos++; 144 | } 145 | } 146 | if (last_delta != 0) { 147 | if (zero_run >= kRleMin && allow_random_access) { 148 | for (size_t cnt = kRleMin; cnt < zero_run; cnt++) { 149 | undo_cb(); 150 | } 151 | cb(kRleContext, zero_run - kRleMin); 152 | } 153 | zero_run = 0; 154 | } 155 | if (last_delta == 0) { 156 | zero_run++; 157 | } 158 | cb(ctx, last_delta); 159 | ref = residuals[j] + 1; 160 | } 161 | if (zero_run >= kRleMin && allow_random_access) { 162 | for (size_t cnt = kRleMin; cnt < zero_run; cnt++) { 163 | undo_cb(); 164 | } 165 | cb(kRleContext, zero_run - kRleMin); 166 | } 167 | } 168 | 169 | void UpdateReferencesForMaxLength(const std::vector &saved_costs, 170 | std::vector &references, 171 | size_t max_length) { 172 | ZKR_ASSERT(saved_costs.size() == references.size()); 173 | size_t N = references.size(); 174 | for (size_t i = 0; i < N; i++) { 175 | ZKR_ASSERT(references[i] <= i); 176 | ZKR_ASSERT(saved_costs[i] >= 0); 177 | if (references[i] == 0) ZKR_ASSERT(saved_costs[i] == 0); 178 | } 179 | size_t has_ref = 0; 180 | for (size_t i = 0; i < N; i++) { 181 | if (references[i]) { 182 | has_ref++; 183 | } 184 | } 185 | fprintf(stderr, "has ref pre: %lu\n", has_ref); 186 | std::vector> out_edges(N); 187 | for (size_t i = 0; i < N; i++) { 188 | if (references[i] != 0) { 189 | out_edges[i - references[i]].push_back(i); 190 | } 191 | } 192 | std::vector dyn(N * (max_length + 1)); 193 | std::vector choice(N * (max_length + 1)); // true -> use reference. 194 | 195 | // TODO: check this. 196 | for (size_t ip1 = N; ip1 > 0; ip1--) { 197 | size_t i = ip1 - 1; 198 | float child_sum_full_chain = 0; 199 | for (uint64_t child : out_edges[i]) { 200 | child_sum_full_chain += dyn[child * (max_length + 1) + max_length]; 201 | } 202 | 203 | choice[i * (max_length + 1)] = false; 204 | dyn[i * (max_length + 1)] = child_sum_full_chain; 205 | 206 | // counting parent link, if any. 207 | for (size_t links_to_use = 1; links_to_use <= max_length; links_to_use++) { 208 | float child_sum = saved_costs[i]; 209 | // Take it. 210 | for (uint64_t child : out_edges[i]) { 211 | child_sum += dyn[child * (max_length + 1) + links_to_use - 1]; 212 | } 213 | if (child_sum > child_sum_full_chain) { 214 | choice[i * (max_length + 1) + links_to_use] = true; 215 | dyn[i * (max_length + 1) + links_to_use] = child_sum; 216 | } else { 217 | choice[i * (max_length + 1) + links_to_use] = false; 218 | dyn[i * (max_length + 1) + links_to_use] = child_sum_full_chain; 219 | } 220 | } 221 | } 222 | 223 | std::vector available_length(N, max_length); 224 | has_ref = 0; 225 | for (size_t i = 0; i < N; i++) { 226 | if (choice[i * (max_length + 1) + available_length[i]]) { 227 | // Taken: push available_length. 228 | for (uint64_t child : out_edges[i]) { 229 | available_length[child] = available_length[i] - 1; 230 | } 231 | } else { 232 | // Not taken: remove reference. 233 | references[i] = 0; 234 | } 235 | if (references[i]) { 236 | has_ref++; 237 | } 238 | } 239 | fprintf(stderr, "has ref post: %lu\n", has_ref); 240 | } 241 | } // namespace 242 | 243 | std::vector EncodeGraph(const UncompressedGraph &g, 244 | bool allow_random_access, size_t *checksum) { 245 | auto start = std::chrono::high_resolution_clock::now(); 246 | size_t N = g.size(); 247 | size_t chksum = 0; 248 | size_t edges = 0; 249 | BitWriter writer; 250 | writer.Reserve(64); 251 | writer.Write(48, N); 252 | writer.Write(1, allow_random_access); 253 | size_t with_blocks = 0; 254 | IntegerData tokens; 255 | size_t ref = 0; 256 | size_t last_degree_delta = 0; 257 | std::vector references(N); 258 | std::vector saved_costs(N); 259 | 260 | std::vector symbol_cost(kNumContexts * kNumSymbols, 1.0f); 261 | std::vector residuals; 262 | std::vector blocks; 263 | std::vector adj_block; 264 | std::vector> symbol_count(kNumContexts); 265 | for (size_t i = 0; i < kNumContexts; i++) { 266 | symbol_count[i].resize(kNumSymbols, 0); 267 | } 268 | 269 | // More rounds improve compression a bit, but are also much slower. 270 | // TODO: sometimes, it actually makes things worse (???). Might be max 271 | // chain length. 272 | for (size_t round = 0; round < absl::GetFlag(FLAGS_num_rounds); round++) { 273 | fprintf(stderr, "Selecting references, round %lu%20s\n", round + 1, ""); 274 | std::fill(references.begin(), references.end(), 0); 275 | float c = 0; 276 | auto token_cost = [&](size_t ctx, size_t v) { 277 | int token = IntegerCoder::Token(v); 278 | c += IntegerCoder::Cost(ctx, v, symbol_cost.data()); 279 | symbol_count[ctx][token]++; 280 | }; 281 | // Very rough estimate. 282 | auto rle_undo = [&]() { 283 | c -= symbol_cost[kResidualBaseContext * kNumSymbols]; 284 | }; 285 | 286 | static constexpr size_t kMaxChainLength = 3; 287 | bool greedy = 288 | allow_random_access && absl::GetFlag(FLAGS_greedy_random_access); 289 | std::vector chain_length(N, 0); 290 | for (size_t i = 0; i < N; i++) { 291 | if (i % 32 == 0) fprintf(stderr, "%lu/%lu\r", i, N); 292 | c = 0; 293 | // No block copying. 294 | residuals.assign(g.Neighbours(i).begin(), g.Neighbours(i).end()); 295 | ProcessResiduals(residuals, i, adj_block, allow_random_access, rle_undo, 296 | token_cost); 297 | float cost = c; 298 | float base_cost = c; 299 | saved_costs[i] = 0; 300 | 301 | for (size_t ref = 1; ref < std::min(SearchNum(), i) + 1; ref++) { 302 | if (greedy && chain_length[i - ref] >= kMaxChainLength) continue; 303 | adj_block.clear(); 304 | c = 0; 305 | ComputeBlocksAndResiduals(g, i, ref, &blocks, &residuals); 306 | ProcessBlocks( 307 | blocks, g, i, ref, [&](size_t x) { adj_block.push_back(x); }, 308 | token_cost); 309 | ProcessResiduals(residuals, i, adj_block, allow_random_access, rle_undo, 310 | token_cost); 311 | if (c + 1e-6f < cost) { 312 | references[i] = ref; 313 | cost = c; 314 | saved_costs[i] = base_cost - c; 315 | } 316 | } 317 | if (references[i] != 0) { 318 | chain_length[i] = chain_length[i - references[i]] + 1; 319 | } 320 | } 321 | 322 | // Ensure max reference chain length. 323 | if (allow_random_access && !greedy) { 324 | UpdateReferencesForMaxLength(saved_costs, references, kMaxChainLength); 325 | std::vector chain_length(N); 326 | for (size_t i = 0; i < N; i++) { 327 | if (references[i] != 0) { 328 | chain_length[i] = chain_length[i - references[i]] + 1; 329 | } 330 | } 331 | std::vector fwd_chain_length(N); 332 | for (size_t ip1 = N; ip1 > 0; ip1--) { 333 | size_t i = ip1 - 1; 334 | if (references[i] != 0) { 335 | fwd_chain_length[i - references[i]] = std::max( 336 | fwd_chain_length[i] + 1, fwd_chain_length[i - references[i]]); 337 | } 338 | } 339 | fprintf(stderr, "Adding removed references, round %lu%20s\n", round + 1, 340 | ""); 341 | for (size_t i = 0; i < N; i++) { 342 | if (i % 32 == 0) fprintf(stderr, "%lu/%lu\r", i, N); 343 | if (references[i] != 0) { 344 | chain_length[i] = chain_length[i - references[i]] + 1; 345 | continue; 346 | } 347 | c = 0; 348 | // No block copying 349 | residuals.assign(g.Neighbours(i).begin(), g.Neighbours(i).end()); 350 | ProcessResiduals(residuals, i, adj_block, allow_random_access, rle_undo, 351 | token_cost); 352 | float cost = c; 353 | 354 | for (size_t ref = 1; ref < std::min(SearchNum(), i) + 1; ref++) { 355 | if (chain_length[i - ref] + fwd_chain_length[i] + 1 > 356 | kMaxChainLength) { 357 | continue; 358 | } 359 | adj_block.clear(); 360 | c = 0; 361 | ComputeBlocksAndResiduals(g, i, ref, &blocks, &residuals); 362 | ProcessBlocks( 363 | blocks, g, i, ref, [&](size_t x) { adj_block.push_back(x); }, 364 | token_cost); 365 | ProcessResiduals(residuals, i, adj_block, allow_random_access, 366 | rle_undo, token_cost); 367 | if (c + 1e-6f < cost) { 368 | references[i] = ref; 369 | cost = c; 370 | } 371 | } 372 | if (references[i] != 0) { 373 | chain_length[i] = chain_length[i - references[i]] + 1; 374 | } 375 | } 376 | size_t has_ref = 0; 377 | for (size_t i = 0; i < N; i++) { 378 | if (references[i]) { 379 | has_ref++; 380 | } 381 | } 382 | fprintf(stderr, "has ref restore: %lu\n", has_ref); 383 | } 384 | 385 | // TODO: update references to take into account max chain length. 386 | for (size_t i = 0; i < kNumContexts; i++) { 387 | symbol_count[i].clear(); 388 | symbol_count[i].resize(256, 0); 389 | } 390 | 391 | if (round + 1 != absl::GetFlag(FLAGS_num_rounds)) { 392 | fprintf(stderr, "Computing freqs, round %lu%20s\n", round + 1, ""); 393 | for (size_t i = 0; i < N; i++) { 394 | if (i % 32 == 0) fprintf(stderr, "%lu/%lu\r", i, N); 395 | adj_block.clear(); 396 | if (references[i] == 0) { 397 | residuals.assign(g.Neighbours(i).begin(), g.Neighbours(i).end()); 398 | } else { 399 | ComputeBlocksAndResiduals(g, i, references[i], &blocks, &residuals); 400 | ProcessBlocks( 401 | blocks, g, i, references[i], 402 | [&](size_t x) { adj_block.push_back(x); }, token_cost); 403 | } 404 | ProcessResiduals(residuals, i, adj_block, allow_random_access, rle_undo, 405 | token_cost); 406 | } 407 | 408 | for (size_t i = 0; i < kNumContexts; i++) { 409 | float total_symbols = std::accumulate(symbol_count[i].begin(), 410 | symbol_count[i].end(), 0ul); 411 | if (total_symbols < 0.5f) { 412 | continue; 413 | } 414 | for (size_t s = 0; s < 256; s++) { 415 | float cnt = std::max(1.0f * symbol_count[i][s], 0.1f); 416 | symbol_cost[i * kNumSymbols + s] = std::log(total_symbols / cnt); 417 | symbol_count[i][s] = 0; 418 | } 419 | } 420 | } 421 | } 422 | 423 | // Holds the index of every node degree delta in `tokens` . 424 | std::vector node_degree_indices; 425 | 426 | size_t last_reference = 0; 427 | fprintf(stderr, "Compressing%20s\n", ""); 428 | for (size_t i = 0; i < N; i++) { 429 | if (i % 32 == 0) fprintf(stderr, "%lu/%lu\r", i, N); 430 | fflush(stderr); 431 | if ((allow_random_access && i % kDegreeReferenceChunkSize == 0) || i == 0) { 432 | last_reference = 0; 433 | last_degree_delta = g.Degree(i); 434 | node_degree_indices.push_back(tokens.Size()); 435 | tokens.Add(kFirstDegreeContext, last_degree_delta); 436 | } else { 437 | size_t ctx = DegreeContext(last_degree_delta); 438 | last_degree_delta = PackSigned(g.Degree(i) - ref); 439 | node_degree_indices.push_back(tokens.Size()); 440 | tokens.Add(ctx, last_degree_delta); 441 | } 442 | ref = g.Degree(i); 443 | if (g.Degree(i) == 0) { 444 | continue; 445 | } 446 | size_t reference = references[i]; 447 | std::vector residuals; 448 | std::vector blocks; 449 | if (reference == 0) { 450 | residuals.assign(g.Neighbours(i).begin(), g.Neighbours(i).end()); 451 | } else { 452 | ComputeBlocksAndResiduals(g, i, reference, &blocks, &residuals); 453 | } 454 | std::vector adj_block; 455 | if (i != 0) { 456 | tokens.Add(ReferenceContext(last_reference), reference); 457 | last_reference = reference; 458 | if (reference != 0) { 459 | with_blocks++; 460 | ProcessBlocks( 461 | blocks, g, i, reference, [&](size_t x) { adj_block.push_back(x); }, 462 | [&](size_t ctx, size_t v) { tokens.Add(ctx, v); }); 463 | } 464 | } 465 | // Residuals. 466 | ProcessResiduals( 467 | residuals, i, adj_block, allow_random_access, 468 | [&]() { tokens.RemoveLast(); }, 469 | [&](size_t ctx, size_t v) { tokens.Add(ctx, v); }); 470 | } 471 | for (size_t i = 0; i < N; i++) { 472 | edges += g.Degree(i); 473 | for (size_t j = 0; j < g.Degree(i); j++) { 474 | chksum = Checksum(chksum, i, g.Neighbours(i)[j]); 475 | } 476 | } 477 | 478 | std::vector bits_per_ctx; 479 | if (allow_random_access) { 480 | HuffmanEncode(tokens, kNumContexts, &writer, node_degree_indices, 481 | &bits_per_ctx); 482 | } else { 483 | ANSEncode(tokens, kNumContexts, &writer, &bits_per_ctx); 484 | } 485 | auto data = std::move(writer).GetData(); 486 | auto stop = std::chrono::high_resolution_clock::now(); 487 | 488 | if (absl::GetFlag(FLAGS_print_bits_breakdown)) { 489 | double degree_bits = 0; 490 | for (size_t i = kFirstDegreeContext; i < kReferenceContextBase; i++) { 491 | degree_bits += bits_per_ctx[i]; 492 | } 493 | double reference_bits = 0; 494 | for (size_t i = kReferenceContextBase; i < kBlockCountContext; i++) { 495 | reference_bits += bits_per_ctx[i]; 496 | } 497 | double block_bits = 0; 498 | for (size_t i = kBlockCountContext; i < kFirstResidualBaseContext; i++) { 499 | block_bits += bits_per_ctx[i]; 500 | } 501 | double first_residual_bits = 0; 502 | for (size_t i = kFirstResidualBaseContext; i < kResidualBaseContext; i++) { 503 | first_residual_bits += bits_per_ctx[i]; 504 | } 505 | double residual_bits = 0; 506 | for (size_t i = kResidualBaseContext; i < kNumContexts; i++) { 507 | residual_bits += bits_per_ctx[i]; 508 | } 509 | double total_bits = data.size() * 8.0f; 510 | fprintf(stderr, "Degree bits: %10.2f [%5.2f bits/edge]\n", 511 | degree_bits, degree_bits / edges); 512 | fprintf(stderr, "Reference bits: %10.2f [%5.2f bits/edge]\n", 513 | reference_bits, reference_bits / edges); 514 | fprintf(stderr, "Block bits: %10.2f [%5.2f bits/edge]\n", 515 | block_bits, block_bits / edges); 516 | fprintf(stderr, "First residual bits: %10.2f [%5.2f bits/edge]\n", 517 | first_residual_bits, first_residual_bits / edges); 518 | fprintf(stderr, "Residual bits: %10.2f [%5.2f bits/edge]\n", 519 | residual_bits, residual_bits / edges); 520 | fprintf(stderr, "Total bits: %10.2f [%5.2f bits/edge]\n", 521 | total_bits, total_bits / edges); 522 | } 523 | 524 | float elapsed = 525 | std::chrono::duration_cast(stop - start) 526 | .count(); 527 | 528 | fprintf(stderr, "Compressed %.2f ME/s (%zu) to %.2f BPE. Checksum: %lx\n", 529 | edges / elapsed, edges, 8.0 * data.size() / edges, chksum); 530 | if (checksum) *checksum = chksum; 531 | return data; 532 | } 533 | 534 | } // namespace zuckerli 535 | --------------------------------------------------------------------------------