This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git


The following commit(s) were added to refs/heads/main by this push:
     new b62ceac3 perf(parquet): SIMD BYTE_STREAM_SPLIT encoding (#1260)
b62ceac3 is described below

commit b62ceac3ff84e2d1179bb8ac97279d27436e4ae2
Author: Minh Vu <[email protected]>
AuthorDate: Mon Aug 31 22:39:09 2026 +0200

    perf(parquet): SIMD BYTE_STREAM_SPLIT encoding (#1260)
    
    ## What
    
    - Add runtime-dispatched SIMD encoding for 4-byte and 8-byte
    BYTE_STREAM_SPLIT values.
    - Use NEON structure loads and unzip operations on arm64.
    - Use AVX2 unpacking on amd64.
    - Reuse the fast path for numeric encoders and fixed-length byte arrays
    of width 4 or 8.
    - Keep the scalar fallback for `noasm` builds and CPUs without the
    required feature.
    
    ## Benchmark
    
    Direct split benchmark with 65,536 values on an Apple M1 Pro, Go 1.26.3,
    1 CPU, 150 ms, 7 runs. `dispatch` uses the NEON path on this machine.
    
    | Value width | Scalar | SIMD | Speedup |
    | ---: | ---: | ---: | ---: |
    | 4 bytes | 113,877 ns/op | 6,745 ns/op | 16.9x |
    | 8 bytes | 218,824 ns/op | 28,861 ns/op | 7.6x |
    
    ## Tests
    
    - `go test ./parquet/internal/encoding`
    - `go test -tags noasm ./parquet/internal/encoding`
    - `GOOS=darwin GOARCH=amd64 go test -c -o /dev/null
    ./parquet/internal/encoding`
    - `PARQUET_TEST_DATA="$(pwd)/parquet-testing/data" go test ./...`
    - `go vet ./parquet/internal/encoding`
---
 parquet/internal/encoding/byte_stream_split.go     |   9 +-
 .../internal/encoding/byte_stream_split_amd64.go   |  24 +++
 .../internal/encoding/byte_stream_split_arm64.go   |  24 +++
 .../byte_stream_split_encode_amd64_test.go         |  39 ++++
 .../byte_stream_split_encode_arm64_test.go         |  39 ++++
 .../encoding/byte_stream_split_encode_avx2_amd64.s | 222 +++++++++++++++++++++
 .../encoding/byte_stream_split_encode_neon_arm64.s | 178 +++++++++++++++++
 .../encoding/byte_stream_split_encode_test.go      | 112 +++++++++++
 .../encoding/fixed_len_byte_array_encoder.go       |   4 +-
 9 files changed, 647 insertions(+), 4 deletions(-)

diff --git a/parquet/internal/encoding/byte_stream_split.go 
b/parquet/internal/encoding/byte_stream_split.go
index 1854c6c4..970e0f88 100644
--- a/parquet/internal/encoding/byte_stream_split.go
+++ b/parquet/internal/encoding/byte_stream_split.go
@@ -88,6 +88,11 @@ func encodeByteStreamSplitWidth8(data []byte, in []byte) {
        }
 }
 
+var (
+       encodeByteStreamSplitWidth4Impl = encodeByteStreamSplitWidth4
+       encodeByteStreamSplitWidth8Impl = encodeByteStreamSplitWidth8
+)
+
 func releaseBufferToPool(pooled *PooledBufferWriter) {
        buf := pooled.buf
        memory.Set(buf.Buf(), 0)
@@ -126,9 +131,9 @@ func (enc *byteStreamSplitEncoder[T]) FlushValues() 
(Buffer, error) {
        var z T
        switch any(z).(type) {
        case int32, float32:
-               encodeByteStreamSplitWidth4(enc.flushBuffer.Bytes(), in.Bytes())
+               encodeByteStreamSplitWidth4Impl(enc.flushBuffer.Bytes(), 
in.Bytes())
        case int64, float64:
-               encodeByteStreamSplitWidth8(enc.flushBuffer.Bytes(), in.Bytes())
+               encodeByteStreamSplitWidth8Impl(enc.flushBuffer.Bytes(), 
in.Bytes())
        }
 
        return enc.flushBuffer.Finish(), nil
diff --git a/parquet/internal/encoding/byte_stream_split_amd64.go 
b/parquet/internal/encoding/byte_stream_split_amd64.go
index b6aad9d7..8c8342b4 100644
--- a/parquet/internal/encoding/byte_stream_split_amd64.go
+++ b/parquet/internal/encoding/byte_stream_split_amd64.go
@@ -29,9 +29,33 @@ func init() {
        if cpu.X86.HasAVX2 {
                decodeByteStreamSplitBatchWidth4InByteOrder = 
decodeByteStreamSplitBatchWidth4AVX2
                decodeByteStreamSplitBatchWidth8InByteOrder = 
decodeByteStreamSplitBatchWidth8AVX2
+               encodeByteStreamSplitWidth4Impl = 
encodeByteStreamSplitWidth4AVX2
+               encodeByteStreamSplitWidth8Impl = 
encodeByteStreamSplitWidth8AVX2
        }
 }
 
+//go:noescape
+func _encodeByteStreamSplitWidth4AVX2(in, out unsafe.Pointer, nValues int)
+
+//go:noescape
+func _encodeByteStreamSplitWidth8AVX2(in, out unsafe.Pointer, nValues int)
+
+func encodeByteStreamSplitWidth4AVX2(data, in []byte) {
+       if len(in) == 0 {
+               return
+       }
+       debug.Assert(len(data) >= len(in), "not enough space in destination 
buffer for encoding")
+       _encodeByteStreamSplitWidth4AVX2(unsafe.Pointer(&in[0]), 
unsafe.Pointer(&data[0]), len(in)/4)
+}
+
+func encodeByteStreamSplitWidth8AVX2(data, in []byte) {
+       if len(in) == 0 {
+               return
+       }
+       debug.Assert(len(data) >= len(in), "not enough space in destination 
buffer for encoding")
+       _encodeByteStreamSplitWidth8AVX2(unsafe.Pointer(&in[0]), 
unsafe.Pointer(&data[0]), len(in)/8)
+}
+
 //go:noescape
 func _decodeByteStreamSplitWidth4AVX2(data, out unsafe.Pointer, nValues, 
stride int)
 
diff --git a/parquet/internal/encoding/byte_stream_split_arm64.go 
b/parquet/internal/encoding/byte_stream_split_arm64.go
index 6f638812..40e13919 100644
--- a/parquet/internal/encoding/byte_stream_split_arm64.go
+++ b/parquet/internal/encoding/byte_stream_split_arm64.go
@@ -29,9 +29,33 @@ func init() {
        if cpu.ARM64.HasASIMD {
                decodeByteStreamSplitBatchWidth4InByteOrder = 
decodeByteStreamSplitBatchWidth4NEON
                decodeByteStreamSplitBatchWidth8InByteOrder = 
decodeByteStreamSplitBatchWidth8NEON
+               encodeByteStreamSplitWidth4Impl = 
encodeByteStreamSplitWidth4NEON
+               encodeByteStreamSplitWidth8Impl = 
encodeByteStreamSplitWidth8NEON
        }
 }
 
+//go:noescape
+func _encodeByteStreamSplitWidth4NEON(in, out unsafe.Pointer, nValues int)
+
+//go:noescape
+func _encodeByteStreamSplitWidth8NEON(in, out unsafe.Pointer, nValues int)
+
+func encodeByteStreamSplitWidth4NEON(data, in []byte) {
+       if len(in) == 0 {
+               return
+       }
+       debug.Assert(len(data) >= len(in), "not enough space in destination 
buffer for encoding")
+       _encodeByteStreamSplitWidth4NEON(unsafe.Pointer(&in[0]), 
unsafe.Pointer(&data[0]), len(in)/4)
+}
+
+func encodeByteStreamSplitWidth8NEON(data, in []byte) {
+       if len(in) == 0 {
+               return
+       }
+       debug.Assert(len(data) >= len(in), "not enough space in destination 
buffer for encoding")
+       _encodeByteStreamSplitWidth8NEON(unsafe.Pointer(&in[0]), 
unsafe.Pointer(&data[0]), len(in)/8)
+}
+
 //go:noescape
 func _decodeByteStreamSplitWidth4NEON(data, out unsafe.Pointer, nValues, 
stride int)
 
diff --git a/parquet/internal/encoding/byte_stream_split_encode_amd64_test.go 
b/parquet/internal/encoding/byte_stream_split_encode_amd64_test.go
new file mode 100644
index 00000000..0a38f627
--- /dev/null
+++ b/parquet/internal/encoding/byte_stream_split_encode_amd64_test.go
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+//go:build !noasm && !appengine
+
+package encoding
+
+import (
+       "testing"
+
+       "golang.org/x/sys/cpu"
+)
+
+func TestEncodeByteStreamSplitWidth4AVX2(t *testing.T) {
+       if !cpu.X86.HasAVX2 {
+               t.Skip("AVX2 is not available")
+       }
+       testEncodeByteStreamSplit(t, 4, encodeByteStreamSplitWidth4AVX2)
+}
+
+func TestEncodeByteStreamSplitWidth8AVX2(t *testing.T) {
+       if !cpu.X86.HasAVX2 {
+               t.Skip("AVX2 is not available")
+       }
+       testEncodeByteStreamSplit(t, 8, encodeByteStreamSplitWidth8AVX2)
+}
diff --git a/parquet/internal/encoding/byte_stream_split_encode_arm64_test.go 
b/parquet/internal/encoding/byte_stream_split_encode_arm64_test.go
new file mode 100644
index 00000000..b53aa25c
--- /dev/null
+++ b/parquet/internal/encoding/byte_stream_split_encode_arm64_test.go
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+//go:build !noasm && !appengine
+
+package encoding
+
+import (
+       "testing"
+
+       "golang.org/x/sys/cpu"
+)
+
+func TestEncodeByteStreamSplitWidth4NEON(t *testing.T) {
+       if !cpu.ARM64.HasASIMD {
+               t.Skip("ASIMD is not available")
+       }
+       testEncodeByteStreamSplit(t, 4, encodeByteStreamSplitWidth4NEON)
+}
+
+func TestEncodeByteStreamSplitWidth8NEON(t *testing.T) {
+       if !cpu.ARM64.HasASIMD {
+               t.Skip("ASIMD is not available")
+       }
+       testEncodeByteStreamSplit(t, 8, encodeByteStreamSplitWidth8NEON)
+}
diff --git a/parquet/internal/encoding/byte_stream_split_encode_avx2_amd64.s 
b/parquet/internal/encoding/byte_stream_split_encode_avx2_amd64.s
new file mode 100644
index 00000000..7c3e1443
--- /dev/null
+++ b/parquet/internal/encoding/byte_stream_split_encode_avx2_amd64.s
@@ -0,0 +1,222 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+//go:build !noasm && !appengine
+// +build !noasm,!appengine
+
+// AVX2 implementation of BYTE_STREAM_SPLIT encoding.
+
+#include "textflag.h"
+
+// func _encodeByteStreamSplitWidth4AVX2(in, out unsafe.Pointer, nValues int)
+//
+// Transposes eight 4-byte values at a time. The unpack stages produce two
+// 8-byte streams per output register.
+TEXT ·_encodeByteStreamSplitWidth4AVX2(SB), NOSPLIT, $0-24
+       MOVQ in+0(FP), SI
+       MOVQ out+8(FP), DI
+       MOVQ nValues+16(FP), CX
+       TESTQ CX, CX
+       JZ encode_w4_done
+
+       MOVQ CX, AX
+       SHRQ $3, AX
+       LEAQ (DI)(CX*1), R8
+       LEAQ (DI)(CX*2), R9
+       LEAQ (R8)(CX*2), R10
+       MOVQ AX, R11
+       SHLQ $3, R11
+       SUBQ R11, CX
+
+       TESTQ AX, AX
+       JZ encode_w4_tail
+
+encode_w4_vector:
+       VMOVD 0(SI), X0
+       VMOVD 4(SI), X1
+       VMOVD 8(SI), X2
+       VMOVD 12(SI), X3
+       VMOVD 16(SI), X4
+       VMOVD 20(SI), X5
+       VMOVD 24(SI), X6
+       VMOVD 28(SI), X7
+
+       VPUNPCKLBW X1, X0, X8
+       VPUNPCKLBW X3, X2, X9
+       VPUNPCKLBW X5, X4, X10
+       VPUNPCKLBW X7, X6, X11
+
+       VPUNPCKLWD X9, X8, X12
+       VPUNPCKLWD X11, X10, X13
+
+       VPUNPCKLDQ X13, X12, X0
+       VPUNPCKHDQ X13, X12, X1
+
+       VMOVQ X0, (DI)
+       VPSRLDQ $8, X0, X0
+       VMOVQ X0, (R8)
+       VMOVQ X1, (R9)
+       VPSRLDQ $8, X1, X1
+       VMOVQ X1, (R10)
+
+       ADDQ $32, SI
+       ADDQ $8, DI
+       ADDQ $8, R8
+       ADDQ $8, R9
+       ADDQ $8, R10
+       DECQ AX
+       JNZ encode_w4_vector
+
+encode_w4_tail:
+       TESTQ CX, CX
+       JZ encode_w4_done
+
+encode_w4_tail_loop:
+       MOVBQZX 0(SI), BX
+       MOVB BX, (DI)
+       MOVBQZX 1(SI), BX
+       MOVB BX, (R8)
+       MOVBQZX 2(SI), BX
+       MOVB BX, (R9)
+       MOVBQZX 3(SI), BX
+       MOVB BX, (R10)
+
+       ADDQ $4, SI
+       INCQ DI
+       INCQ R8
+       INCQ R9
+       INCQ R10
+       DECQ CX
+       JNZ encode_w4_tail_loop
+
+encode_w4_done:
+       VZEROUPPER
+       RET
+
+// func _encodeByteStreamSplitWidth8AVX2(in, out unsafe.Pointer, nValues int)
+//
+// Transposes eight 8-byte values at a time. The unpack stages produce two
+// 8-byte streams per output register.
+TEXT ·_encodeByteStreamSplitWidth8AVX2(SB), NOSPLIT, $0-24
+       MOVQ in+0(FP), SI
+       MOVQ out+8(FP), DI
+       MOVQ nValues+16(FP), CX
+       TESTQ CX, CX
+       JZ encode_w8_done
+
+       MOVQ CX, AX
+       SHRQ $3, AX
+       LEAQ (DI)(CX*1), R8
+       LEAQ (DI)(CX*2), R9
+       LEAQ (R8)(CX*2), R10
+       LEAQ (DI)(CX*4), R11
+       LEAQ (R8)(CX*4), R12
+       LEAQ (R9)(CX*4), R13
+       LEAQ (R10)(CX*4), R14
+       MOVQ AX, R15
+       SHLQ $3, R15
+       SUBQ R15, CX
+
+       TESTQ AX, AX
+       JZ encode_w8_tail
+
+encode_w8_vector:
+       VMOVQ 0(SI), X0
+       VMOVQ 8(SI), X1
+       VMOVQ 16(SI), X2
+       VMOVQ 24(SI), X3
+       VMOVQ 32(SI), X4
+       VMOVQ 40(SI), X5
+       VMOVQ 48(SI), X6
+       VMOVQ 56(SI), X7
+
+       VPUNPCKLBW X1, X0, X8
+       VPUNPCKLBW X3, X2, X9
+       VPUNPCKLBW X5, X4, X10
+       VPUNPCKLBW X7, X6, X11
+
+       VPUNPCKLWD X9, X8, X12
+       VPUNPCKHWD X9, X8, X13
+       VPUNPCKLWD X11, X10, X14
+       VPUNPCKHWD X11, X10, X15
+
+       VPUNPCKLDQ X14, X12, X0
+       VPUNPCKHDQ X14, X12, X1
+       VPUNPCKLDQ X15, X13, X2
+       VPUNPCKHDQ X15, X13, X3
+
+       VMOVQ X0, (DI)
+       VPSRLDQ $8, X0, X0
+       VMOVQ X0, (R8)
+       VMOVQ X1, (R9)
+       VPSRLDQ $8, X1, X1
+       VMOVQ X1, (R10)
+       VMOVQ X2, (R11)
+       VPSRLDQ $8, X2, X2
+       VMOVQ X2, (R12)
+       VMOVQ X3, (R13)
+       VPSRLDQ $8, X3, X3
+       VMOVQ X3, (R14)
+
+       ADDQ $64, SI
+       ADDQ $8, DI
+       ADDQ $8, R8
+       ADDQ $8, R9
+       ADDQ $8, R10
+       ADDQ $8, R11
+       ADDQ $8, R12
+       ADDQ $8, R13
+       ADDQ $8, R14
+       DECQ AX
+       JNZ encode_w8_vector
+
+encode_w8_tail:
+       TESTQ CX, CX
+       JZ encode_w8_done
+
+encode_w8_tail_loop:
+       MOVBQZX 0(SI), BX
+       MOVB BX, (DI)
+       MOVBQZX 1(SI), BX
+       MOVB BX, (R8)
+       MOVBQZX 2(SI), BX
+       MOVB BX, (R9)
+       MOVBQZX 3(SI), BX
+       MOVB BX, (R10)
+       MOVBQZX 4(SI), BX
+       MOVB BX, (R11)
+       MOVBQZX 5(SI), BX
+       MOVB BX, (R12)
+       MOVBQZX 6(SI), BX
+       MOVB BX, (R13)
+       MOVBQZX 7(SI), BX
+       MOVB BX, (R14)
+
+       ADDQ $8, SI
+       INCQ DI
+       INCQ R8
+       INCQ R9
+       INCQ R10
+       INCQ R11
+       INCQ R12
+       INCQ R13
+       INCQ R14
+       DECQ CX
+       JNZ encode_w8_tail_loop
+
+encode_w8_done:
+       VZEROUPPER
+       RET
diff --git a/parquet/internal/encoding/byte_stream_split_encode_neon_arm64.s 
b/parquet/internal/encoding/byte_stream_split_encode_neon_arm64.s
new file mode 100644
index 00000000..a1e6986d
--- /dev/null
+++ b/parquet/internal/encoding/byte_stream_split_encode_neon_arm64.s
@@ -0,0 +1,178 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+//go:build !noasm && !appengine
+// +build !noasm,!appengine
+
+// NEON implementation of BYTE_STREAM_SPLIT encoding.
+
+#include "textflag.h"
+
+// func _encodeByteStreamSplitWidth4NEON(in, out unsafe.Pointer, nValues int)
+//
+// VLD4 deinterleaves sixteen 4-byte values into the four output streams.
+TEXT ·_encodeByteStreamSplitWidth4NEON(SB), NOSPLIT, $0-24
+       MOVD in+0(FP), R0
+       MOVD out+8(FP), R1
+       MOVD nValues+16(FP), R2
+       CBZ R2, encode_w4_neon_done
+
+       MOVD R2, R3
+       LSR $4, R3, R3
+       LSL $4, R3, R12
+       SUB R12, R2, R13
+
+       MOVD R1, R4
+       ADD R2, R1, R5
+       ADD R2, R5, R6
+       ADD R2, R6, R7
+
+       CBZ R3, encode_w4_neon_tail
+
+encode_w4_neon_vector:
+       VLD4 (R0), [V0.B16, V1.B16, V2.B16, V3.B16]
+       VST1 [V0.B16], (R4)
+       VST1 [V1.B16], (R5)
+       VST1 [V2.B16], (R6)
+       VST1 [V3.B16], (R7)
+
+       ADD $64, R0, R0
+       ADD $16, R4, R4
+       ADD $16, R5, R5
+       ADD $16, R6, R6
+       ADD $16, R7, R7
+       SUB $1, R3, R3
+       CBNZ R3, encode_w4_neon_vector
+
+encode_w4_neon_tail:
+       CBZ R13, encode_w4_neon_done
+
+encode_w4_neon_tail_loop:
+       MOVBU 0(R0), R14
+       MOVB R14, (R4)
+       MOVBU 1(R0), R14
+       MOVB R14, (R5)
+       MOVBU 2(R0), R14
+       MOVB R14, (R6)
+       MOVBU 3(R0), R14
+       MOVB R14, (R7)
+
+       ADD $4, R0, R0
+       ADD $1, R4, R4
+       ADD $1, R5, R5
+       ADD $1, R6, R6
+       ADD $1, R7, R7
+       SUB $1, R13, R13
+       CBNZ R13, encode_w4_neon_tail_loop
+
+encode_w4_neon_done:
+       RET
+
+// func _encodeByteStreamSplitWidth8NEON(in, out unsafe.Pointer, nValues int)
+//
+// VLD4 first separates each 8-byte value into its low and high four-byte
+// groups. VUZP then separates those groups into the eight byte streams.
+TEXT ·_encodeByteStreamSplitWidth8NEON(SB), NOSPLIT, $0-24
+       MOVD in+0(FP), R0
+       MOVD out+8(FP), R1
+       MOVD nValues+16(FP), R2
+       CBZ R2, encode_w8_neon_done
+
+       MOVD R2, R3
+       LSR $3, R3, R3
+       LSL $3, R3, R12
+       SUB R12, R2, R13
+
+       MOVD R1, R4
+       ADD R2, R1, R5
+       ADD R2, R5, R6
+       ADD R2, R6, R7
+       LSL $2, R2, R12
+       ADD R12, R1, R8
+       ADD R12, R5, R9
+       ADD R12, R6, R10
+       ADD R12, R7, R11
+
+       CBZ R3, encode_w8_neon_tail
+
+encode_w8_neon_vector:
+       VLD4 (R0), [V0.B16, V1.B16, V2.B16, V3.B16]
+
+       VUZP1 V0.B16, V0.B16, V4.B16
+       VUZP2 V0.B16, V0.B16, V8.B16
+       VUZP1 V1.B16, V1.B16, V5.B16
+       VUZP2 V1.B16, V1.B16, V9.B16
+       VUZP1 V2.B16, V2.B16, V6.B16
+       VUZP2 V2.B16, V2.B16, V10.B16
+       VUZP1 V3.B16, V3.B16, V7.B16
+       VUZP2 V3.B16, V3.B16, V11.B16
+
+       VST1 [V4.B8], (R4)
+       VST1 [V5.B8], (R5)
+       VST1 [V6.B8], (R6)
+       VST1 [V7.B8], (R7)
+       VST1 [V8.B8], (R8)
+       VST1 [V9.B8], (R9)
+       VST1 [V10.B8], (R10)
+       VST1 [V11.B8], (R11)
+
+       ADD $64, R0, R0
+       ADD $8, R4, R4
+       ADD $8, R5, R5
+       ADD $8, R6, R6
+       ADD $8, R7, R7
+       ADD $8, R8, R8
+       ADD $8, R9, R9
+       ADD $8, R10, R10
+       ADD $8, R11, R11
+       SUB $1, R3, R3
+       CBNZ R3, encode_w8_neon_vector
+
+encode_w8_neon_tail:
+       CBZ R13, encode_w8_neon_done
+
+encode_w8_neon_tail_loop:
+       MOVBU 0(R0), R14
+       MOVB R14, (R4)
+       MOVBU 1(R0), R14
+       MOVB R14, (R5)
+       MOVBU 2(R0), R14
+       MOVB R14, (R6)
+       MOVBU 3(R0), R14
+       MOVB R14, (R7)
+       MOVBU 4(R0), R14
+       MOVB R14, (R8)
+       MOVBU 5(R0), R14
+       MOVB R14, (R9)
+       MOVBU 6(R0), R14
+       MOVB R14, (R10)
+       MOVBU 7(R0), R14
+       MOVB R14, (R11)
+
+       ADD $8, R0, R0
+       ADD $1, R4, R4
+       ADD $1, R5, R5
+       ADD $1, R6, R6
+       ADD $1, R7, R7
+       ADD $1, R8, R8
+       ADD $1, R9, R9
+       ADD $1, R10, R10
+       ADD $1, R11, R11
+       SUB $1, R13, R13
+       CBNZ R13, encode_w8_neon_tail_loop
+
+encode_w8_neon_done:
+       RET
diff --git a/parquet/internal/encoding/byte_stream_split_encode_test.go 
b/parquet/internal/encoding/byte_stream_split_encode_test.go
new file mode 100644
index 00000000..23b545b7
--- /dev/null
+++ b/parquet/internal/encoding/byte_stream_split_encode_test.go
@@ -0,0 +1,112 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 encoding
+
+import (
+       "bytes"
+       "fmt"
+       "testing"
+)
+
+func TestEncodeByteStreamSplitWidth4(t *testing.T) {
+       testEncodeByteStreamSplit(t, 4, encodeByteStreamSplitWidth4Impl)
+}
+
+func TestEncodeByteStreamSplitWidth8(t *testing.T) {
+       testEncodeByteStreamSplit(t, 8, encodeByteStreamSplitWidth8Impl)
+}
+
+func testEncodeByteStreamSplit(t *testing.T, width int, implementation 
func([]byte, []byte)) {
+       for _, nValues := range []int{0, 1, 2, 7, 8, 9, 15, 16, 17, 31, 32, 33, 
63, 64, 65, 127, 128, 129, 1023, 1024, 1025} {
+               t.Run(fmt.Sprintf("nValues=%d", nValues), func(t *testing.T) {
+                       input := bytes.Repeat([]byte{0xa5}, nValues*width+2)
+                       in := input[1 : len(input)-1]
+                       for i := range in {
+                               in[i] = byte((i*37 + width) ^ (i >> 3))
+                       }
+                       original := bytes.Clone(input)
+
+                       want := make([]byte, len(in))
+                       output := bytes.Repeat([]byte{0x5a}, len(in)+2)
+                       got := output[1 : len(output)-1]
+                       switch width {
+                       case 4:
+                               encodeByteStreamSplitWidth4(want, in)
+                       case 8:
+                               encodeByteStreamSplitWidth8(want, in)
+                       }
+                       implementation(got, in)
+                       if !bytes.Equal(got, want) {
+                               t.Fatalf("encoded output mismatch: got %x, want 
%x", got, want)
+                       }
+                       if output[0] != 0x5a || output[len(output)-1] != 0x5a {
+                               t.Fatal("encoding modified bytes outside the 
output slice")
+                       }
+                       if !bytes.Equal(input, original) {
+                               t.Fatal("encoding modified the input")
+                       }
+               })
+       }
+}
+
+func BenchmarkEncodeByteStreamSplitWidth4(b *testing.B) {
+       benchmarkEncodeByteStreamSplit(b, 4)
+}
+
+func BenchmarkEncodeByteStreamSplitWidth8(b *testing.B) {
+       benchmarkEncodeByteStreamSplit(b, 8)
+}
+
+func benchmarkEncodeByteStreamSplit(b *testing.B, width int) {
+       for _, nValues := range []int{8, 1024, 65536} {
+               b.Run(fmt.Sprintf("nValues=%d", nValues), func(b *testing.B) {
+                       in := make([]byte, nValues*width)
+                       for i := range in {
+                               in[i] = byte((i*37 + width) ^ (i >> 3))
+                       }
+                       out := make([]byte, len(in))
+
+                       b.Run("scalar", func(b *testing.B) {
+                               b.SetBytes(int64(len(in)))
+                               b.ReportAllocs()
+                               b.ResetTimer()
+                               for i := 0; i < b.N; i++ {
+                                       switch width {
+                                       case 4:
+                                               
encodeByteStreamSplitWidth4(out, in)
+                                       case 8:
+                                               
encodeByteStreamSplitWidth8(out, in)
+                                       }
+                               }
+                       })
+
+                       b.Run("dispatch", func(b *testing.B) {
+                               b.SetBytes(int64(len(in)))
+                               b.ReportAllocs()
+                               b.ResetTimer()
+                               for i := 0; i < b.N; i++ {
+                                       switch width {
+                                       case 4:
+                                               
encodeByteStreamSplitWidth4Impl(out, in)
+                                       case 8:
+                                               
encodeByteStreamSplitWidth8Impl(out, in)
+                                       }
+                               }
+                       })
+               })
+       }
+}
diff --git a/parquet/internal/encoding/fixed_len_byte_array_encoder.go 
b/parquet/internal/encoding/fixed_len_byte_array_encoder.go
index 190802ee..e4c3c650 100644
--- a/parquet/internal/encoding/fixed_len_byte_array_encoder.go
+++ b/parquet/internal/encoding/fixed_len_byte_array_encoder.go
@@ -110,9 +110,9 @@ func (enc *ByteStreamSplitFixedLenByteArrayEncoder) 
FlushValues() (Buffer, error
        case 2:
                encodeByteStreamSplitWidth2(enc.flushBuffer.Bytes(), in.Bytes())
        case 4:
-               encodeByteStreamSplitWidth4(enc.flushBuffer.Bytes(), in.Bytes())
+               encodeByteStreamSplitWidth4Impl(enc.flushBuffer.Bytes(), 
in.Bytes())
        case 8:
-               encodeByteStreamSplitWidth8(enc.flushBuffer.Bytes(), in.Bytes())
+               encodeByteStreamSplitWidth8Impl(enc.flushBuffer.Bytes(), 
in.Bytes())
        default:
                encodeByteStreamSplit(enc.flushBuffer.Bytes(), in.Bytes(), 
enc.typeLen)
        }

Reply via email to