github.com/apache/arrow/go/v14@v14.0.1/parquet/pqarrow/reader_writer_test.go (about)

     1  // Licensed to the Apache Software Foundation (ASF) under one
     2  // or more contributor license agreements.  See the NOTICE file
     3  // distributed with this work for additional information
     4  // regarding copyright ownership.  The ASF licenses this file
     5  // to you under the Apache License, Version 2.0 (the
     6  // "License"); you may not use this file except in compliance
     7  // with the License.  You may obtain a copy of the License at
     8  //
     9  // http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package pqarrow_test
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"testing"
    23  	"unsafe"
    24  
    25  	"github.com/apache/arrow/go/v14/arrow"
    26  	"github.com/apache/arrow/go/v14/arrow/array"
    27  	"github.com/apache/arrow/go/v14/arrow/memory"
    28  	"github.com/apache/arrow/go/v14/parquet"
    29  	"github.com/apache/arrow/go/v14/parquet/file"
    30  	"github.com/apache/arrow/go/v14/parquet/pqarrow"
    31  	"golang.org/x/exp/rand"
    32  	"gonum.org/v1/gonum/stat/distuv"
    33  )
    34  
    35  const alternateOrNA = -1
    36  const SIZELEN = 1024 * 1024
    37  
    38  func randomUint8(size, truePct int, sampleVals [2]uint8, seed uint64) []uint8 {
    39  	ret := make([]uint8, size)
    40  	if truePct == alternateOrNA {
    41  		for idx := range ret {
    42  			ret[idx] = uint8(idx % 2)
    43  		}
    44  		return ret
    45  	}
    46  
    47  	dist := distuv.Bernoulli{
    48  		P:   float64(truePct) / 100.0,
    49  		Src: rand.NewSource(seed),
    50  	}
    51  
    52  	for idx := range ret {
    53  		ret[idx] = sampleVals[int(dist.Rand())]
    54  	}
    55  	return ret
    56  }
    57  
    58  func randomInt32(size, truePct int, sampleVals [2]int32, seed uint64) []int32 {
    59  	ret := make([]int32, size)
    60  	if truePct == alternateOrNA {
    61  		for idx := range ret {
    62  			ret[idx] = int32(idx % 2)
    63  		}
    64  		return ret
    65  	}
    66  
    67  	dist := distuv.Bernoulli{
    68  		P:   float64(truePct) / 100.0,
    69  		Src: rand.NewSource(seed),
    70  	}
    71  
    72  	for idx := range ret {
    73  		ret[idx] = sampleVals[int(dist.Rand())]
    74  	}
    75  	return ret
    76  }
    77  
    78  func tableFromVec(dt arrow.DataType, size int, data interface{}, nullable bool, nullPct int) arrow.Table {
    79  	if !nullable && nullPct != alternateOrNA {
    80  		panic("bad check")
    81  	}
    82  
    83  	var valid []bool
    84  	if nullable {
    85  		// true values select index 1 of sample values
    86  		validBytes := randomUint8(size, nullPct, [2]uint8{1, 0}, 500)
    87  		valid = *(*[]bool)(unsafe.Pointer(&validBytes))
    88  	}
    89  
    90  	bldr := array.NewBuilder(memory.DefaultAllocator, dt)
    91  	defer bldr.Release()
    92  
    93  	switch v := data.(type) {
    94  	case []int32:
    95  		bldr.(*array.Int32Builder).AppendValues(v, valid)
    96  	case []int64:
    97  		bldr.(*array.Int64Builder).AppendValues(v, valid)
    98  	case []float32:
    99  		bldr.(*array.Float32Builder).AppendValues(v, valid)
   100  	case []float64:
   101  		bldr.(*array.Float64Builder).AppendValues(v, valid)
   102  	}
   103  
   104  	arr := bldr.NewArray()
   105  
   106  	field := arrow.Field{Name: "column", Type: dt, Nullable: nullable}
   107  	sc := arrow.NewSchema([]arrow.Field{field}, nil)
   108  	col := arrow.NewColumnFromArr(field, arr)
   109  	defer col.Release()
   110  	return array.NewTable(sc, []arrow.Column{col}, int64(size))
   111  }
   112  
   113  func BenchmarkWriteColumn(b *testing.B) {
   114  	int32Values := make([]int32, SIZELEN)
   115  	int64Values := make([]int64, SIZELEN)
   116  	float32Values := make([]float32, SIZELEN)
   117  	float64Values := make([]float64, SIZELEN)
   118  	for i := 0; i < SIZELEN; i++ {
   119  		int32Values[i] = 128
   120  		int64Values[i] = 128
   121  		float32Values[i] = 128
   122  		float64Values[i] = 128
   123  	}
   124  
   125  	tests := []struct {
   126  		name     string
   127  		dt       arrow.DataType
   128  		values   interface{}
   129  		nullable bool
   130  		nbytes   int64
   131  	}{
   132  		{"int32 not nullable", arrow.PrimitiveTypes.Int32, int32Values, false, int64(arrow.Int32Traits.BytesRequired(SIZELEN))},
   133  		{"int32 nullable", arrow.PrimitiveTypes.Int32, int32Values, true, int64(arrow.Int32Traits.BytesRequired(SIZELEN))},
   134  		{"int64 not nullable", arrow.PrimitiveTypes.Int64, int64Values, false, int64(arrow.Int64Traits.BytesRequired(SIZELEN))},
   135  		{"int64 nullable", arrow.PrimitiveTypes.Int64, int64Values, true, int64(arrow.Int64Traits.BytesRequired(SIZELEN))},
   136  		{"float32 not nullable", arrow.PrimitiveTypes.Float32, float32Values, false, int64(arrow.Float32Traits.BytesRequired(SIZELEN))},
   137  		{"float32 nullable", arrow.PrimitiveTypes.Float32, float32Values, true, int64(arrow.Float32Traits.BytesRequired(SIZELEN))},
   138  		{"float64 not nullable", arrow.PrimitiveTypes.Float64, float64Values, false, int64(arrow.Float64Traits.BytesRequired(SIZELEN))},
   139  		{"float64 nullable", arrow.PrimitiveTypes.Float64, float64Values, true, int64(arrow.Float64Traits.BytesRequired(SIZELEN))},
   140  	}
   141  
   142  	props := parquet.NewWriterProperties(parquet.WithDictionaryDefault(false))
   143  	arrProps := pqarrow.DefaultWriterProps()
   144  
   145  	for _, tt := range tests {
   146  		b.Run(tt.name, func(b *testing.B) {
   147  			tbl := tableFromVec(tt.dt, SIZELEN, tt.values, tt.nullable, alternateOrNA)
   148  			b.Cleanup(func() { tbl.Release() })
   149  			var buf bytes.Buffer
   150  			buf.Grow(int(tt.nbytes))
   151  			b.ResetTimer()
   152  			b.SetBytes(tt.nbytes)
   153  
   154  			for i := 0; i < b.N; i++ {
   155  				buf.Reset()
   156  				err := pqarrow.WriteTable(tbl, &buf, SIZELEN, props, arrProps)
   157  				if err != nil {
   158  					b.Error(err)
   159  				}
   160  			}
   161  		})
   162  	}
   163  }
   164  
   165  func benchReadTable(b *testing.B, name string, tbl arrow.Table, nbytes int64) {
   166  	props := parquet.NewWriterProperties(parquet.WithDictionaryDefault(false))
   167  	arrProps := pqarrow.DefaultWriterProps()
   168  
   169  	var buf bytes.Buffer
   170  	if err := pqarrow.WriteTable(tbl, &buf, SIZELEN, props, arrProps); err != nil {
   171  		b.Error(err)
   172  	}
   173  	ctx := context.Background()
   174  
   175  	b.ResetTimer()
   176  	b.Run(name, func(b *testing.B) {
   177  		b.SetBytes(nbytes)
   178  
   179  		for i := 0; i < b.N; i++ {
   180  			pf, err := file.NewParquetReader(bytes.NewReader(buf.Bytes()))
   181  			if err != nil {
   182  				b.Error(err)
   183  			}
   184  
   185  			reader, err := pqarrow.NewFileReader(pf, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator)
   186  			if err != nil {
   187  				b.Error(err)
   188  			}
   189  
   190  			tbl, err := reader.ReadTable(ctx)
   191  			if err != nil {
   192  				b.Error(err)
   193  			}
   194  			defer tbl.Release()
   195  		}
   196  	})
   197  }
   198  
   199  func BenchmarkReadColumnInt32(b *testing.B) {
   200  	tests := []struct {
   201  		name     string
   202  		nullable bool
   203  		nullPct  int
   204  		fvPct    int
   205  	}{
   206  		{"int32 not null 1pct", false, alternateOrNA, 1},
   207  		{"int32 not null 10pct", false, alternateOrNA, 10},
   208  		{"int32 not null 50pct", false, alternateOrNA, 50},
   209  		{"int32 nullable alt", true, alternateOrNA, 0},
   210  		{"int32 nullable 1pct 1pct", true, 1, 1},
   211  		{"int32 nullable 10pct 10pct", true, 10, 10},
   212  		{"int32 nullable 25pct 5pct", true, 25, 5},
   213  		{"int32 nullable 50pct 50pct", true, 50, 50},
   214  		{"int32 nullable 50pct 0pct", true, 50, 0},
   215  		{"int32 nullable 99pct 50pct", true, 99, 50},
   216  		{"int32 nullable 99pct 0pct", true, 99, 0},
   217  	}
   218  
   219  	for _, tt := range tests {
   220  		values := randomInt32(SIZELEN, tt.fvPct, [2]int32{127, 128}, 500)
   221  		tbl := tableFromVec(arrow.PrimitiveTypes.Int32, SIZELEN, values, tt.nullable, tt.nullPct)
   222  		benchReadTable(b, tt.name, tbl, int64(arrow.Int32Traits.BytesRequired(SIZELEN)))
   223  	}
   224  }
   225  
   226  func BenchmarkReadColumnInt64(b *testing.B) {
   227  	tests := []struct {
   228  		name     string
   229  		nullable bool
   230  		nullPct  int
   231  		fvPct    int
   232  	}{
   233  		{"int64 not null 1pct", false, alternateOrNA, 1},
   234  		{"int64 not null 10pct", false, alternateOrNA, 10},
   235  		{"int64 not null 50pct", false, alternateOrNA, 50},
   236  		{"int64 nullable alt", true, alternateOrNA, 0},
   237  		{"int64 nullable 1pct 1pct", true, 1, 1},
   238  		{"int64 nullable 5pct 5pct", true, 5, 5},
   239  		{"int64 nullable 10pct 5pct", true, 10, 5},
   240  		{"int64 nullable 25pct 10pct", true, 25, 10},
   241  		{"int64 nullable 30pct 10pct", true, 30, 10},
   242  		{"int64 nullable 35pct 10pct", true, 35, 10},
   243  		{"int64 nullable 45pct 25pct", true, 45, 25},
   244  		{"int64 nullable 50pct 50pct", true, 50, 50},
   245  		{"int64 nullable 50pct 1pct", true, 50, 1},
   246  		{"int64 nullable 75pct 1pct", true, 75, 1},
   247  		{"int64 nullable 99pct 50pct", true, 99, 50},
   248  		{"int64 nullable 99pct 0pct", true, 99, 0},
   249  	}
   250  
   251  	for _, tt := range tests {
   252  		values := randomInt32(SIZELEN, tt.fvPct, [2]int32{127, 128}, 500)
   253  		tbl := tableFromVec(arrow.PrimitiveTypes.Int32, SIZELEN, values, tt.nullable, tt.nullPct)
   254  		benchReadTable(b, tt.name, tbl, int64(arrow.Int32Traits.BytesRequired(SIZELEN)))
   255  	}
   256  }
   257  
   258  func BenchmarkReadColumnFloat64(b *testing.B) {
   259  	tests := []struct {
   260  		name     string
   261  		nullable bool
   262  		nullPct  int
   263  		fvPct    int
   264  	}{
   265  		{"double not null 1pct", false, alternateOrNA, 0},
   266  		{"double not null 20pct", false, alternateOrNA, 20},
   267  		{"double nullable alt", true, alternateOrNA, 0},
   268  		{"double nullable 10pct 50pct", true, 10, 50},
   269  		{"double nullable 25pct 25pct", true, 25, 25},
   270  	}
   271  
   272  	for _, tt := range tests {
   273  		values := randomInt32(SIZELEN, tt.fvPct, [2]int32{127, 128}, 500)
   274  		tbl := tableFromVec(arrow.PrimitiveTypes.Int32, SIZELEN, values, tt.nullable, tt.nullPct)
   275  		benchReadTable(b, tt.name, tbl, int64(arrow.Int32Traits.BytesRequired(SIZELEN)))
   276  	}
   277  }