Skip to content

Commit

Permalink
Use Java's hardware-accelerated CRC32C implementation where available.
Browse files Browse the repository at this point in the history
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: 539722059
  • Loading branch information
cpovirk authored and Google Java Core Libraries committed Jun 13, 2023
1 parent 501a016 commit e9583ab
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Random;
import java.util.zip.Adler32;
import java.util.zip.CRC32;
import java.util.zip.CRC32C;
import java.util.zip.Checksum;

/**
Expand Down Expand Up @@ -69,6 +70,24 @@ byte crc32Checksum(int reps) throws Exception {
return result;
}

// CRC32C

@Benchmark
byte crc32cHashFunction(int reps) {
return runHashFunction(reps, Hashing.crc32c());
}

@Benchmark
byte crc32cChecksum(int reps) throws Exception {
byte result = 0x01;
for (int i = 0; i < reps; i++) {
CRC32C checksum = new CRC32C();
checksum.update(testBytes, 0, testBytes.length);
result = (byte) (result ^ checksum.getValue());
}
return result;
}

// Adler32

@Benchmark
Expand Down
30 changes: 30 additions & 0 deletions android/guava/src/com/google/common/hash/IgnoreJRERequirement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2019 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

package com.google.common.hash;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Target;

/**
* Disables Animal Sniffer's checking of compatibility with older versions of Java/Android.
*
* <p>Each package's copy of this annotation needs to be listed in our {@code pom.xml}.
*/
@Target({METHOD, CONSTRUCTOR, TYPE})
@ElementTypesAreNonnullByDefault
@interface IgnoreJRERequirement {}
2 changes: 1 addition & 1 deletion android/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.23</version>
<configuration>
<annotations>com.google.common.io.IgnoreJRERequirement,com.google.common.reflect.IgnoreJRERequirement,com.google.common.testing.IgnoreJRERequirement</annotations>
<annotations>com.google.common.hash.IgnoreJRERequirement,com.google.common.io.IgnoreJRERequirement,com.google.common.reflect.IgnoreJRERequirement,com.google.common.testing.IgnoreJRERequirement</annotations>
<checkTestClasses>true</checkTestClasses>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Random;
import java.util.zip.Adler32;
import java.util.zip.CRC32;
import java.util.zip.CRC32C;
import java.util.zip.Checksum;

/**
Expand Down Expand Up @@ -69,6 +70,24 @@ byte crc32Checksum(int reps) throws Exception {
return result;
}

// CRC32C

@Benchmark
byte crc32cHashFunction(int reps) {
return runHashFunction(reps, Hashing.crc32c());
}

@Benchmark
byte crc32cChecksum(int reps) throws Exception {
byte result = 0x01;
for (int i = 0; i < reps; i++) {
CRC32C checksum = new CRC32C();
checksum.update(testBytes, 0, testBytes.length);
result = (byte) (result ^ checksum.getValue());
}
return result;
}

// Adler32

@Benchmark
Expand Down
86 changes: 85 additions & 1 deletion guava/src/com/google/common/hash/Hashing.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.throwIfUnchecked;
import static java.lang.invoke.MethodType.methodType;

import com.google.errorprone.annotations.Immutable;
import com.google.j2objc.annotations.J2ObjCIncompatible;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.security.Key;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -399,14 +404,40 @@ public static HashFunction crc32c() {

@Immutable
private enum Crc32CSupplier implements ImmutableSupplier<HashFunction> {
@J2ObjCIncompatible
JAVA_UTIL_ZIP {
@Override
public HashFunction get() {
return ChecksumType.CRC_32C.hashFunction;
}
},
ABSTRACT_HASH_FUNCTION {
@Override
public HashFunction get() {
return Crc32cHashFunction.CRC_32_C;
}
};

static final HashFunction HASH_FUNCTION = values()[0].get();
static final HashFunction HASH_FUNCTION = pickFunction().get();

private static Crc32CSupplier pickFunction() {
Crc32CSupplier[] functions = values();

if (functions.length == 1) {
// We're running under J2ObjC.
return functions[0];
}

// We can't refer to JAVA_UTIL_ZIP directly at compile time because of J2ObjC.
Crc32CSupplier javaUtilZip = functions[0];

try {
Class.forName("java.util.zip.CRC32C");
return javaUtilZip;
} catch (ClassNotFoundException runningUnderJava8) {
return ABSTRACT_HASH_FUNCTION;
}
}
}

/**
Expand Down Expand Up @@ -449,6 +480,13 @@ public Checksum get() {
return new CRC32();
}
},
@J2ObjCIncompatible
CRC_32C("Hashing.crc32c()") {
@Override
public Checksum get() {
return Crc32cMethodHandles.newCrc32c();
}
},
ADLER_32("Hashing.adler32()") {
@Override
public Checksum get() {
Expand All @@ -463,6 +501,52 @@ public Checksum get() {
}
}

@J2ObjCIncompatible
@SuppressWarnings("unused")
private static final class Crc32cMethodHandles {
private static final MethodHandle CONSTRUCTOR = crc32cConstructor();

@IgnoreJRERequirement // https://github.com/mojohaus/animal-sniffer/issues/67
static Checksum newCrc32c() {
try {
return (Checksum) CONSTRUCTOR.invokeExact();
} catch (Throwable e) {
throwIfUnchecked(e);
// That constructor has no `throws` clause.
throw newLinkageError(e);
}
}

private static MethodHandle crc32cConstructor() {
try {
Class<?> clazz = Class.forName("java.util.zip.CRC32C");
/*
* We can't cast to CRC32C at the call site because we support building with Java 8
* (https://github.com/google/guava/issues/6549). So we have to use asType() to change from
* CRC32C to Checksum. This may carry some performance cost
* (https://stackoverflow.com/a/22321671/28465), but I'd have to benchmark more carefully to
* even detect it.
*/
return MethodHandles.lookup()
.findConstructor(clazz, methodType(void.class))
.asType(methodType(Checksum.class));
} catch (ClassNotFoundException e) {
// We check that the class is available before calling this method.
throw new AssertionError(e);
} catch (IllegalAccessException e) {
// That API is public.
throw newLinkageError(e);
} catch (NoSuchMethodException e) {
// That constructor exists.
throw newLinkageError(e);
}
}

private static LinkageError newLinkageError(Throwable cause) {
return new LinkageError(cause.toString(), cause);
}
}

/**
* Returns a hash function implementing FarmHash's Fingerprint64, an open-source algorithm.
*
Expand Down
30 changes: 30 additions & 0 deletions guava/src/com/google/common/hash/IgnoreJRERequirement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2019 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

package com.google.common.hash;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Target;

/**
* Disables Animal Sniffer's checking of compatibility with older versions of Java/Android.
*
* <p>Each package's copy of this annotation needs to be listed in our {@code pom.xml}.
*/
@Target({METHOD, CONSTRUCTOR, TYPE})
@ElementTypesAreNonnullByDefault
@interface IgnoreJRERequirement {}
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.23</version>
<configuration>
<annotations>com.google.common.io.IgnoreJRERequirement,com.google.common.reflect.IgnoreJRERequirement,com.google.common.testing.IgnoreJRERequirement</annotations>
<annotations>com.google.common.hash.IgnoreJRERequirement,com.google.common.io.IgnoreJRERequirement,com.google.common.reflect.IgnoreJRERequirement,com.google.common.testing.IgnoreJRERequirement</annotations>
<checkTestClasses>true</checkTestClasses>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
Expand Down

0 comments on commit e9583ab

Please sign in to comment.