Skip to content

Commit 3a785d3

Browse files
committed
chore: add fast crc32c concat implementation
This new implementation provides a constant time computation regardless of the length of B, rather than the previous implementation which took linear time relative to the length of B. #### Benchmark summary CPU: AMD Ryzen Threadripper PRO 3945WX 12-Cores OS: Debian Testing ``` # JMH version: 1.37 # VM version: JDK 11.0.17, OpenJDK 64-Bit Server VM, 11.0.17+8 # VM invoker: /home/benwhitehead/opt/java/jdk-11.0.17+8/bin/java # VM options: -Xms1g -Xmx1g -XX:+AlwaysPreTouch # Blackhole mode: full + dont-inline hint (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) # Warmup: 4 iterations, 30 s each # Measurement: 8 iterations, 30 s each # Timeout: 10 min per iteration # Threads: 1 thread, will synchronize iterations # Benchmark mode: Sampling time ``` ``` Benchmark (objectSize) Mode Cnt Score Error Units Crc32cBenchmark.concatCrc32c 131072 sample 9243752 0.074 ± 0.001 us/op Crc32cBenchmark.concatCrc32c 262144 sample 9015106 0.078 ± 0.001 us/op Crc32cBenchmark.concatCrc32c 524288 sample 9010257 0.074 ± 0.001 us/op Crc32cBenchmark.concatCrc32c 1048576 sample 9165519 0.074 ± 0.001 us/op Crc32cBenchmark.concatCrc32c 2097152 sample 8993038 0.075 ± 0.001 us/op Crc32cBenchmark.crc32cCombineGoogle 131072 sample 2070639 115.806 ± 0.018 us/op Crc32cBenchmark.crc32cCombineGoogle 262144 sample 1029209 233.019 ± 0.045 us/op Crc32cBenchmark.crc32cCombineGoogle 524288 sample 516517 464.360 ± 0.106 us/op Crc32cBenchmark.crc32cCombineGoogle 1048576 sample 262118 915.067 ± 0.354 us/op Crc32cBenchmark.crc32cCombineGoogle 2097152 sample 131385 1825.685 ± 0.834 us/op ```
1 parent 74b7498 commit 3a785d3

4 files changed

Lines changed: 66 additions & 285 deletions

File tree

google-cloud-storage/src/main/java/com/google/cloud/storage/Crc32cUtility.java

Lines changed: 61 additions & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -19,288 +19,70 @@
1919
class Crc32cUtility {
2020
private Crc32cUtility() {}
2121

22+
// Castagnoli polynomial and its degree.
23+
private static final long CASTAGNOLI_POLY = 4812730177L;
24+
private static final int DEGREE = 32;
25+
26+
// Table storing values of x^(2^k) mod CASTANOLI_POLY for all k < 31. This is sufficient since
27+
// x^(2^31) = x.
28+
private static final long[] X_POW_2K_TABLE = {
29+
2L, 4L, 16L, 256L, 65536L, 517762881L, 984302966L,
30+
408362264L, 1503875210L, 2862076957L, 3884826397L, 1324787473L, 621200174L, 1758783527L,
31+
1416537776L, 1180494764L, 648569364L, 2521473789L, 994858823L, 1728245375L, 3498467999L,
32+
4059169852L, 3345064394L, 2828422810L, 2429203150L, 3336788029L, 860151998L, 2102628683L,
33+
1033187991L, 4243778976L, 1123580069L
34+
};
35+
36+
// Multiplies two polynomials together modulo CASTAGNOLI_POLY.
37+
private static int multiply(int p, int q) {
38+
long q64 = q;
39+
int result = 0;
40+
long topBit = (1L << DEGREE);
41+
for (int i = 0; i < DEGREE; i++) {
42+
if ((p & 1) != 0) {
43+
result ^= (int) q64;
44+
}
45+
q64 <<= 1; // Multiply by x.
46+
47+
// If multiplying by x gave q64 a non-zero 32nd coefficient, it no longer encodes the desired
48+
// representative of that polynomial modulo CASTAGNOLI_POLY, so we subtract the generator.
49+
if ((q64 & topBit) != 0) {
50+
q64 ^= CASTAGNOLI_POLY;
51+
}
52+
p >>= 1;
53+
}
54+
return result;
55+
}
56+
57+
// Given crc representing polynomial P(x), compute P(x)*x^numBits.
58+
private static int extendByZeros(int crc, long numBits) {
59+
// Begin by reversing the bits to most-significant coefficient first for comprehensibility.
60+
crc = Integer.reverse(crc);
61+
int i = 0;
62+
// Iterate over the binary representation of numBits, multiplying by x^(2^k) for numBits_k = 1.
63+
while (numBits != 0) {
64+
if ((numBits & 1) != 0) {
65+
crc = multiply(crc, (int) X_POW_2K_TABLE[i % X_POW_2K_TABLE.length]);
66+
}
67+
i += 1;
68+
numBits >>= 1;
69+
}
70+
crc = Integer.reverse(crc); // Return to the standard bit-order.
71+
return crc;
72+
}
73+
2274
/**
23-
* Straight forward implementation to combine two crc32c values
75+
* Efficiently computes CRC32C for concat(A, B) given crc(A), crc(B) and len(B).
2476
*
25-
* <p>There is a more efficient implementation in https://github.com/google/crc32c, however for
26-
* brevity we are following the byte by byte computation, implemented in:
27-
* https://github.com/google/crc32c/blob/main/src/crc32c_portable.cc#L252-L257
28-
*
29-
* @param crc1 crc32c int of an object to combine crc2 with
30-
* @param crc2 crc32c int of the second object
31-
* @param crc2ObjectSize byte length of object which generated crc2
32-
* @return
33-
* @see <a target="_blank"
34-
* href="https://github.com/google/crc32c/blob/21fc8ef30415a635e7351ffa0e5d5367943d4a94/src/crc32c_portable.cc#L252-L257">https://github.com/google/crc32c/blob/21fc8ef30415a635e7351ffa0e5d5367943d4a94/src/crc32c_portable.cc#L252-L257</a>
77+
* @param crcA A 32-bit integer representing crc(A) with least-significant coefficient first.
78+
* @param crcB Same as crcA for B.
79+
* @param numBytesInB Length of B in bytes.
80+
* @return CRC32C for concat(A, B) PiperOrigin-RevId: 158626905
3581
*/
36-
public static int crc32cCombineGoogle(int crc1, int crc2, long crc2ObjectSize) {
37-
int l = crc1;
38-
// run number of 0's based on size
39-
for (long i = 0; i < crc2ObjectSize; i++) {
40-
l = (l >>> 8) ^ kByteExtensionTable[l & 0xff];
82+
public static int concatCrc32c(int crcA, int crcB, long numBytesInB) {
83+
if (numBytesInB == 0) {
84+
return crcA;
4185
}
42-
return l ^ crc2;
86+
return extendByZeros(crcA, 8 * numBytesInB) ^ crcB;
4387
}
44-
45-
// Ported from:
46-
// https://github.com/google/crc32c/blob/21fc8ef30415a635e7351ffa0e5d5367943d4a94/src/crc32c_portable.cc#L16-L59
47-
private static final int[] kByteExtensionTable =
48-
new int[] {
49-
0x00000000,
50-
0xf26b8303,
51-
0xe13b70f7,
52-
0x1350f3f4,
53-
0xc79a971f,
54-
0x35f1141c,
55-
0x26a1e7e8,
56-
0xd4ca64eb,
57-
0x8ad958cf,
58-
0x78b2dbcc,
59-
0x6be22838,
60-
0x9989ab3b,
61-
0x4d43cfd0,
62-
0xbf284cd3,
63-
0xac78bf27,
64-
0x5e133c24,
65-
0x105ec76f,
66-
0xe235446c,
67-
0xf165b798,
68-
0x030e349b,
69-
0xd7c45070,
70-
0x25afd373,
71-
0x36ff2087,
72-
0xc494a384,
73-
0x9a879fa0,
74-
0x68ec1ca3,
75-
0x7bbcef57,
76-
0x89d76c54,
77-
0x5d1d08bf,
78-
0xaf768bbc,
79-
0xbc267848,
80-
0x4e4dfb4b,
81-
0x20bd8ede,
82-
0xd2d60ddd,
83-
0xc186fe29,
84-
0x33ed7d2a,
85-
0xe72719c1,
86-
0x154c9ac2,
87-
0x061c6936,
88-
0xf477ea35,
89-
0xaa64d611,
90-
0x580f5512,
91-
0x4b5fa6e6,
92-
0xb93425e5,
93-
0x6dfe410e,
94-
0x9f95c20d,
95-
0x8cc531f9,
96-
0x7eaeb2fa,
97-
0x30e349b1,
98-
0xc288cab2,
99-
0xd1d83946,
100-
0x23b3ba45,
101-
0xf779deae,
102-
0x05125dad,
103-
0x1642ae59,
104-
0xe4292d5a,
105-
0xba3a117e,
106-
0x4851927d,
107-
0x5b016189,
108-
0xa96ae28a,
109-
0x7da08661,
110-
0x8fcb0562,
111-
0x9c9bf696,
112-
0x6ef07595,
113-
0x417b1dbc,
114-
0xb3109ebf,
115-
0xa0406d4b,
116-
0x522bee48,
117-
0x86e18aa3,
118-
0x748a09a0,
119-
0x67dafa54,
120-
0x95b17957,
121-
0xcba24573,
122-
0x39c9c670,
123-
0x2a993584,
124-
0xd8f2b687,
125-
0x0c38d26c,
126-
0xfe53516f,
127-
0xed03a29b,
128-
0x1f682198,
129-
0x5125dad3,
130-
0xa34e59d0,
131-
0xb01eaa24,
132-
0x42752927,
133-
0x96bf4dcc,
134-
0x64d4cecf,
135-
0x77843d3b,
136-
0x85efbe38,
137-
0xdbfc821c,
138-
0x2997011f,
139-
0x3ac7f2eb,
140-
0xc8ac71e8,
141-
0x1c661503,
142-
0xee0d9600,
143-
0xfd5d65f4,
144-
0x0f36e6f7,
145-
0x61c69362,
146-
0x93ad1061,
147-
0x80fde395,
148-
0x72966096,
149-
0xa65c047d,
150-
0x5437877e,
151-
0x4767748a,
152-
0xb50cf789,
153-
0xeb1fcbad,
154-
0x197448ae,
155-
0x0a24bb5a,
156-
0xf84f3859,
157-
0x2c855cb2,
158-
0xdeeedfb1,
159-
0xcdbe2c45,
160-
0x3fd5af46,
161-
0x7198540d,
162-
0x83f3d70e,
163-
0x90a324fa,
164-
0x62c8a7f9,
165-
0xb602c312,
166-
0x44694011,
167-
0x5739b3e5,
168-
0xa55230e6,
169-
0xfb410cc2,
170-
0x092a8fc1,
171-
0x1a7a7c35,
172-
0xe811ff36,
173-
0x3cdb9bdd,
174-
0xceb018de,
175-
0xdde0eb2a,
176-
0x2f8b6829,
177-
0x82f63b78,
178-
0x709db87b,
179-
0x63cd4b8f,
180-
0x91a6c88c,
181-
0x456cac67,
182-
0xb7072f64,
183-
0xa457dc90,
184-
0x563c5f93,
185-
0x082f63b7,
186-
0xfa44e0b4,
187-
0xe9141340,
188-
0x1b7f9043,
189-
0xcfb5f4a8,
190-
0x3dde77ab,
191-
0x2e8e845f,
192-
0xdce5075c,
193-
0x92a8fc17,
194-
0x60c37f14,
195-
0x73938ce0,
196-
0x81f80fe3,
197-
0x55326b08,
198-
0xa759e80b,
199-
0xb4091bff,
200-
0x466298fc,
201-
0x1871a4d8,
202-
0xea1a27db,
203-
0xf94ad42f,
204-
0x0b21572c,
205-
0xdfeb33c7,
206-
0x2d80b0c4,
207-
0x3ed04330,
208-
0xccbbc033,
209-
0xa24bb5a6,
210-
0x502036a5,
211-
0x4370c551,
212-
0xb11b4652,
213-
0x65d122b9,
214-
0x97baa1ba,
215-
0x84ea524e,
216-
0x7681d14d,
217-
0x2892ed69,
218-
0xdaf96e6a,
219-
0xc9a99d9e,
220-
0x3bc21e9d,
221-
0xef087a76,
222-
0x1d63f975,
223-
0x0e330a81,
224-
0xfc588982,
225-
0xb21572c9,
226-
0x407ef1ca,
227-
0x532e023e,
228-
0xa145813d,
229-
0x758fe5d6,
230-
0x87e466d5,
231-
0x94b49521,
232-
0x66df1622,
233-
0x38cc2a06,
234-
0xcaa7a905,
235-
0xd9f75af1,
236-
0x2b9cd9f2,
237-
0xff56bd19,
238-
0x0d3d3e1a,
239-
0x1e6dcdee,
240-
0xec064eed,
241-
0xc38d26c4,
242-
0x31e6a5c7,
243-
0x22b65633,
244-
0xd0ddd530,
245-
0x0417b1db,
246-
0xf67c32d8,
247-
0xe52cc12c,
248-
0x1747422f,
249-
0x49547e0b,
250-
0xbb3ffd08,
251-
0xa86f0efc,
252-
0x5a048dff,
253-
0x8ecee914,
254-
0x7ca56a17,
255-
0x6ff599e3,
256-
0x9d9e1ae0,
257-
0xd3d3e1ab,
258-
0x21b862a8,
259-
0x32e8915c,
260-
0xc083125f,
261-
0x144976b4,
262-
0xe622f5b7,
263-
0xf5720643,
264-
0x07198540,
265-
0x590ab964,
266-
0xab613a67,
267-
0xb831c993,
268-
0x4a5a4a90,
269-
0x9e902e7b,
270-
0x6cfbad78,
271-
0x7fab5e8c,
272-
0x8dc0dd8f,
273-
0xe330a81a,
274-
0x115b2b19,
275-
0x020bd8ed,
276-
0xf0605bee,
277-
0x24aa3f05,
278-
0xd6c1bc06,
279-
0xc5914ff2,
280-
0x37faccf1,
281-
0x69e9f0d5,
282-
0x9b8273d6,
283-
0x88d28022,
284-
0x7ab90321,
285-
0xae7367ca,
286-
0x5c18e4c9,
287-
0x4f48173d,
288-
0xbd23943e,
289-
0xf36e6f75,
290-
0x0105ec76,
291-
0x12551f82,
292-
0xe03e9c81,
293-
0x34f4f86a,
294-
0xc69f7b69,
295-
0xd5cf889d,
296-
0x27a40b9e,
297-
0x79b737ba,
298-
0x8bdcb4b9,
299-
0x988c474d,
300-
0x6ae7c44e,
301-
0xbe2da0a5,
302-
0x4c4623a6,
303-
0x5f16d052,
304-
0xad7d5351
305-
};
30688
}

google-cloud-storage/src/main/java/com/google/cloud/storage/Crc32cValue.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public int getValue() {
8181

8282
@Override
8383
public Crc32cLengthUnknown concat(Crc32cLengthKnown other) {
84-
int combined = Crc32cUtility.crc32cCombineGoogle(value, other.value, other.length);
84+
int combined = Crc32cUtility.concatCrc32c(value, other.value, other.length);
8585
return new Crc32cLengthUnknown(combined);
8686
}
8787

@@ -137,7 +137,7 @@ public long getLength() {
137137

138138
@Override
139139
public Crc32cLengthKnown concat(Crc32cLengthKnown other) {
140-
int combined = Crc32cUtility.crc32cCombineGoogle(value, other.value, other.length);
140+
int combined = Crc32cUtility.concatCrc32c(value, other.value, other.length);
141141
return new Crc32cLengthKnown(combined, length + other.length);
142142
}
143143

google-cloud-storage/src/test/java/com/google/cloud/storage/Crc32cUtilityPropertyTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ public void testCrc32cCombinePropertyTest(
2929
int secondPartHash = Hashing.crc32c().hashBytes(secondObject).asInt();
3030
int expected =
3131
Hashing.crc32c().newHasher().putBytes(firstObject).putBytes(secondObject).hash().asInt();
32-
int actual =
33-
Crc32cUtility.crc32cCombineGoogle(firstPartHash, secondPartHash, secondObject.length);
32+
int actual = Crc32cUtility.concatCrc32c(firstPartHash, secondPartHash, secondObject.length);
3433
assertThat(actual).isEqualTo(expected);
3534
}
3635
}

google-cloud-storage/src/test/java/com/google/cloud/storage/Crc32cUtilityTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public void testCrc32cCombine() {
3030
int object2_hash = 0x31AA814E;
3131
// length("world") -> 5
3232
int object2_size = 5;
33-
int combined = Crc32cUtility.crc32cCombineGoogle(object1_hash, object2_hash, object2_size);
33+
int combined = Crc32cUtility.concatCrc32c(object1_hash, object2_hash, object2_size);
3434
Assert.assertEquals(expected, combined);
3535
}
3636

@@ -42,7 +42,7 @@ public void testCrc32cCombineGuavaValues() {
4242
int expected = Hashing.crc32c().hashBytes(helloWorld.getBytes()).asInt();
4343
int object1Hash = Hashing.crc32c().hashBytes(hello.getBytes()).asInt();
4444
int object2Hash = Hashing.crc32c().hashBytes(world.getBytes()).asInt();
45-
int combined = Crc32cUtility.crc32cCombineGoogle(object1Hash, object2Hash, world.length());
45+
int combined = Crc32cUtility.concatCrc32c(object1Hash, object2Hash, world.length());
4646
Assert.assertEquals(expected, combined);
4747
}
4848
}

0 commit comments

Comments
 (0)