]> git.sesse.net Git - s573compress/commitdiff
Add a faster compressor.
authorSteinar H. Gunderson <steinar+ddr@gunderson.no>
Fri, 29 Nov 2019 17:14:28 +0000 (18:14 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Fri, 29 Nov 2019 17:14:28 +0000 (18:14 +0100)
compress_fast.cpp [new file with mode: 0644]

diff --git a/compress_fast.cpp b/compress_fast.cpp
new file mode 100644 (file)
index 0000000..95a042e
--- /dev/null
@@ -0,0 +1,181 @@
+// See compress.cpp; this is a version that's much faster, but also _much_ worse
+// (5% larger files or so). It's still not optimal by any means.
+//
+// Same copyright and license (GPLv3).
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+using namespace std;
+
+static uint16_t read16(const char *ptr)
+{
+        // We don't care about the endianness, so native is fine.
+        uint16_t result;
+        memcpy(&result, ptr, sizeof(result));
+        return result;
+}
+
+static uint32_t read24(const char *ptr)
+{
+        // We don't care about the endianness, so native is fine.
+        uint32_t result = 0;
+        memcpy(&result, ptr, 3);
+        return result;
+}
+
+static int find_match_length(const char *s1, const char *s2, int max_length)
+{
+        int length = 0;
+        while (length < max_length && *s1++ == *s2++) {
+                ++length;
+        }
+        return length;
+}
+
+enum TagType { NOT_SET, LONG_MATCH, SHORT_MATCH, ONE_BYTE_LITERAL, LONG_LITERAL };  // EOF is handled separately.
+
+struct Output {
+       string data;
+       int control_bits_used = 8;
+       string::size_type control_byte_pos = string::npos;
+
+       void emit_control_bit(int bit);
+       void emit_literal(const char *ptr, int length);
+       void emit_literal_internal(const char *ptr, int length);
+       void emit_match(int length, int distance);
+       void emit_eof();
+};
+
+void Output::emit_control_bit(int bit)
+{
+       if (control_bits_used == 8) {
+               // We need to output a new control byte for the next eight tags.
+               data.push_back('\0');
+               control_bits_used = 0;
+               control_byte_pos = data.size() - 1;
+       }
+       data[control_byte_pos] |= (bit << control_bits_used);
+       ++control_bits_used;
+}
+
+void Output::emit_literal(const char *ptr, int length)
+{
+       while (length > 0) {
+               int ll = min(length, 70);
+               emit_literal_internal(ptr, ll);
+               ptr += ll;
+               length -= ll;
+       }
+}
+
+void Output::emit_literal_internal(const char *ptr, int length)
+{
+       assert(length <= 70);
+       if (length <= 8) {
+               for (int i = 0; i < length; ++i) {
+                       emit_control_bit(0);
+                       data.push_back(*ptr++);
+               }
+       } else {
+               emit_control_bit(1);
+               data.push_back(0xc0 | (length - 8));
+               data.insert(data.end(), ptr, ptr + length);
+       }
+}
+
+void Output::emit_match(int length, int distance)
+{
+       if (length <= 5 && distance <= 16) {
+               // Short match.
+               emit_control_bit(1);
+               data.push_back(0x80 | ((length - 2) << 4) | (distance - 1));
+       } else {
+               // Long match.
+               assert(length >= 3 && length <= 34 && distance <= 1023);
+               emit_control_bit(1);
+               data.push_back(((length - 3) << 2) | (distance >> 8));
+               data.push_back(distance & 0xff);
+       }
+}
+
+void Output::emit_eof()
+{
+       emit_control_bit(1);
+       data.push_back(0xff);
+}
+
+string compress_konamilz77(const string &input)
+{
+       unordered_map<uint32_t, int> match_to_pos;
+
+       Output output;
+       int unemitted_literal_bytes = 0;
+       for (int pos = 0; pos < int(input.size()); ++pos) {
+               int bytes_left = input.size() - pos;
+
+               if (bytes_left < 3) {
+                       // Can't start a match here.
+                       ++unemitted_literal_bytes;
+                       continue;
+               }
+
+               // See if we can find a short match.
+               int best_match_length = 0;
+               int best_match_distance = -1;
+               uint16_t val16 = read16(&input[pos]);
+               for (int distance = 1; distance <= min(pos, 16); ++distance) {
+                       if (read16(&input[pos - distance]) == val16) {
+                               // A 2-byte match; see if we can extend it.
+                               int match_length = 2 + find_match_length(&input[pos] + 2, &input[pos - distance] + 2, std::min(bytes_left, 34) - 2);
+                               if (match_length > best_match_length) {
+                                       best_match_length = match_length;  // Note: Could now be a long match.
+                                       best_match_distance = distance;
+                               }
+                       }
+               }
+
+               uint32_t val24 = read24(&input[pos]);
+               const auto match_it = match_to_pos.find(val24);
+               if (match_it != match_to_pos.end()) {
+                       int distance = pos - match_it->second;
+                       if (distance <= 1023) {
+                               // A 3-byte match; see if we can extend it.
+                               int match_length = 3 + find_match_length(&input[pos] + 3, &input[pos - distance] + 3, std::min(bytes_left, 34) - 3);
+                               if (match_length > best_match_length) {
+                                       best_match_length = match_length;
+                                       best_match_distance = distance;
+                               }
+                       }
+               }
+               match_to_pos[val24] = pos;
+
+               if (best_match_length == 0) {
+                       ++unemitted_literal_bytes;
+               } else {
+                       // We have a match! First, output the pending literal if there's any.
+                       if (unemitted_literal_bytes > 0) {
+                               output.emit_literal(&input[pos - unemitted_literal_bytes], unemitted_literal_bytes);
+                               unemitted_literal_bytes = 0;
+                       }
+                       output.emit_match(best_match_length, best_match_distance);
+                       pos += best_match_length - 1;
+               }
+       }
+
+       // Output the final literal, if any.
+       if (unemitted_literal_bytes > 0) {
+               output.emit_literal(&input[input.size() - unemitted_literal_bytes], unemitted_literal_bytes);
+       }
+       output.emit_eof();
+
+       return output.data;
+}