Skip to content

Commit 65c7f10

Browse files
cpovirkGoogle Java Core Libraries
authored and
Google Java Core Libraries
committedJun 13, 2023
Use Java's hardware-accelerated CRC32C implementation where available.
This is the first use of a Java 9 API in Guava, but we use the API only when it's available, so we maintain compatibility with Java 8. Use of Java 9 APIs is relevant to #6549 and #3990 (and also mojohaus/animal-sniffer#67). I didn't make the same change for `guava-android`, which [will add `java.util.zip.CRC32C` in API Level 34](https://developer.android.com/reference/java/util/zip/CRC32C). I don't know if Android is providing similar performance improvements, so it might not even matter. But even if I wanted to do it, I can't with my current approach, which relies on `MethodHandle`—unless I want to make even the usage of `MethodHandle` conditional on a reflective check :) RELNOTES=`hash`: Enhanced `crc32c()` to use Java's hardware-accelerated implementation where available. PiperOrigin-RevId: 539983316
1 parent 501a016 commit 65c7f10

File tree

7 files changed

+161
-3
lines changed

7 files changed

+161
-3
lines changed
 

‎android/guava-tests/benchmark/com/google/common/hash/ChecksumBenchmark.java

+7
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ byte crc32Checksum(int reps) throws Exception {
6969
return result;
7070
}
7171

72+
// CRC32C
73+
74+
@Benchmark
75+
byte crc32cHashFunction(int reps) {
76+
return runHashFunction(reps, Hashing.crc32c());
77+
}
78+
7279
// Adler32
7380

7481
@Benchmark
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2019 The Guava Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.common.hash;
16+
17+
import static java.lang.annotation.ElementType.CONSTRUCTOR;
18+
import static java.lang.annotation.ElementType.METHOD;
19+
import static java.lang.annotation.ElementType.TYPE;
20+
21+
import java.lang.annotation.Target;
22+
23+
/**
24+
* Disables Animal Sniffer's checking of compatibility with older versions of Java/Android.
25+
*
26+
* <p>Each package's copy of this annotation needs to be listed in our {@code pom.xml}.
27+
*/
28+
@Target({METHOD, CONSTRUCTOR, TYPE})
29+
@ElementTypesAreNonnullByDefault
30+
@interface IgnoreJRERequirement {}

‎android/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176
<artifactId>animal-sniffer-maven-plugin</artifactId>
177177
<version>1.23</version>
178178
<configuration>
179-
<annotations>com.google.common.io.IgnoreJRERequirement,com.google.common.reflect.IgnoreJRERequirement,com.google.common.testing.IgnoreJRERequirement</annotations>
179+
<annotations>com.google.common.hash.IgnoreJRERequirement,com.google.common.io.IgnoreJRERequirement,com.google.common.reflect.IgnoreJRERequirement,com.google.common.testing.IgnoreJRERequirement</annotations>
180180
<checkTestClasses>true</checkTestClasses>
181181
<signature>
182182
<groupId>org.codehaus.mojo.signature</groupId>

‎guava-tests/benchmark/com/google/common/hash/ChecksumBenchmark.java

+7
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ byte crc32Checksum(int reps) throws Exception {
6969
return result;
7070
}
7171

72+
// CRC32C
73+
74+
@Benchmark
75+
byte crc32cHashFunction(int reps) {
76+
return runHashFunction(reps, Hashing.crc32c());
77+
}
78+
7279
// Adler32
7380

7481
@Benchmark

‎guava/src/com/google/common/hash/Hashing.java

+85-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616

1717
import static com.google.common.base.Preconditions.checkArgument;
1818
import static com.google.common.base.Preconditions.checkNotNull;
19+
import static com.google.common.base.Throwables.throwIfUnchecked;
20+
import static java.lang.invoke.MethodType.methodType;
1921

2022
import com.google.errorprone.annotations.Immutable;
23+
import com.google.j2objc.annotations.J2ObjCIncompatible;
24+
import java.lang.invoke.MethodHandle;
25+
import java.lang.invoke.MethodHandles;
2126
import java.security.Key;
2227
import java.util.ArrayList;
2328
import java.util.Arrays;
@@ -399,14 +404,40 @@ public static HashFunction crc32c() {
399404

400405
@Immutable
401406
private enum Crc32CSupplier implements ImmutableSupplier<HashFunction> {
407+
@J2ObjCIncompatible
408+
JAVA_UTIL_ZIP {
409+
@Override
410+
public HashFunction get() {
411+
return ChecksumType.CRC_32C.hashFunction;
412+
}
413+
},
402414
ABSTRACT_HASH_FUNCTION {
403415
@Override
404416
public HashFunction get() {
405417
return Crc32cHashFunction.CRC_32_C;
406418
}
407419
};
408420

409-
static final HashFunction HASH_FUNCTION = values()[0].get();
421+
static final HashFunction HASH_FUNCTION = pickFunction().get();
422+
423+
private static Crc32CSupplier pickFunction() {
424+
Crc32CSupplier[] functions = values();
425+
426+
if (functions.length == 1) {
427+
// We're running under J2ObjC.
428+
return functions[0];
429+
}
430+
431+
// We can't refer to JAVA_UTIL_ZIP directly at compile time because of J2ObjC.
432+
Crc32CSupplier javaUtilZip = functions[0];
433+
434+
try {
435+
Class.forName("java.util.zip.CRC32C");
436+
return javaUtilZip;
437+
} catch (ClassNotFoundException runningUnderJava8) {
438+
return ABSTRACT_HASH_FUNCTION;
439+
}
440+
}
410441
}
411442

412443
/**
@@ -449,6 +480,13 @@ public Checksum get() {
449480
return new CRC32();
450481
}
451482
},
483+
@J2ObjCIncompatible
484+
CRC_32C("Hashing.crc32c()") {
485+
@Override
486+
public Checksum get() {
487+
return Crc32cMethodHandles.newCrc32c();
488+
}
489+
},
452490
ADLER_32("Hashing.adler32()") {
453491
@Override
454492
public Checksum get() {
@@ -463,6 +501,52 @@ public Checksum get() {
463501
}
464502
}
465503

504+
@J2ObjCIncompatible
505+
@SuppressWarnings("unused")
506+
private static final class Crc32cMethodHandles {
507+
private static final MethodHandle CONSTRUCTOR = crc32cConstructor();
508+
509+
@IgnoreJRERequirement // https://github.com/mojohaus/animal-sniffer/issues/67
510+
static Checksum newCrc32c() {
511+
try {
512+
return (Checksum) CONSTRUCTOR.invokeExact();
513+
} catch (Throwable e) {
514+
throwIfUnchecked(e);
515+
// That constructor has no `throws` clause.
516+
throw newLinkageError(e);
517+
}
518+
}
519+
520+
private static MethodHandle crc32cConstructor() {
521+
try {
522+
Class<?> clazz = Class.forName("java.util.zip.CRC32C");
523+
/*
524+
* We can't cast to CRC32C at the call site because we support building with Java 8
525+
* (https://github.com/google/guava/issues/6549). So we have to use asType() to change from
526+
* CRC32C to Checksum. This may carry some performance cost
527+
* (https://stackoverflow.com/a/22321671/28465), but I'd have to benchmark more carefully to
528+
* even detect it.
529+
*/
530+
return MethodHandles.lookup()
531+
.findConstructor(clazz, methodType(void.class))
532+
.asType(methodType(Checksum.class));
533+
} catch (ClassNotFoundException e) {
534+
// We check that the class is available before calling this method.
535+
throw new AssertionError(e);
536+
} catch (IllegalAccessException e) {
537+
// That API is public.
538+
throw newLinkageError(e);
539+
} catch (NoSuchMethodException e) {
540+
// That constructor exists.
541+
throw newLinkageError(e);
542+
}
543+
}
544+
545+
private static LinkageError newLinkageError(Throwable cause) {
546+
return new LinkageError(cause.toString(), cause);
547+
}
548+
}
549+
466550
/**
467551
* Returns a hash function implementing FarmHash's Fingerprint64, an open-source algorithm.
468552
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2019 The Guava Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.common.hash;
16+
17+
import static java.lang.annotation.ElementType.CONSTRUCTOR;
18+
import static java.lang.annotation.ElementType.METHOD;
19+
import static java.lang.annotation.ElementType.TYPE;
20+
21+
import java.lang.annotation.Target;
22+
23+
/**
24+
* Disables Animal Sniffer's checking of compatibility with older versions of Java/Android.
25+
*
26+
* <p>Each package's copy of this annotation needs to be listed in our {@code pom.xml}.
27+
*/
28+
@Target({METHOD, CONSTRUCTOR, TYPE})
29+
@ElementTypesAreNonnullByDefault
30+
@interface IgnoreJRERequirement {}

‎pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@
177177
<artifactId>animal-sniffer-maven-plugin</artifactId>
178178
<version>1.23</version>
179179
<configuration>
180-
<annotations>com.google.common.io.IgnoreJRERequirement,com.google.common.reflect.IgnoreJRERequirement,com.google.common.testing.IgnoreJRERequirement</annotations>
180+
<annotations>com.google.common.hash.IgnoreJRERequirement,com.google.common.io.IgnoreJRERequirement,com.google.common.reflect.IgnoreJRERequirement,com.google.common.testing.IgnoreJRERequirement</annotations>
181181
<checkTestClasses>true</checkTestClasses>
182182
<signature>
183183
<groupId>org.codehaus.mojo.signature</groupId>

0 commit comments

Comments
 (0)
Please sign in to comment.