Skip to content

Commit

Permalink
fix: support bytea in preferQueryMode=simple
Browse files Browse the repository at this point in the history
  • Loading branch information
vlsi committed May 12, 2024
1 parent 4956902 commit 15d8a5c
Show file tree
Hide file tree
Showing 13 changed files with 323 additions and 73 deletions.
@@ -0,0 +1,173 @@
/*
* Copyright (c) 2024, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/

package org.postgresql.benchmark.encoding;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
* Tests the performance of hex encoding.
* The results for OpenJDK 17.0.10 are as follows, so it looks like
* {@link Character#forDigit(int, int)} is the way to go.
* <pre>
* Benchmark (length) Mode Cnt Score Error Units
* HexEncoding.character_fordigit 100 avgt 15 251,683 ± 0,292 ns/op
* HexEncoding.digit1_array_lookup 100 avgt 15 264,285 ± 0,215 ns/op
* HexEncoding.digit1_branchless 100 avgt 15 304,102 ± 0,361 ns/op
* HexEncoding.digit1_conditional 100 avgt 15 252,811 ± 1,458 ns/op
* HexEncoding.digit2_char_array 100 avgt 15 291,056 ± 2,524 ns/op
* HexEncoding.digit2_string_array 100 avgt 15 453,326 ± 5,742 ns/op
* HexEncoding.digit2_subarray 100 avgt 15 330,701 ± 2,523 ns/op
* </pre>
*/
@Fork(value = 3, jvmArgsPrepend = "-Xmx128m")
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
@Threads(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class HexEncodingBenchmark {

@Param({"100"})//{"1", "5", "10", "50", "100"})
public int length;

byte[] data;
StringBuilder sb = new StringBuilder();

private static final char[] HEX_DIGITS = "01234567890abcdef".toCharArray();
private static final String[] HEX_PAIRS = new String[256];
private static final char[][] HEX_CHAR_PAIRS = new char[256][];
private static final char[] HEX_CHAR_BIG_ARRAY;

static {
for (int i = 0; i < HEX_PAIRS.length; i++) {
HEX_PAIRS[i] = String.format("%02x", i);
HEX_CHAR_PAIRS[i] = HEX_PAIRS[i].toCharArray();
}
HEX_CHAR_BIG_ARRAY = String.join("", HEX_PAIRS).toCharArray();
}

@Setup
public void setup() {
data = new byte[length];
sb.ensureCapacity(length * 2);
ThreadLocalRandom.current().nextBytes(data);
}

@Setup(Level.Invocation)
public void clearBuffer() {
sb.setLength(0);
}

@Benchmark
public StringBuilder digit1_array_lookup() {
StringBuilder sb = this.sb;
char[] hexDigits = HEX_DIGITS;
for (byte b : data) {
sb.append(hexDigits[(b >> 4) & 0xF]);
sb.append(hexDigits[b & 0xF]);
}
return sb;
}

@Benchmark
public StringBuilder character_fordigit() {
StringBuilder sb = this.sb;
for (byte b : data) {
sb.append(Character.forDigit((b >> 4) & 0xF, 16));
sb.append(Character.forDigit(b & 0xF, 16));
}
return sb;
}

private static char digit_branchless(int digit) {
return (char) (87 + digit + (((digit - 10) >> 31) & -39));
}

@Benchmark
public StringBuilder digit1_branchless() {
StringBuilder sb = this.sb;
for (byte b : data) {
sb.append(digit_branchless((b >> 4) & 0xF));
sb.append(digit_branchless(b & 0xF));
}
return sb;
}

private static char digit_conditional(int digit) {
if (digit < 10) {
return (char) ('0' + digit);
}
return (char) ('a' - 10 + digit);
}

@Benchmark
public StringBuilder digit1_conditional() {
StringBuilder sb = this.sb;
for (byte b : data) {
sb.append(digit_conditional((b >> 4) & 0xF));
sb.append(digit_conditional(b & 0xF));
}
return sb;
}

@Benchmark
public StringBuilder digit2_string_array() {
StringBuilder sb = this.sb;
for (byte b : data) {
sb.append(HEX_PAIRS[b & 0xff]);
}
return sb;
}

@Benchmark
public StringBuilder digit2_char_array() {
StringBuilder sb = this.sb;
for (byte b : data) {
sb.append(HEX_CHAR_PAIRS[b & 0xff]);
}
return sb;
}

@Benchmark
public StringBuilder digit2_subarray() {
StringBuilder sb = this.sb;
for (byte b : data) {
int idx = (b & 0xff) * 2;
sb.append(HEX_CHAR_BIG_ARRAY, idx, 2);
}
return sb;
}

public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(HexEncodingBenchmark.class.getSimpleName())
//.addProfiler(GCProfiler.class)
.detectJvmArgs()
.build();

new Runner(opt).run();
}
}
6 changes: 2 additions & 4 deletions pgjdbc/src/main/java/org/postgresql/core/Utils.java
Expand Up @@ -7,6 +7,7 @@
package org.postgresql.core;

import org.postgresql.util.GT;
import org.postgresql.util.PGbytea;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;

Expand All @@ -27,10 +28,7 @@ public class Utils {
*/
public static String toHexString(byte[] data) {
StringBuilder sb = new StringBuilder(data.length * 2);
for (byte element : data) {
sb.append(Integer.toHexString((element >> 4) & 15));
sb.append(Integer.toHexString(element & 15));
}
PGbytea.appendHexString(sb, data, 0, data.length);
return sb.toString();
}

Expand Down
Expand Up @@ -16,6 +16,7 @@
import org.postgresql.util.ByteConverter;
import org.postgresql.util.ByteStreamWriter;
import org.postgresql.util.GT;
import org.postgresql.util.PGbytea;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import org.postgresql.util.StreamWrapper;
Expand Down Expand Up @@ -232,6 +233,10 @@ private static String quoteAndCast(String text, @Nullable String type, boolean s
return sb.toString();
}

private static <E extends Throwable> RuntimeException sneakyThrow(Throwable e) throws E {
throw (E) e;
}

@Override
public String toString(@Positive int index, boolean standardConformingStrings) {
--index;
Expand All @@ -243,6 +248,23 @@ public String toString(@Positive int index, boolean standardConformingStrings) {
}
String textValue;
String type;
if (paramTypes[index] == Oid.BYTEA) {
try {
return PGbytea.toPGLiteral(paramValue);
} catch (Throwable e) {
Throwable cause = e;
if (!(cause instanceof IOException)) {
// This is for compatibilty with the similar handling in QueryExecutorImpl
cause = new IOException("Error writing bytes to stream", e);
}
throw sneakyThrow(
new PSQLException(
GT.tr("Unable to convert bytea parameter at position {0} to literal",
index),
PSQLState.INVALID_PARAMETER_VALUE,
cause));
}
}
if ((flags[index] & BINARY) == BINARY) {
// handle some of the numeric types
switch (paramTypes[index]) {
Expand Down
16 changes: 2 additions & 14 deletions pgjdbc/src/main/java/org/postgresql/jdbc/ArrayEncoding.java
Expand Up @@ -10,6 +10,7 @@
import org.postgresql.core.Oid;
import org.postgresql.util.ByteConverter;
import org.postgresql.util.GT;
import org.postgresql.util.PGbytea;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;

Expand Down Expand Up @@ -924,12 +925,6 @@ byte[] toSingleDimensionBinaryRepresentation(BaseConnection connection, String[]
private static final AbstractArrayEncoder<byte[][]> BYTEA_ARRAY = new AbstractArrayEncoder<byte[][]>(Oid.BYTEA,
Oid.BYTEA_ARRAY) {

/**
* The possible characters to use for representing hex binary data.
*/
private final char[] hexDigits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
'e', 'f'};

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -1025,14 +1020,7 @@ public void appendArray(StringBuilder sb, char delim, byte[][] array) {

if (array[i] != null) {
sb.append("\"\\\\x");
for (int j = 0; j < array[i].length; j++) {
byte b = array[i][j];

// get the value for the left 4 bits (drop sign)
sb.append(hexDigits[(b & 0xF0) >>> 4]);
// get the value for the right 4 bits
sb.append(hexDigits[b & 0x0F]);
}
PGbytea.appendHexString(sb, array[i], 0, array[i].length);
sb.append('"');
} else {
sb.append("NULL");
Expand Down

0 comments on commit 15d8a5c

Please sign in to comment.