package com.bellsw.crc; import static java.util.concurrent.TimeUnit.*; import static org.openjdk.jmh.annotations.Mode.*; import org.openjdk.jmh.annotations.*; import sun.misc.Unsafe; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.reflect.Constructor; import java.nio.ByteOrder; import java.util.Random; import java.util.function.LongSupplier; @BenchmarkMode(AverageTime) @Warmup(iterations = 5, time = 1, timeUnit = SECONDS) @Measurement(iterations = 5, time = 1, timeUnit = SECONDS) @OutputTimeUnit(NANOSECONDS) @Fork(5) @State(Scope.Thread) public class CRC32CAltBench { private final static int size = 512; byte[] bytes; @Setup public void setup() { bytes = new byte[size]; new Random(1).nextBytes(bytes); } @Benchmark @Threads(1) public long calcCRC32C_before() { return updateBytesBefore(0, bytes, 0, bytes.length); } @Benchmark @Threads(1) public long calcCRC32C_after() { return updateBytesAfter(0, bytes, 0, bytes.length); } private static int updateBytesBefore(int crc, byte[] b, int off, int end) { // Do only byte reads for arrays so short they can't be aligned // or if bytes are stored with a larger witdh than one byte.,% if (end - off >= 8 && Unsafe.ARRAY_BYTE_INDEX_SCALE == 1) { // align on 8 bytes int alignLength = (8 - ((Unsafe.ARRAY_BYTE_BASE_OFFSET + off) & 0x7)) & 0x7; for (int alignEnd = off + alignLength; off < alignEnd; off++) { crc = (crc >>> 8) ^ byteTable[(crc ^ b[off]) & 0xFF]; } if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) { crc = Integer.reverseBytes(crc); } // slicing-by-8 for (; off < (end - Long.BYTES); off += Long.BYTES) { int firstHalf; int secondHalf; if (Unsafe.ADDRESS_SIZE == 4) { // On 32 bit platforms read two ints instead of a single 64bit long firstHalf = UNSAFE.getInt(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off); secondHalf = UNSAFE.getInt(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off + Integer.BYTES); } else { long value = UNSAFE.getLong(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off); if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { firstHalf = (int) value; secondHalf = (int) (value >>> 32); } else { // ByteOrder.BIG_ENDIAN firstHalf = (int) (value >>> 32); secondHalf = (int) value; } } crc ^= firstHalf; if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { crc = byteTable7[crc & 0xFF] ^ byteTable6[(crc >>> 8) & 0xFF] ^ byteTable5[(crc >>> 16) & 0xFF] ^ byteTable4[crc >>> 24] ^ byteTable3[secondHalf & 0xFF] ^ byteTable2[(secondHalf >>> 8) & 0xFF] ^ byteTable1[(secondHalf >>> 16) & 0xFF] ^ byteTable0[secondHalf >>> 24]; } else { // ByteOrder.BIG_ENDIAN crc = byteTable0[secondHalf & 0xFF] ^ byteTable1[(secondHalf >>> 8) & 0xFF] ^ byteTable2[(secondHalf >>> 16) & 0xFF] ^ byteTable3[secondHalf >>> 24] ^ byteTable4[crc & 0xFF] ^ byteTable5[(crc >>> 8) & 0xFF] ^ byteTable6[(crc >>> 16) & 0xFF] ^ byteTable7[crc >>> 24]; } } if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) { crc = Integer.reverseBytes(crc); } } // Tail for (; off < end; off++) { crc = (crc >>> 8) ^ byteTable[(crc ^ b[off]) & 0xFF]; } return crc; } private static int updateBytesAfter(int crc, byte[] b, int off, int end) { // Do only byte reads for arrays so short they can't be aligned // or if bytes are stored with a larger witdh than one byte.,% if (end - off >= 8 && Unsafe.ARRAY_BYTE_INDEX_SCALE == 1) { // align on 8 bytes int alignLength = (8 - ((Unsafe.ARRAY_BYTE_BASE_OFFSET + off) & 0x7)) & 0x7; for (int alignEnd = off + alignLength; off < alignEnd; off++) { crc = (crc >>> 8) ^ byteTable[(crc ^ b[off]) & 0xFF]; } if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) { crc = Integer.reverseBytes(crc); // slicing-by-8 for (; off < (end - Long.BYTES); off += Long.BYTES) { int firstHalf; int secondHalf; if (Unsafe.ADDRESS_SIZE == 4) { // On 32 bit platforms read two ints instead of a single 64bit long firstHalf = UNSAFE.getInt(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off); secondHalf = UNSAFE.getInt(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off + Integer.BYTES); } else { long value = UNSAFE.getLong(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off); firstHalf = (int) (value >>> 32); secondHalf = (int) value; } crc ^= firstHalf; int xor1 = byteTable0[secondHalf & 0xFF] ^ byteTable1[(secondHalf >>> 8) & 0xFF]; int xor2 = byteTable2[(secondHalf >>> 16) & 0xFF] ^ byteTable3[secondHalf >>> 24]; int xor3 = byteTable4[crc & 0xFF] ^ byteTable5[(crc >>> 8) & 0xFF]; int xor4 = byteTable6[(crc >>> 16) & 0xFF] ^ byteTable7[crc >>> 24]; crc = xor1 ^ xor2 ^ xor3 ^ xor4; } crc = Integer.reverseBytes(crc); } else { // ByteOrder.LITTLE_ENDIAN // slicing-by-8 for (; off < (end - Long.BYTES); off += Long.BYTES) { int firstHalf; int secondHalf; if (Unsafe.ADDRESS_SIZE == 4) { // On 32 bit platforms read two ints instead of a single 64bit long firstHalf = UNSAFE.getInt(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off); secondHalf = UNSAFE.getInt(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off + Integer.BYTES); } else { long value = UNSAFE.getLong(b, (long)Unsafe.ARRAY_BYTE_BASE_OFFSET + off); firstHalf = (int) value; secondHalf = (int) (value >>> 32); } crc ^= firstHalf; int xor1 = byteTable7[crc & 0xFF] ^ byteTable6[(crc >>> 8) & 0xFF]; int xor2 = byteTable5[(crc >>> 16) & 0xFF] ^ byteTable4[crc >>> 24]; int xor3 = byteTable3[secondHalf & 0xFF] ^ byteTable2[(secondHalf >>> 8) & 0xFF]; int xor4 = byteTable1[(secondHalf >>> 16) & 0xFF] ^ byteTable0[secondHalf >>> 24]; crc = xor1 ^ xor2 ^ xor3 ^ xor4; } } } // Tail for (; off < end; off++) { crc = (crc >>> 8) ^ byteTable[(crc ^ b[off]) & 0xFF]; } return crc; } /// COMMON COPY private static final int CRC32C_POLY = 0x1EDC6F41; private static final int REVERSED_CRC32C_POLY = Integer.reverse(CRC32C_POLY); // Lookup tables // Lookup table for single byte calculations private static final int[] byteTable; // Lookup tables for bulk operations in 'slicing-by-8' algorithm private static final int[][] byteTables = new int[8][256]; private static final int[] byteTable0 = byteTables[0]; private static final int[] byteTable1 = byteTables[1]; private static final int[] byteTable2 = byteTables[2]; private static final int[] byteTable3 = byteTables[3]; private static final int[] byteTable4 = byteTables[4]; private static final int[] byteTable5 = byteTables[5]; private static final int[] byteTable6 = byteTables[6]; private static final int[] byteTable7 = byteTables[7]; static { // Generate lookup tables // High-order polynomial term stored in LSB of r. for (int index = 0; index < byteTables[0].length; index++) { int r = index; for (int i = 0; i < Byte.SIZE; i++) { if ((r & 1) != 0) { r = (r >>> 1) ^ REVERSED_CRC32C_POLY; } else { r >>>= 1; } } byteTables[0][index] = r; } for (int index = 0; index < byteTables[0].length; index++) { int r = byteTables[0][index]; for (int k = 1; k < byteTables.length; k++) { r = byteTables[0][r & 0xFF] ^ (r >>> 8); byteTables[k][index] = r; } } if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { byteTable = byteTables[0]; } else { // ByteOrder.BIG_ENDIAN byteTable = new int[byteTable0.length]; System.arraycopy(byteTable0, 0, byteTable, 0, byteTable0.length); for (int[] table : byteTables) { for (int index = 0; index < table.length; index++) { table[index] = Integer.reverseBytes(table[index]); } } } } private final static Unsafe UNSAFE; static { try { /* works for jdk8 and jdk9 */ Constructor unsafeConstructor = Unsafe.class.getDeclaredConstructor(); unsafeConstructor.setAccessible(true); UNSAFE = unsafeConstructor.newInstance(); } catch (ReflectiveOperationException roe) { throw new Error("Can't get unsafe"); } } }