From 6403c957e0fa9ecda28586be75eef54dede6d9c4 Mon Sep 17 00:00:00 2001 From: Alberto Donizetti Date: Sun, 23 Aug 2015 17:15:07 +0200 Subject: [PATCH] compress/bzip2: make decoding faster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue 6754 reports that Go bzip2 Decode function is much slower (about 2.5x in go1.5) than the Python equivalent (which is actually just a wrapper around the usual C library) on random data. Profiling the code shows that half a dozen of CMP instructions in a tight loop are responsibile for most of the execution time. This patch reduces the number of branches of the loop, greatly improving performance on random data and speeding up decoding of real data. name old time/op new time/op delta DecodeDigits-4 9.28ms ± 1% 8.05ms ± 1% -13.18% (p=0.000 n=15+14) DecodeTwain-4 28.9ms ± 2% 26.4ms ± 1% -8.57% (p=0.000 n=15+14) DecodeRand-4 3.94ms ± 1% 3.06ms ± 1% -22.45% (p=0.000 n=15+14) name old speed new speed delta DecodeDigits-4 4.65MB/s ± 1% 5.36MB/s ± 1% +15.21% (p=0.000 n=13+14) DecodeTwain-4 4.32MB/s ± 2% 4.72MB/s ± 1% +9.36% (p=0.000 n=15+14) DecodeRand-4 4.27MB/s ± 1% 5.51MB/s ± 1% +28.86% (p=0.000 n=15+14) I've run some benchmark comparing Go bzip2 implementation with the usual Linux bzip2 command (which is written in C). On my machine this patch brings go1.5 from ~2.26x to ~1.50x of bzip2 time (on 64MB random data) from ~1.70x to ~1.50x of bzip2 time (on 100MB english text) from ~2.00x to ~1.88x of bzip2 time (on 64MB /dev/zero data) Fixes #6754 Change-Id: I3cb12d2c0c2243c1617edef1edc88f05f91d26d1 Reviewed-on: https://go-review.googlesource.com/13853 Reviewed-by: Nigel Tao --- src/compress/bzip2/bit_reader.go | 8 ---- src/compress/bzip2/bzip2_test.go | 4 ++ src/compress/bzip2/huffman.go | 44 +++++++++++++------- src/compress/bzip2/testdata/random.data.bz2 | Bin 0 -> 16846 bytes 4 files changed, 32 insertions(+), 24 deletions(-) create mode 100644 src/compress/bzip2/testdata/random.data.bz2 diff --git a/src/compress/bzip2/bit_reader.go b/src/compress/bzip2/bit_reader.go index 32d1036ae1b..ab1d6065143 100644 --- a/src/compress/bzip2/bit_reader.go +++ b/src/compress/bzip2/bit_reader.go @@ -77,14 +77,6 @@ func (br *bitReader) ReadBit() bool { return n != 0 } -func (br *bitReader) TryReadBit() (bit byte, ok bool) { - if br.bits > 0 { - br.bits-- - return byte(br.n>>br.bits) & 1, true - } - return 0, false -} - func (br *bitReader) Err() error { return br.err } diff --git a/src/compress/bzip2/bzip2_test.go b/src/compress/bzip2/bzip2_test.go index 77c50dfe948..c315e70b9c3 100644 --- a/src/compress/bzip2/bzip2_test.go +++ b/src/compress/bzip2/bzip2_test.go @@ -173,6 +173,7 @@ const rand3Hex = "1744b384d68c042371244e13500d4bfb98c6244e3d71a5b700224420b59c59 const ( digits = iota twain + random ) var testfiles = []string{ @@ -182,6 +183,8 @@ var testfiles = []string{ digits: "testdata/e.txt.bz2", // Twain is Project Gutenberg's edition of Mark Twain's classic English novel. twain: "testdata/Mark.Twain-Tom.Sawyer.txt.bz2", + // 16KB of random data from /dev/urandom + random: "testdata/random.data.bz2", } func benchmarkDecode(b *testing.B, testfile int) { @@ -198,6 +201,7 @@ func benchmarkDecode(b *testing.B, testfile int) { func BenchmarkDecodeDigits(b *testing.B) { benchmarkDecode(b, digits) } func BenchmarkDecodeTwain(b *testing.B) { benchmarkDecode(b, twain) } +func BenchmarkDecodeRand(b *testing.B) { benchmarkDecode(b, random) } func TestBufferOverrun(t *testing.T) { // Tests https://golang.org/issue/5747. diff --git a/src/compress/bzip2/huffman.go b/src/compress/bzip2/huffman.go index 75a6223d813..9d574b9bdef 100644 --- a/src/compress/bzip2/huffman.go +++ b/src/compress/bzip2/huffman.go @@ -38,23 +38,35 @@ func (t *huffmanTree) Decode(br *bitReader) (v uint16) { for { node := &t.nodes[nodeIndex] - bit, ok := br.TryReadBit() - if !ok && br.ReadBit() { - bit = 1 - } - // bzip2 encodes left as a true bit. - if bit != 0 { - // left - if node.left == invalidNodeValue { - return node.leftValue - } - nodeIndex = node.left + + var bit uint16 + if br.bits > 0 { + // Get next bit - fast path. + br.bits-- + bit = 0 - (uint16(br.n>>br.bits) & 1) } else { - // right - if node.right == invalidNodeValue { - return node.rightValue - } - nodeIndex = node.right + // Get next bit - slow path. + // Use ReadBits to retrieve a single bit + // from the underling io.ByteReader. + bit = 0 - uint16(br.ReadBits(1)) + } + // now + // bit = 0xffff if the next bit was 1 + // bit = 0x0000 if the next bit was 0 + + // 1 means left, 0 means right. + // + // if bit == 0xffff { + // nodeIndex = node.left + // } else { + // nodeIndex = node.right + // } + nodeIndex = (bit & node.left) | (^bit & node.right) + + if nodeIndex == invalidNodeValue { + // We found a leaf. Use the value of bit to decide + // whether is a left or a right value. + return (bit & node.leftValue) | (^bit & node.rightValue) } } } diff --git a/src/compress/bzip2/testdata/random.data.bz2 b/src/compress/bzip2/testdata/random.data.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..1ef23001668181e139bee59ebe5f5cd9e9b8b8c9 GIT binary patch literal 16846 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 literal 0 HcmV?d00001