diff --git a/core/trino-main/pom.xml b/core/trino-main/pom.xml index d3c4aecab468..39b0403ba94f 100644 --- a/core/trino-main/pom.xml +++ b/core/trino-main/pom.xml @@ -556,4 +556,29 @@ test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + ${extraJavaVectorArgs} + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + ${extraJavaVectorArgs} + + + + + diff --git a/core/trino-main/src/main/java/io/trino/FeaturesConfig.java b/core/trino-main/src/main/java/io/trino/FeaturesConfig.java index 219c42b7db9b..ba4eea8de0d5 100644 --- a/core/trino-main/src/main/java/io/trino/FeaturesConfig.java +++ b/core/trino-main/src/main/java/io/trino/FeaturesConfig.java @@ -94,6 +94,7 @@ public class FeaturesConfig * default value is overwritten for fault tolerant execution in {@link #applyFaultTolerantExecutionDefaults()}} */ private CompressionCodec exchangeCompressionCodec = NONE; + private BlockSerdeVectorizedNullSuppressionStrategy blockSerdeVectorizedNullSuppressionStrategy = BlockSerdeVectorizedNullSuppressionStrategy.AUTO; private boolean pagesIndexEagerCompactionEnabled; private boolean omitDateTimeTypePrecision; private int maxRecursionDepth = 10; @@ -133,6 +134,12 @@ public enum DataIntegrityVerification /**/; } + public enum BlockSerdeVectorizedNullSuppressionStrategy + { + AUTO, + NONE, + } + public boolean isOmitDateTimeTypePrecision() { return omitDateTimeTypePrecision; @@ -366,6 +373,19 @@ public FeaturesConfig setExchangeCompressionCodec(CompressionCodec exchangeCompr return this; } + @Config("experimental.blockserde-vectorized-null-suppression-strategy") + @ConfigDescription("Strategy used for vectorized null suppression in block serde") + public FeaturesConfig setBlockSerdeVectorizedNullSuppressionStrategy(BlockSerdeVectorizedNullSuppressionStrategy blockSerdeVectorizedNullSuppressionStrategy) + { + this.blockSerdeVectorizedNullSuppressionStrategy = blockSerdeVectorizedNullSuppressionStrategy; + return this; + } + + public BlockSerdeVectorizedNullSuppressionStrategy getBlockSerdeVectorizedNullSuppressionStrategy() + { + return blockSerdeVectorizedNullSuppressionStrategy; + } + public DataIntegrityVerification getExchangeDataIntegrityVerification() { return exchangeDataIntegrityVerification; diff --git a/core/trino-main/src/main/java/io/trino/metadata/BlockEncodingManager.java b/core/trino-main/src/main/java/io/trino/metadata/BlockEncodingManager.java index 54ab5fabf85c..cb47c832b9c8 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/BlockEncodingManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/BlockEncodingManager.java @@ -13,6 +13,8 @@ */ package io.trino.metadata; +import com.google.inject.Inject; +import io.trino.simd.BlockEncodingSimdSupport; import io.trino.spi.block.ArrayBlockEncoding; import io.trino.spi.block.Block; import io.trino.spi.block.BlockEncoding; @@ -32,6 +34,7 @@ import java.util.concurrent.ConcurrentHashMap; import static com.google.common.base.Preconditions.checkArgument; +import static io.trino.simd.BlockEncodingSimdSupport.TESTING_BLOCK_ENCODING_SIMD_SUPPORT; import static java.util.Objects.requireNonNull; public final class BlockEncodingManager @@ -41,14 +44,19 @@ public final class BlockEncodingManager // for serialization private final Map, BlockEncoding> blockEncodingNamesByClass = new ConcurrentHashMap<>(); - public BlockEncodingManager() + public static final BlockEncodingManager TESTING_BLOCK_ENCODING_MANAGER = new BlockEncodingManager(TESTING_BLOCK_ENCODING_SIMD_SUPPORT); + + @Inject + public BlockEncodingManager( + BlockEncodingSimdSupport blockEncodingSimdSupport) { // add the built-in BlockEncodings + BlockEncodingSimdSupport.SimdSupport simdSupport = blockEncodingSimdSupport.getSimdSupport(); addBlockEncoding(new VariableWidthBlockEncoding()); - addBlockEncoding(new ByteArrayBlockEncoding()); - addBlockEncoding(new ShortArrayBlockEncoding()); - addBlockEncoding(new IntArrayBlockEncoding()); - addBlockEncoding(new LongArrayBlockEncoding()); + addBlockEncoding(new ByteArrayBlockEncoding(simdSupport.expandAndCompressByte())); + addBlockEncoding(new ShortArrayBlockEncoding(simdSupport.expandAndCompressShort())); + addBlockEncoding(new IntArrayBlockEncoding(simdSupport.expandAndCompressInt())); + addBlockEncoding(new LongArrayBlockEncoding(simdSupport.expandAndCompressLong())); addBlockEncoding(new Fixed12BlockEncoding()); addBlockEncoding(new Int128ArrayBlockEncoding()); addBlockEncoding(new DictionaryBlockEncoding()); diff --git a/core/trino-main/src/main/java/io/trino/server/ServerMainModule.java b/core/trino-main/src/main/java/io/trino/server/ServerMainModule.java index 32ba910f978f..6c0e821a125b 100644 --- a/core/trino-main/src/main/java/io/trino/server/ServerMainModule.java +++ b/core/trino-main/src/main/java/io/trino/server/ServerMainModule.java @@ -100,6 +100,7 @@ import io.trino.server.protocol.PreparedStatementEncoder; import io.trino.server.protocol.spooling.SpoolingServerModule; import io.trino.server.remotetask.HttpLocationFactory; +import io.trino.simd.BlockEncodingSimdSupport; import io.trino.spi.PageIndexerFactory; import io.trino.spi.PageSorter; import io.trino.spi.VersionEmbedder; @@ -427,6 +428,9 @@ protected void setup(Binder binder) .to(ServerPluginsProvider.class).in(Scopes.SINGLETON); configBinder(binder).bindConfig(ServerPluginsProviderConfig.class); + // SIMD support + binder.bind(BlockEncodingSimdSupport.class).in(Scopes.SINGLETON); + // block encodings binder.bind(BlockEncodingManager.class).in(Scopes.SINGLETON); jsonBinder(binder).addSerializerBinding(Block.class).to(BlockJsonSerde.Serializer.class); diff --git a/core/trino-main/src/main/java/io/trino/simd/BlockEncodingSimdSupport.java b/core/trino-main/src/main/java/io/trino/simd/BlockEncodingSimdSupport.java new file mode 100644 index 000000000000..962895050130 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/simd/BlockEncodingSimdSupport.java @@ -0,0 +1,112 @@ +/* + * 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 io.trino.simd; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import io.trino.FeaturesConfig; +import io.trino.util.MachineInfo; +import jdk.incubator.vector.ByteVector; +import jdk.incubator.vector.IntVector; +import jdk.incubator.vector.LongVector; +import jdk.incubator.vector.ShortVector; +import oshi.hardware.CentralProcessor.ProcessorIdentifier; + +import java.util.EnumSet; +import java.util.Set; + +import static io.trino.FeaturesConfig.BlockSerdeVectorizedNullSuppressionStrategy.AUTO; +import static io.trino.util.MachineInfo.readCpuFlags; +import static java.util.Locale.ENGLISH; + +@Singleton +public final class BlockEncodingSimdSupport +{ + public record SimdSupport( + boolean expandAndCompressByte, + boolean expandAndCompressShort, + boolean expandAndCompressInt, + boolean expandAndCompressLong) + { + public static final SimdSupport NONE = new SimdSupport(false, false, false, false); + public static final SimdSupport ALL = new SimdSupport(true, true, true, true); + } + + public static final int MINIMUM_SIMD_LENGTH = 512; + private final SimdSupport simdSupport; + private static final SimdSupport AUTO_DETECTED_SUPPORT = detectSimd(); + + public static final BlockEncodingSimdSupport TESTING_BLOCK_ENCODING_SIMD_SUPPORT = new BlockEncodingSimdSupport(true); + + @Inject + public BlockEncodingSimdSupport( + FeaturesConfig featuresConfig) + { + this(featuresConfig.getBlockSerdeVectorizedNullSuppressionStrategy().equals(AUTO)); + } + + public BlockEncodingSimdSupport( + boolean enableAutoDetectedSimdSupport) + { + if (enableAutoDetectedSimdSupport) { + simdSupport = AUTO_DETECTED_SUPPORT; + } + else { + simdSupport = SimdSupport.NONE; + } + } + + private static SimdSupport detectSimd() + { + ProcessorIdentifier id = MachineInfo.getProcessorInfo(); + + String vendor = id.getVendor().toLowerCase(ENGLISH); + + if (vendor.contains("intel") || vendor.contains("amd")) { + return detectX86SimdSupport(); + } + + return SimdSupport.NONE; + } + + private static SimdSupport detectX86SimdSupport() + { + enum X86SimdInstructionSet { + avx512f, + avx512vbmi2 + } + + Set flags = readCpuFlags(); + EnumSet x86Flags = EnumSet.noneOf(X86SimdInstructionSet.class); + + if (!flags.isEmpty()) { + for (X86SimdInstructionSet instructionSet : X86SimdInstructionSet.values()) { + if (flags.contains(instructionSet.name())) { + x86Flags.add(instructionSet); + } + } + } + + return new SimdSupport( + (ByteVector.SPECIES_PREFERRED.vectorBitSize() >= MINIMUM_SIMD_LENGTH) && x86Flags.contains(X86SimdInstructionSet.avx512vbmi2), + (ShortVector.SPECIES_PREFERRED.vectorBitSize() >= MINIMUM_SIMD_LENGTH) && x86Flags.contains(X86SimdInstructionSet.avx512vbmi2), + (IntVector.SPECIES_PREFERRED.vectorBitSize() >= MINIMUM_SIMD_LENGTH) && x86Flags.contains(X86SimdInstructionSet.avx512f), + (LongVector.SPECIES_PREFERRED.vectorBitSize() >= MINIMUM_SIMD_LENGTH) && x86Flags.contains(X86SimdInstructionSet.avx512f)); + } + + public SimdSupport getSimdSupport() + { + return simdSupport; + } +} diff --git a/core/trino-main/src/main/java/io/trino/testing/PlanTester.java b/core/trino-main/src/main/java/io/trino/testing/PlanTester.java index e41574a6fa7c..60c1bb09a8cb 100644 --- a/core/trino-main/src/main/java/io/trino/testing/PlanTester.java +++ b/core/trino-main/src/main/java/io/trino/testing/PlanTester.java @@ -80,7 +80,6 @@ import io.trino.memory.MemoryManagerConfig; import io.trino.memory.NodeMemoryConfig; import io.trino.metadata.AnalyzePropertyManager; -import io.trino.metadata.BlockEncodingManager; import io.trino.metadata.CatalogManager; import io.trino.metadata.ColumnPropertyManager; import io.trino.metadata.DisabledSystemSecurityMetadata; @@ -252,6 +251,7 @@ import static io.trino.execution.ParameterExtractor.bindParameters; import static io.trino.execution.querystats.PlanOptimizersStatsCollector.createPlanOptimizersStatsCollector; import static io.trino.execution.warnings.WarningCollector.NOOP; +import static io.trino.metadata.BlockEncodingManager.TESTING_BLOCK_ENCODING_MANAGER; import static io.trino.node.TestingInternalNodeManager.CURRENT_NODE; import static io.trino.spi.connector.Constraint.alwaysTrue; import static io.trino.spi.connector.DynamicFilter.EMPTY; @@ -358,10 +358,9 @@ private PlanTester(Session defaultSession, int nodeCountForStats) catalogManager, notificationExecutor); - BlockEncodingManager blockEncodingManager = new BlockEncodingManager(); TypeRegistry typeRegistry = new TypeRegistry(typeOperators, new FeaturesConfig()); TypeManager typeManager = new InternalTypeManager(typeRegistry); - InternalBlockEncodingSerde blockEncodingSerde = new InternalBlockEncodingSerde(blockEncodingManager, typeManager); + InternalBlockEncodingSerde blockEncodingSerde = new InternalBlockEncodingSerde(TESTING_BLOCK_ENCODING_MANAGER, typeManager); SecretsResolver secretsResolver = new SecretsResolver(ImmutableMap.of()); this.globalFunctionCatalog = new GlobalFunctionCatalog( @@ -496,7 +495,7 @@ private PlanTester(Session defaultSession, int nodeCountForStats) new GroupProviderManager(secretsResolver), new SessionPropertyDefaults(nodeInfo, accessControl, secretsResolver), typeRegistry, - blockEncodingManager, + TESTING_BLOCK_ENCODING_MANAGER, new HandleResolver(), exchangeManagerRegistry, spoolingManagerRegistry); diff --git a/core/trino-main/src/main/java/io/trino/util/MachineInfo.java b/core/trino-main/src/main/java/io/trino/util/MachineInfo.java index 9011b59f40dd..74d5e257721d 100644 --- a/core/trino-main/src/main/java/io/trino/util/MachineInfo.java +++ b/core/trino-main/src/main/java/io/trino/util/MachineInfo.java @@ -14,14 +14,25 @@ package io.trino.util; import com.google.common.base.StandardSystemProperty; +import com.google.common.collect.ImmutableSet; import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.CentralProcessor.ProcessorIdentifier; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static java.lang.Math.min; +import static java.util.Locale.ENGLISH; public final class MachineInfo { // cache physical processor count, so that it's not queried multiple times during tests private static volatile int physicalProcessorCount = -1; + private static final SystemInfo SYSTEM_INFO = new SystemInfo(); private MachineInfo() {} @@ -38,7 +49,7 @@ public static int getAvailablePhysicalProcessorCount() if ("amd64".equals(osArch) || "x86_64".equals(osArch)) { // Oshi can recognize physical processor count (without hyper threading) for x86 platforms. // However, it doesn't correctly recognize physical processor count for ARM platforms. - totalPhysicalProcessorCount = new SystemInfo() + totalPhysicalProcessorCount = SYSTEM_INFO .getHardware() .getProcessor() .getPhysicalProcessorCount(); @@ -52,4 +63,68 @@ public static int getAvailablePhysicalProcessorCount() physicalProcessorCount = min(totalPhysicalProcessorCount, availableProcessorCount); return physicalProcessorCount; } + + public static ProcessorIdentifier getProcessorInfo() + { + return SYSTEM_INFO.getHardware().getProcessor().getProcessorIdentifier(); + } + + public static Set readCpuFlags() + { + CentralProcessor cpu = SYSTEM_INFO.getHardware().getProcessor(); + List flags = cpu.getFeatureFlags(); + if (flags == null || flags.isEmpty()) { + return ImmutableSet.of(); + } + // Each element of flags represents the hardware support for an individual core, so we're want to calculate flags + // advertised by all cores + Set intersection = null; + + for (String line : flags) { + if (line == null || line.isBlank()) { + continue; + } + + // Strip the "flags:" / "Features:" prefix if present. + String body = line; + int colon = line.indexOf(':'); + if (colon >= 0) { + body = line.substring(colon + 1); + } + + // Tokenize + normalize. + Set tokens = Arrays.stream(body.trim().split("\\s+")) + .map(token -> normalizeFlag(token)) + .filter(token -> !token.isEmpty()) + .collect(toImmutableSet()); + + if (tokens.isEmpty()) { + continue; + } + + if (intersection == null) { + intersection = new HashSet<>(tokens); + } + else { + intersection.retainAll(tokens); + if (intersection.isEmpty()) { + break; // nothing in common + } + } + } + + return intersection == null ? ImmutableSet.of() : intersection; + } + + public static String normalizeFlag(String flag) + { + flag = flag.toLowerCase(ENGLISH).replace("_", "").trim(); + + // Skip stray keys that may sneak in if the colon wasn’t found. + if (flag.equals("flags") || flag.equals("features")) { + return ""; + } + + return flag; + } } diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/BenchmarkBlockSerde.java b/core/trino-main/src/test/java/io/trino/execution/buffer/BenchmarkBlockSerde.java index 2d0dddb9adf5..5710ab4302ea 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/BenchmarkBlockSerde.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/BenchmarkBlockSerde.java @@ -81,7 +81,7 @@ public class BenchmarkBlockSerde { private static final DecimalType LONG_DECIMAL_TYPE = createDecimalType(30, 5); - public static final int ROWS = 10_000_000; + public static final int ROWS = 8192; @Benchmark public Object serializeLongDecimal(LongDecimalBenchmarkData data) diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java index c2633ffb92a0..0b64bf0b28af 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java @@ -19,7 +19,6 @@ import io.airlift.slice.SliceInput; import io.airlift.slice.SliceOutput; import io.airlift.slice.Slices; -import io.trino.metadata.BlockEncodingManager; import io.trino.metadata.InternalBlockEncodingSerde; import io.trino.spi.Page; import io.trino.spi.PageBuilder; @@ -47,6 +46,7 @@ import static io.trino.execution.buffer.CompressionCodec.NONE; import static io.trino.execution.buffer.PagesSerdeUtil.readPages; import static io.trino.execution.buffer.PagesSerdeUtil.writePages; +import static io.trino.metadata.BlockEncodingManager.TESTING_BLOCK_ENCODING_MANAGER; import static io.trino.operator.PageAssertions.assertPageEquals; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DoubleType.DOUBLE; @@ -66,7 +66,7 @@ public class TestPagesSerde @BeforeAll public void setup() { - blockEncodingSerde = new InternalBlockEncodingSerde(new BlockEncodingManager(), TESTING_TYPE_MANAGER); + blockEncodingSerde = new InternalBlockEncodingSerde(TESTING_BLOCK_ENCODING_MANAGER, TESTING_TYPE_MANAGER); } @AfterAll diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestingPagesSerdes.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestingPagesSerdes.java index 74b336922ace..82ed7157119b 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestingPagesSerdes.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestingPagesSerdes.java @@ -13,17 +13,17 @@ */ package io.trino.execution.buffer; -import io.trino.metadata.BlockEncodingManager; import io.trino.metadata.InternalBlockEncodingSerde; import static io.trino.execution.buffer.CompressionCodec.NONE; +import static io.trino.metadata.BlockEncodingManager.TESTING_BLOCK_ENCODING_MANAGER; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; public final class TestingPagesSerdes { private TestingPagesSerdes() {} - private static final InternalBlockEncodingSerde BLOCK_ENCODING_SERDE = new InternalBlockEncodingSerde(new BlockEncodingManager(), TESTING_TYPE_MANAGER); + private static final InternalBlockEncodingSerde BLOCK_ENCODING_SERDE = new InternalBlockEncodingSerde(TESTING_BLOCK_ENCODING_MANAGER, TESTING_TYPE_MANAGER); public static PagesSerdeFactory createTestingPagesSerdeFactory() { diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestMetadataManager.java b/core/trino-main/src/test/java/io/trino/metadata/TestMetadataManager.java index 5e1c2f09a304..eeb738cbd412 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestMetadataManager.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestMetadataManager.java @@ -28,6 +28,7 @@ import java.util.Set; import static io.trino.client.NodeVersion.UNKNOWN; +import static io.trino.metadata.BlockEncodingManager.TESTING_BLOCK_ENCODING_MANAGER; import static io.trino.metadata.CatalogManager.NO_CATALOGS; import static io.trino.transaction.InMemoryTransactionManager.createTestTransactionManager; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; @@ -98,7 +99,7 @@ public MetadataManager build() } if (languageFunctionManager == null) { - BlockEncodingSerde blockEncodingSerde = new InternalBlockEncodingSerde(new BlockEncodingManager(), typeManager); + BlockEncodingSerde blockEncodingSerde = new InternalBlockEncodingSerde(TESTING_BLOCK_ENCODING_MANAGER, typeManager); LanguageFunctionEngineManager engineManager = new LanguageFunctionEngineManager(); languageFunctionManager = new LanguageFunctionManager(new SqlParser(), typeManager, _ -> ImmutableSet.of(), blockEncodingSerde, engineManager); } diff --git a/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java b/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java index 9d46eca7ddfd..63e5eeff5c2d 100644 --- a/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java +++ b/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java @@ -61,6 +61,7 @@ import io.trino.server.FailTaskRequest; import io.trino.server.HttpRemoteTaskFactory; import io.trino.server.TaskUpdateRequest; +import io.trino.simd.BlockEncodingSimdSupport; import io.trino.spi.ErrorCode; import io.trino.spi.QueryId; import io.trino.spi.block.Block; @@ -676,6 +677,7 @@ public void configure(Binder binder) jsonCodecBinder(binder).bindJsonCodec(TaskUpdateRequest.class); jsonCodecBinder(binder).bindJsonCodec(FailTaskRequest.class); + binder.bind(BlockEncodingSimdSupport.class).toInstance(new BlockEncodingSimdSupport(true)); binder.bind(TypeManager.class).toInstance(TESTING_TYPE_MANAGER); binder.bind(BlockEncodingManager.class).in(SINGLETON); binder.bind(BlockEncodingSerde.class).to(InternalBlockEncodingSerde.class).in(SINGLETON); diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestFeaturesConfig.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestFeaturesConfig.java index 46f5d501f8c7..c1241b0d2b2d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestFeaturesConfig.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestFeaturesConfig.java @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.DataSize; import io.trino.FeaturesConfig; +import io.trino.FeaturesConfig.BlockSerdeVectorizedNullSuppressionStrategy; import io.trino.FeaturesConfig.DataIntegrityVerification; import org.junit.jupiter.api.Test; @@ -54,6 +55,7 @@ public void testDefaults() .setMemoryRevokingThreshold(0.9) .setMemoryRevokingTarget(0.5) .setExchangeCompressionCodec(NONE) + .setBlockSerdeVectorizedNullSuppressionStrategy(BlockSerdeVectorizedNullSuppressionStrategy.AUTO) .setExchangeDataIntegrityVerification(DataIntegrityVerification.ABORT) .setPagesIndexEagerCompactionEnabled(false) .setFilterAndProjectMinOutputPageSize(DataSize.of(500, KILOBYTE)) @@ -89,6 +91,7 @@ public void testExplicitPropertyMappings() .put("memory-revoking-threshold", "0.2") .put("memory-revoking-target", "0.8") .put("exchange.compression-codec", "ZSTD") + .put("experimental.blockserde-vectorized-null-suppression-strategy", "NONE") .put("exchange.data-integrity-verification", "RETRY") .put("pages-index.eager-compaction-enabled", "true") .put("filter-and-project-min-output-page-size", "1MB") @@ -121,6 +124,7 @@ public void testExplicitPropertyMappings() .setMemoryRevokingThreshold(0.2) .setMemoryRevokingTarget(0.8) .setExchangeCompressionCodec(ZSTD) + .setBlockSerdeVectorizedNullSuppressionStrategy(BlockSerdeVectorizedNullSuppressionStrategy.NONE) .setExchangeDataIntegrityVerification(DataIntegrityVerification.RETRY) .setPagesIndexEagerCompactionEnabled(true) .setFilterAndProjectMinOutputPageSize(DataSize.of(1, MEGABYTE)) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestingPlannerContext.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestingPlannerContext.java index 1d4cc3af65b6..f0c024059bc2 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestingPlannerContext.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestingPlannerContext.java @@ -16,7 +16,6 @@ import com.google.common.collect.ImmutableSet; import io.trino.FeaturesConfig; import io.trino.connector.CatalogServiceProvider; -import io.trino.metadata.BlockEncodingManager; import io.trino.metadata.FunctionBundle; import io.trino.metadata.FunctionManager; import io.trino.metadata.GlobalFunctionCatalog; @@ -51,6 +50,7 @@ import static com.google.common.base.Preconditions.checkState; import static io.airlift.tracing.Tracing.noopTracer; import static io.trino.client.NodeVersion.UNKNOWN; +import static io.trino.metadata.BlockEncodingManager.TESTING_BLOCK_ENCODING_MANAGER; import static java.util.Objects.requireNonNull; public final class TestingPlannerContext @@ -125,7 +125,7 @@ public PlannerContext build() globalFunctionCatalog.addFunctions(SystemFunctionBundle.create(featuresConfig, typeOperators, new BlockTypeOperators(typeOperators), UNKNOWN)); functionBundles.forEach(globalFunctionCatalog::addFunctions); - BlockEncodingSerde blockEncodingSerde = new InternalBlockEncodingSerde(new BlockEncodingManager(), typeManager); + BlockEncodingSerde blockEncodingSerde = new InternalBlockEncodingSerde(TESTING_BLOCK_ENCODING_MANAGER, typeManager); LanguageFunctionManager languageFunctionManager = new LanguageFunctionManager( new SqlParser(), diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 56a634fc7bf6..455b214b033e 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -319,6 +319,34 @@ method long[] io.trino.spi.PageSorter::sort(java.util.List<io.trino.spi.type.Type>, java.util.List<io.trino.spi.Page>, java.util.List<java.lang.Integer>, java.util.List<io.trino.spi.connector.SortOrder>, int) method java.util.Iterator<io.trino.spi.Page> io.trino.spi.PageSorter::sort(java.util.List<io.trino.spi.type.Type>, java.util.List<io.trino.spi.Page>, java.util.List<java.lang.Integer>, java.util.List<io.trino.spi.connector.SortOrder>, int) + + true + java.method.numberOfParametersChanged + method void io.trino.spi.block.ByteArrayBlockEncoding::<init>() + method void io.trino.spi.block.ByteArrayBlockEncoding::<init>(boolean) + ByteArrayBlockEncoding need to accept a parameter to enable SIMD support + + + true + java.method.numberOfParametersChanged + method void io.trino.spi.block.IntArrayBlockEncoding::<init>() + method void io.trino.spi.block.IntArrayBlockEncoding::<init>(boolean) + IntArrayBlockEncoding need to accept a parameter to enable SIMD support + + + true + java.method.numberOfParametersChanged + method void io.trino.spi.block.LongArrayBlockEncoding::<init>() + method void io.trino.spi.block.LongArrayBlockEncoding::<init>(boolean) + LongArrayBlockEncoding need to accept a parameter to enable SIMD support + + + true + java.method.numberOfParametersChanged + method void io.trino.spi.block.ShortArrayBlockEncoding::<init>() + method void io.trino.spi.block.ShortArrayBlockEncoding::<init>(boolean) + ShortArrayBlockEncoding need to accept a parameter to enable SIMD support + diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockEncoding.java b/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockEncoding.java index cc03f3f18dfc..7e0dc3ff4ac2 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockEncoding.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockEncoding.java @@ -28,6 +28,12 @@ public class ByteArrayBlockEncoding implements BlockEncoding { public static final String NAME = "BYTE_ARRAY"; + private final boolean enableVectorizedNullSuppression; + + public ByteArrayBlockEncoding(boolean enableVectorizedNullSuppression) + { + this.enableVectorizedNullSuppression = enableVectorizedNullSuppression; + } @Override public String getName() @@ -60,15 +66,12 @@ public void writeBlock(BlockEncodingSerde blockEncodingSerde, SliceOutput sliceO sliceOutput.writeBytes(rawValues, rawOffset, positionCount); } else { - byte[] valuesWithoutNull = new byte[positionCount]; - int nonNullPositionCount = 0; - for (int i = 0; i < positionCount; i++) { - valuesWithoutNull[nonNullPositionCount] = rawValues[i + rawOffset]; - nonNullPositionCount += isNull[i + rawOffset] ? 0 : 1; + if (enableVectorizedNullSuppression) { + EncoderUtil.compressBytesWithNullsVectorized(sliceOutput, rawValues, isNull, rawOffset, positionCount); + } + else { + EncoderUtil.compressBytesWithNullsScalar(sliceOutput, rawValues, isNull, rawOffset, positionCount); } - - sliceOutput.writeInt(nonNullPositionCount); - sliceOutput.writeBytes(valuesWithoutNull, 0, nonNullPositionCount); } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/EncoderUtil.java b/core/trino-spi/src/main/java/io/trino/spi/block/EncoderUtil.java index 2d6c181cdfe0..8de4eb8686d3 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/EncoderUtil.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/EncoderUtil.java @@ -16,13 +16,27 @@ import io.airlift.slice.SliceInput; import io.airlift.slice.SliceOutput; import jakarta.annotation.Nullable; +import jdk.incubator.vector.ByteVector; +import jdk.incubator.vector.IntVector; +import jdk.incubator.vector.LongVector; +import jdk.incubator.vector.ShortVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorSpecies; import java.io.IOException; import java.io.UncheckedIOException; import java.util.Optional; +import static java.util.Objects.checkFromIndexSize; +import static java.util.Objects.requireNonNull; + final class EncoderUtil { + private static final VectorSpecies LONG_SPECIES = LongVector.SPECIES_PREFERRED; + private static final VectorSpecies INT_SPECIES = IntVector.SPECIES_PREFERRED; + private static final VectorSpecies BYTE_SPECIES = ByteVector.SPECIES_PREFERRED; + private static final VectorSpecies SHORT_SPECIES = ShortVector.SPECIES_PREFERRED; + private EncoderUtil() {} /** @@ -120,4 +134,156 @@ public static byte[] retrieveNullBits(SliceInput sliceInput, int positionCount) throw new UncheckedIOException(e); } } + + static void compressBytesWithNullsVectorized(SliceOutput sliceOutput, byte[] values, boolean[] isNull, int offset, int length) + { + requireNonNull(sliceOutput, "sliceOutput is null"); + checkFromIndexSize(offset, length, values.length); + checkFromIndexSize(offset, length, isNull.length); + byte[] compressed = new byte[length]; + int valuesIndex = 0; + int compressedIndex = 0; + for (; valuesIndex < BYTE_SPECIES.loopBound(length); valuesIndex += BYTE_SPECIES.length()) { + VectorMask mask = BYTE_SPECIES.loadMask(isNull, valuesIndex + offset).not(); + ByteVector.fromArray(BYTE_SPECIES, values, valuesIndex + offset) + .compress(mask) + .intoArray(compressed, compressedIndex); + compressedIndex += mask.trueCount(); + } + for (; valuesIndex < length; valuesIndex++) { + compressed[compressedIndex] = values[valuesIndex + offset]; + compressedIndex += isNull[valuesIndex + offset] ? 0 : 1; + } + sliceOutput.writeInt(compressedIndex); + sliceOutput.writeBytes(compressed, 0, compressedIndex); + } + + static void compressBytesWithNullsScalar(SliceOutput sliceOutput, byte[] values, boolean[] isNull, int offset, int length) + { + requireNonNull(sliceOutput, "sliceOutput is null"); + checkFromIndexSize(offset, length, values.length); + checkFromIndexSize(offset, length, isNull.length); + byte[] compressed = new byte[length]; + int compressedIndex = 0; + for (int i = 0; i < length; i++) { + compressed[compressedIndex] = values[i + offset]; + compressedIndex += isNull[i + offset] ? 0 : 1; + } + sliceOutput.writeInt(compressedIndex); + sliceOutput.writeBytes(compressed, 0, compressedIndex); + } + + static void compressShortsWithNullsVectorized(SliceOutput sliceOutput, short[] values, boolean[] isNull, int offset, int length) + { + requireNonNull(sliceOutput, "sliceOutput is null"); + checkFromIndexSize(offset, length, values.length); + checkFromIndexSize(offset, length, isNull.length); + short[] compressed = new short[length]; + int valuesIndex = 0; + int compressedIndex = 0; + for (; valuesIndex < SHORT_SPECIES.loopBound(length); valuesIndex += SHORT_SPECIES.length()) { + VectorMask mask = SHORT_SPECIES.loadMask(isNull, valuesIndex + offset).not(); + ShortVector.fromArray(SHORT_SPECIES, values, valuesIndex + offset) + .compress(mask) + .intoArray(compressed, compressedIndex); + compressedIndex += mask.trueCount(); + } + for (; valuesIndex < length; valuesIndex++) { + compressed[compressedIndex] = values[valuesIndex + offset]; + compressedIndex += isNull[valuesIndex + offset] ? 0 : 1; + } + sliceOutput.writeInt(compressedIndex); + sliceOutput.writeShorts(compressed, 0, compressedIndex); + } + + static void compressShortsWithNullsScalar(SliceOutput sliceOutput, short[] values, boolean[] isNull, int offset, int length) + { + requireNonNull(sliceOutput, "sliceOutput is null"); + checkFromIndexSize(offset, length, values.length); + checkFromIndexSize(offset, length, isNull.length); + short[] compressed = new short[length]; + int compressedIndex = 0; + for (int i = 0; i < length; i++) { + compressed[compressedIndex] = values[i + offset]; + compressedIndex += isNull[i + offset] ? 0 : 1; + } + sliceOutput.writeInt(compressedIndex); + sliceOutput.writeShorts(compressed, 0, compressedIndex); + } + + static void compressIntsWithNullsVectorized(SliceOutput sliceOutput, int[] values, boolean[] isNull, int offset, int length) + { + requireNonNull(sliceOutput, "sliceOutput is null"); + checkFromIndexSize(offset, length, values.length); + checkFromIndexSize(offset, length, isNull.length); + int[] compressed = new int[length]; + int valuesIndex = 0; + int compressedIndex = 0; + for (; valuesIndex < INT_SPECIES.loopBound(length); valuesIndex += INT_SPECIES.length()) { + VectorMask mask = INT_SPECIES.loadMask(isNull, valuesIndex + offset).not(); + IntVector.fromArray(INT_SPECIES, values, valuesIndex + offset) + .compress(mask) + .intoArray(compressed, compressedIndex); + compressedIndex += mask.trueCount(); + } + for (; valuesIndex < length; valuesIndex++) { + compressed[compressedIndex] = values[valuesIndex + offset]; + compressedIndex += isNull[valuesIndex + offset] ? 0 : 1; + } + sliceOutput.writeInt(compressedIndex); + sliceOutput.writeInts(compressed, 0, compressedIndex); + } + + static void compressIntsWithNullsScalar(SliceOutput sliceOutput, int[] values, boolean[] isNull, int offset, int length) + { + requireNonNull(sliceOutput, "sliceOutput is null"); + checkFromIndexSize(offset, length, values.length); + checkFromIndexSize(offset, length, isNull.length); + int[] compressed = new int[length]; + int compressedIndex = 0; + for (int i = 0; i < length; i++) { + compressed[compressedIndex] = values[i + offset]; + compressedIndex += isNull[i + offset] ? 0 : 1; + } + sliceOutput.writeInt(compressedIndex); + sliceOutput.writeInts(compressed, 0, compressedIndex); + } + + static void compressLongsWithNullsVectorized(SliceOutput sliceOutput, long[] values, boolean[] isNull, int offset, int length) + { + requireNonNull(sliceOutput, "sliceOutput is null"); + checkFromIndexSize(offset, length, values.length); + checkFromIndexSize(offset, length, isNull.length); + long[] compressed = new long[length]; + int valuesIndex = 0; + int compressedIndex = 0; + for (; valuesIndex < LONG_SPECIES.loopBound(length); valuesIndex += LONG_SPECIES.length()) { + VectorMask mask = LONG_SPECIES.loadMask(isNull, valuesIndex + offset).not(); + LongVector.fromArray(LONG_SPECIES, values, valuesIndex + offset) + .compress(mask) + .intoArray(compressed, compressedIndex); + compressedIndex += mask.trueCount(); + } + for (; valuesIndex < length; valuesIndex++) { + compressed[compressedIndex] = values[valuesIndex + offset]; + compressedIndex += isNull[valuesIndex + offset] ? 0 : 1; + } + sliceOutput.writeInt(compressedIndex); + sliceOutput.writeLongs(compressed, 0, compressedIndex); + } + + static void compressLongsWithNullsScalar(SliceOutput sliceOutput, long[] values, boolean[] isNull, int offset, int length) + { + requireNonNull(sliceOutput, "sliceOutput is null"); + checkFromIndexSize(offset, length, values.length); + checkFromIndexSize(offset, length, isNull.length); + long[] compressed = new long[length]; + int compressedIndex = 0; + for (int i = 0; i < length; i++) { + compressed[compressedIndex] = values[i + offset]; + compressedIndex += isNull[i + offset] ? 0 : 1; + } + sliceOutput.writeInt(compressedIndex); + sliceOutput.writeLongs(compressed, 0, compressedIndex); + } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockEncoding.java b/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockEncoding.java index 0f76c1d49536..09a752492cd8 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockEncoding.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockEncoding.java @@ -27,6 +27,12 @@ public class IntArrayBlockEncoding implements BlockEncoding { public static final String NAME = "INT_ARRAY"; + private final boolean enableVectorizedNullSuppression; + + public IntArrayBlockEncoding(boolean enableVectorizedNullSuppression) + { + this.enableVectorizedNullSuppression = enableVectorizedNullSuppression; + } @Override public String getName() @@ -59,15 +65,12 @@ public void writeBlock(BlockEncodingSerde blockEncodingSerde, SliceOutput sliceO sliceOutput.writeInts(rawValues, rawOffset, positionCount); } else { - int[] valuesWithoutNull = new int[positionCount]; - int nonNullPositionCount = 0; - for (int i = 0; i < positionCount; i++) { - valuesWithoutNull[nonNullPositionCount] = rawValues[i + rawOffset]; - nonNullPositionCount += isNull[i + rawOffset] ? 0 : 1; + if (enableVectorizedNullSuppression) { + EncoderUtil.compressIntsWithNullsVectorized(sliceOutput, rawValues, isNull, rawOffset, positionCount); + } + else { + EncoderUtil.compressIntsWithNullsScalar(sliceOutput, rawValues, isNull, rawOffset, positionCount); } - - sliceOutput.writeInt(nonNullPositionCount); - sliceOutput.writeInts(valuesWithoutNull, 0, nonNullPositionCount); } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockEncoding.java b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockEncoding.java index 38759e13ef21..ea890bcd5f47 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockEncoding.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockEncoding.java @@ -27,6 +27,12 @@ public class LongArrayBlockEncoding implements BlockEncoding { public static final String NAME = "LONG_ARRAY"; + private final boolean enableVectorizedNullSuppression; + + public LongArrayBlockEncoding(boolean enableVectorizedNullSuppression) + { + this.enableVectorizedNullSuppression = enableVectorizedNullSuppression; + } @Override public String getName() @@ -59,15 +65,12 @@ public void writeBlock(BlockEncodingSerde blockEncodingSerde, SliceOutput sliceO sliceOutput.writeLongs(rawValues, rawOffset, positionCount); } else { - long[] valuesWithoutNull = new long[positionCount]; - int nonNullPositionCount = 0; - for (int i = 0; i < positionCount; i++) { - valuesWithoutNull[nonNullPositionCount] = rawValues[i + rawOffset]; - nonNullPositionCount += isNull[i + rawOffset] ? 0 : 1; + if (enableVectorizedNullSuppression) { + EncoderUtil.compressLongsWithNullsVectorized(sliceOutput, rawValues, isNull, rawOffset, positionCount); + } + else { + EncoderUtil.compressLongsWithNullsScalar(sliceOutput, rawValues, isNull, rawOffset, positionCount); } - - sliceOutput.writeInt(nonNullPositionCount); - sliceOutput.writeLongs(valuesWithoutNull, 0, nonNullPositionCount); } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockEncoding.java b/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockEncoding.java index 512164c605d5..eee56037e0ac 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockEncoding.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockEncoding.java @@ -27,6 +27,12 @@ public class ShortArrayBlockEncoding implements BlockEncoding { public static final String NAME = "SHORT_ARRAY"; + private final boolean enableVectorizedNullSuppression; + + public ShortArrayBlockEncoding(boolean enableVectorizedNullSuppression) + { + this.enableVectorizedNullSuppression = enableVectorizedNullSuppression; + } @Override public String getName() @@ -59,15 +65,12 @@ public void writeBlock(BlockEncodingSerde blockEncodingSerde, SliceOutput sliceO sliceOutput.writeShorts(rawValues, rawOffset, positionCount); } else { - short[] valuesWithoutNull = new short[positionCount]; - int nonNullPositionCount = 0; - for (int i = 0; i < positionCount; i++) { - valuesWithoutNull[nonNullPositionCount] = rawValues[i + rawOffset]; - nonNullPositionCount += isNull[i + rawOffset] ? 0 : 1; + if (enableVectorizedNullSuppression) { + EncoderUtil.compressShortsWithNullsVectorized(sliceOutput, rawValues, isNull, rawOffset, positionCount); + } + else { + EncoderUtil.compressShortsWithNullsScalar(sliceOutput, rawValues, isNull, rawOffset, positionCount); } - - sliceOutput.writeInt(nonNullPositionCount); - sliceOutput.writeShorts(valuesWithoutNull, 0, nonNullPositionCount); } } diff --git a/core/trino-spi/src/main/java/module-info.java b/core/trino-spi/src/main/java/module-info.java index c1ca692fb861..10f9218e0934 100644 --- a/core/trino-spi/src/main/java/module-info.java +++ b/core/trino-spi/src/main/java/module-info.java @@ -17,6 +17,7 @@ requires transitive io.opentelemetry.api; requires jakarta.annotation; requires transitive slice; + requires jdk.incubator.vector; exports io.trino.spi; exports io.trino.spi.block; diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestEncoderUtil.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestEncoderUtil.java new file mode 100644 index 000000000000..abf241d208f5 --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestEncoderUtil.java @@ -0,0 +1,224 @@ +/* + * 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 io.trino.spi.block; + +import io.airlift.slice.DynamicSliceOutput; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import java.util.Random; + +import static org.assertj.core.api.Assertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +public class TestEncoderUtil +{ + private static final int[] TEST_LENGTHS = {0, 3, 255, 257, 512, 530, 1024, 2048, 8192}; + private static final int[] TEST_OFFSETS = {0, 2, 256}; + private static final long RANDOM_SEED = 42; + + @Test + public void testBytesScalarEqualsVector() + { + for (int length : TEST_LENGTHS) { + for (int offset : TEST_OFFSETS) { + byte[] values = randomBytes(offset + length); + for (boolean[] isNull : getIsNullArray(offset + length)) { + byte[] scalar = compressBytesScalar(values, isNull, offset, length); + byte[] vector = compressBytesVectorized(values, isNull, offset, length); + assertThat(vector).as("bytes: scalar and vector outputs differ").isEqualTo(scalar); + } + } + } + } + + @Test + public void testShortsScalarEqualsVector() + { + for (int length : TEST_LENGTHS) { + for (int offset : TEST_OFFSETS) { + short[] values = randomShorts(offset + length); + for (boolean[] isNull : getIsNullArray(offset + length)) { + byte[] scalar = compressShortsScalar(values, isNull, offset, length); + byte[] vector = compressShortsVectorized(values, isNull, offset, length); + assertThat(vector).as("shorts: scalar and vector outputs differ").isEqualTo(scalar); + } + } + } + } + + @Test + public void testIntsScalarEqualsVector() + { + for (int length : TEST_LENGTHS) { + for (int offset : TEST_OFFSETS) { + int[] values = randomInts(offset + length); + for (boolean[] isNull : getIsNullArray(offset + length)) { + byte[] scalar = compressIntsScalar(values, isNull, offset, length); + byte[] vector = compressIntsVectorized(values, isNull, offset, length); + assertThat(vector).as("ints: scalar and vector outputs differ").isEqualTo(scalar); + } + } + } + } + + @Test + public void testLongsScalarEqualsVector() + { + for (int length : TEST_LENGTHS) { + for (int offset : TEST_OFFSETS) { + long[] values = randomLongs(offset + length); + for (boolean[] isNull : getIsNullArray(offset + length)) { + byte[] scalar = compressLongsScalar(values, isNull, offset, length); + byte[] vector = compressLongsVectorized(values, isNull, offset, length); + assertThat(vector).as("longs: scalar and vector outputs differ").isEqualTo(scalar); + } + } + } + } + + public static boolean[][] getIsNullArray(int length) + { + return new boolean[][] { + all(false, length), + all(true, length), + alternating(length), + randomBooleans(length)}; + } + + static byte[] compressBytesScalar(byte[] values, boolean[] isNull, int offset, int length) + { + DynamicSliceOutput out = new DynamicSliceOutput(length * (Byte.BYTES + 4)); + EncoderUtil.compressBytesWithNullsScalar(out, values, isNull, offset, length); + return out.slice().getBytes(); + } + + private static byte[] compressBytesVectorized(byte[] values, boolean[] isNull, int offset, int length) + { + DynamicSliceOutput out = new DynamicSliceOutput(length * (Byte.BYTES + 4)); + EncoderUtil.compressBytesWithNullsVectorized(out, values, isNull, offset, length); + return out.slice().getBytes(); + } + + private static byte[] compressShortsScalar(short[] values, boolean[] isNull, int offset, int length) + { + DynamicSliceOutput out = new DynamicSliceOutput(length * (Short.BYTES + 4)); + EncoderUtil.compressShortsWithNullsScalar(out, values, isNull, offset, length); + return out.slice().getBytes(); + } + + private static byte[] compressShortsVectorized(short[] values, boolean[] isNull, int offset, int length) + { + DynamicSliceOutput out = new DynamicSliceOutput(length * (Short.BYTES + 4)); + EncoderUtil.compressShortsWithNullsVectorized(out, values, isNull, offset, length); + return out.slice().getBytes(); + } + + private static byte[] compressIntsScalar(int[] values, boolean[] isNull, int offset, int length) + { + DynamicSliceOutput out = new DynamicSliceOutput(length * (Integer.BYTES + 4)); + EncoderUtil.compressIntsWithNullsScalar(out, values, isNull, offset, length); + return out.slice().getBytes(); + } + + private static byte[] compressIntsVectorized(int[] values, boolean[] isNull, int offset, int length) + { + DynamicSliceOutput out = new DynamicSliceOutput(length * (Integer.BYTES + 4)); + EncoderUtil.compressIntsWithNullsVectorized(out, values, isNull, offset, length); + return out.slice().getBytes(); + } + + static byte[] compressLongsScalar(long[] values, boolean[] isNull, int offset, int length) + { + DynamicSliceOutput out = new DynamicSliceOutput(length * (Long.BYTES + 4)); + EncoderUtil.compressLongsWithNullsScalar(out, values, isNull, offset, length); + return out.slice().getBytes(); + } + + private static byte[] compressLongsVectorized(long[] values, boolean[] isNull, int offset, int length) + { + DynamicSliceOutput out = new DynamicSliceOutput(length * (Long.BYTES + 4)); + EncoderUtil.compressLongsWithNullsVectorized(out, values, isNull, offset, length); + return out.slice().getBytes(); + } + + private static byte[] randomBytes(int size) + { + byte[] data = new byte[size]; + Random r = new Random(RANDOM_SEED); + r.nextBytes(data); + return data; + } + + private static short[] randomShorts(int size) + { + short[] data = new short[size]; + Random r = new Random(RANDOM_SEED); + for (int i = 0; i < size; i++) { + data[i] = (short) r.nextInt(); + } + return data; + } + + private static int[] randomInts(int size) + { + int[] data = new int[size]; + Random r = new Random(RANDOM_SEED); + for (int i = 0; i < size; i++) { + data[i] = r.nextInt(); + } + return data; + } + + private static long[] randomLongs(int size) + { + long[] data = new long[size]; + Random r = new Random(RANDOM_SEED); + for (int i = 0; i < size; i++) { + data[i] = r.nextLong(); + } + return data; + } + + private static boolean[] all(boolean value, int size) + { + boolean[] out = new boolean[size]; + if (value) { + for (int i = 0; i < size; i++) { + out[i] = true; + } + } + return out; + } + + private static boolean[] alternating(int size) + { + boolean[] out = new boolean[size]; + for (int i = 0; i < size; i++) { + out[i] = (i % 2) == 0; + } + return out; + } + + private static boolean[] randomBooleans(int size) + { + boolean[] out = new boolean[size]; + Random r = new Random(RANDOM_SEED); + for (int i = 0; i < size; i++) { + out[i] = r.nextDouble() < 0.3; + } + return out; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestingBlockEncodingSerde.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestingBlockEncodingSerde.java index 248eebf6323c..24b2731f51f9 100644 --- a/core/trino-spi/src/test/java/io/trino/spi/block/TestingBlockEncodingSerde.java +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestingBlockEncodingSerde.java @@ -47,10 +47,10 @@ public TestingBlockEncodingSerde(Function types) this.types = requireNonNull(types, "types is null"); // add the built-in BlockEncodings addBlockEncoding(new VariableWidthBlockEncoding()); - addBlockEncoding(new ByteArrayBlockEncoding()); - addBlockEncoding(new ShortArrayBlockEncoding()); - addBlockEncoding(new IntArrayBlockEncoding()); - addBlockEncoding(new LongArrayBlockEncoding()); + addBlockEncoding(new ByteArrayBlockEncoding(true)); + addBlockEncoding(new ShortArrayBlockEncoding(true)); + addBlockEncoding(new IntArrayBlockEncoding(true)); + addBlockEncoding(new LongArrayBlockEncoding(true)); addBlockEncoding(new Fixed12BlockEncoding()); addBlockEncoding(new Int128ArrayBlockEncoding()); addBlockEncoding(new DictionaryBlockEncoding());