github.com/grailbio/bigslice@v0.0.0-20230519005545-30c4c12152ad/frame/frame_test.go (about)

     1  // Copyright 2018 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package frame
     6  
     7  import (
     8  	"fmt"
     9  	"math/rand"
    10  	"reflect"
    11  	"runtime"
    12  	"sort"
    13  	"testing"
    14  
    15  	fuzz "github.com/google/gofuzz"
    16  	"github.com/grailbio/bigslice/slicetype"
    17  )
    18  
    19  var (
    20  	typeOfString = reflect.TypeOf("")
    21  	typeOfInt    = reflect.TypeOf(0)
    22  
    23  	testType = slicetype.New(typeOfString, typeOfInt)
    24  )
    25  
    26  func assertEqual(t *testing.T, f, g Frame) {
    27  	t.Helper()
    28  	if got, want := f.Len(), g.Len(); got != want {
    29  		t.Fatalf("length mismatch: got %v, want %v", got, want)
    30  	}
    31  	if got, want := f.NumOut(), g.NumOut(); got != want {
    32  		t.Fatalf("column length mismatch: got %v, want %v", got, want)
    33  	}
    34  	// Test two different ways just to stress the system:
    35  	for i := 0; i < f.NumOut(); i++ {
    36  		if got, want := f.Interface(i), g.Interface(i); !reflect.DeepEqual(got, want) {
    37  			t.Errorf("column %d not equal: got %v, want %v", i, got, want)
    38  		}
    39  	}
    40  	for i := 0; i < f.NumOut(); i++ {
    41  		for j := 0; j < f.Len(); j++ {
    42  			if got, want := f.Index(i, j).Interface(), g.Index(i, j).Interface(); !reflect.DeepEqual(got, want) {
    43  				t.Errorf("value %d,%d not equal: got %v, want %v", i, j, got, want)
    44  			}
    45  		}
    46  	}
    47  }
    48  
    49  func assertZeros(t *testing.T, f Frame) {
    50  	t.Helper()
    51  	for i := 0; i < f.NumOut(); i++ {
    52  		for j := 0; j < f.Len(); j++ {
    53  			if got, want := f.Index(i, j).Interface(), reflect.Zero(f.Out(i)).Interface(); !reflect.DeepEqual(got, want) {
    54  				t.Errorf("not zero %d,%d: got %v, want %v", i, j, got, want)
    55  			}
    56  		}
    57  	}
    58  }
    59  
    60  func fuzzFrame(min int) Frame {
    61  	fz := fuzz.New()
    62  	fz.NilChance(0)
    63  	fz.NumElements(min, min*10)
    64  	var (
    65  		ints []int
    66  		strs []string
    67  	)
    68  	fz.Fuzz(&ints)
    69  	fz.Fuzz(&strs)
    70  	if len(ints) < len(strs) {
    71  		strs = strs[:len(ints)]
    72  	} else {
    73  		ints = ints[:len(strs)]
    74  	}
    75  	return Slices(ints, strs)
    76  }
    77  
    78  func TestSliceRetain(t *testing.T) {
    79  	typ := slicetype.New(typeOfString, typeOfInt)
    80  	frame := Make(typ, 1024, 1024)
    81  	f2 := frame.Slice(0, 200)
    82  	x := f2.Interface(1).([]int)
    83  	if got, want := len(x), 200; got != want {
    84  		t.Errorf("got %v, want %v", got, want)
    85  	}
    86  	frame = Frame{}
    87  	f2 = Frame{}
    88  	runtime.GC()
    89  	if got, want := len(x), 200; got != want {
    90  		t.Errorf("got %v, want %v", got, want)
    91  	}
    92  }
    93  
    94  func TestSlice(t *testing.T) {
    95  	f := fuzzFrame(10)
    96  	assertEqual(t, f.Slice(1, 6).Slice(1, 2), f.Slice(2, 3))
    97  	assertEqual(t, f, f.Slice(0, f.Len()))
    98  	assertEqual(t, f.Slice(0, f.Cap()), f.Slice(0, f.Cap()))
    99  
   100  	g := f.Slice(0, 0)
   101  	if got, want := g.Len(), 0; got != want {
   102  		t.Errorf("got %v, want %v", got, want)
   103  	}
   104  	assertEqual(t, f.Slice(0, 0).Slice(0, f.Len()), f)
   105  }
   106  
   107  func TestCopy(t *testing.T) {
   108  	f := fuzzFrame(100)
   109  	g := Make(f, f.Len(), f.Len())
   110  	if got, want := Copy(g.Slice(0, 1), f), 1; got != want {
   111  		t.Errorf("got %v, want %v", got, want)
   112  	}
   113  	assertEqual(t, f.Slice(0, 1), g.Slice(0, 1))
   114  	if got, want := Copy(g.Slice(50, g.Len()), f), f.Len()-50; got != want {
   115  		t.Errorf("got %v, want %v", got, want)
   116  	}
   117  	assertEqual(t, g.Slice(50, f.Len()-50), f.Slice(0, f.Len()-100))
   118  }
   119  
   120  func TestAppendFrame(t *testing.T) {
   121  	f := fuzzFrame(100)
   122  	g := AppendFrame(f, f)
   123  	if got, want := g.Len(), f.Len()*2; got != want {
   124  		t.Errorf("got %v, want %v", got, want)
   125  	}
   126  	assertEqual(t, f, g.Slice(0, f.Len()))
   127  	assertEqual(t, f, g.Slice(f.Len(), g.Len()))
   128  	g = fuzzFrame(1000)
   129  	assertEqual(t, AppendFrame(f, g), AppendFrame(f, g))
   130  }
   131  
   132  func TestGrow(t *testing.T) {
   133  	f := fuzzFrame(100)
   134  	g := f.Grow(f.Len())
   135  	if got, want := g.Len(), f.Len()*2; got != want {
   136  		t.Errorf("got %v, want %v", got, want)
   137  	}
   138  	assertEqual(t, g.Slice(0, f.Len()), f)
   139  	assertZeros(t, g.Slice(f.Len(), g.Len()))
   140  }
   141  
   142  func TestEnsure(t *testing.T) {
   143  	const N = 50
   144  	f := fuzzFrame(100)
   145  	g := f.Ensure(f.Len() + N)
   146  	if got, want := g.Len(), f.Len()+N; got != want {
   147  		t.Errorf("got %v, want %v", got, want)
   148  	}
   149  	assertEqual(t, g.Slice(0, f.Len()), f)
   150  	assertZeros(t, g.Slice(f.Len(), f.Len()+N))
   151  }
   152  
   153  func TestSwap(t *testing.T) {
   154  	f := fuzzFrame(100)
   155  	g := Make(f, f.Len(), f.Len())
   156  	Copy(g, f)
   157  	for i := 1; i < f.Len(); i++ {
   158  		f.Swap(i-1, i)
   159  	}
   160  	assertEqual(t, g.Slice(0, 1), f.Slice(f.Len()-1, f.Len()))
   161  	assertEqual(t, g.Slice(1, g.Len()), f.Slice(0, f.Len()-1))
   162  }
   163  
   164  func TestValue(t *testing.T) {
   165  	f := fuzzFrame(100)
   166  	if got, want := f.Value(0).Len(), f.Len(); got != want {
   167  		t.Errorf("got %v, want %v", got, want)
   168  	}
   169  	var g Frame
   170  	for i := 0; i < 100; i++ {
   171  		g = AppendFrame(g, f.Slice(0, 1))
   172  		if got, want := g.Value(0).Len(), g.Len(); got != want {
   173  			t.Errorf("got %v, want %v", got, want)
   174  		}
   175  	}
   176  }
   177  
   178  func TestZero(t *testing.T) {
   179  	f := fuzzFrame(100)
   180  	f.Slice(1, 50).Zero()
   181  	g := Make(f, f.Len(), f.Len())
   182  	Copy(g, f)
   183  	assertEqual(t, f.Slice(1, 50), Make(f, 49, 49))
   184  	assertEqual(t, f.Slice(0, 1), g.Slice(0, 1))
   185  	assertEqual(t, f.Slice(50, f.Len()), g.Slice(50, g.Len()))
   186  }
   187  
   188  func TestZerox(t *testing.T) {
   189  	type struct1 struct {
   190  		_, _, _ int
   191  	}
   192  	type struct2 struct {
   193  		_ [10000]byte
   194  	}
   195  	type struct3 struct {
   196  		// nolint: unused
   197  		struct1
   198  		_ *struct2
   199  		_ string
   200  	}
   201  	slices := []interface{}{
   202  		new([]int),
   203  		new([]int64),
   204  		new([]struct1),
   205  		new([]struct2),
   206  		new([]struct3),
   207  		new([]string),
   208  		new([]byte),
   209  	}
   210  	fz := fuzz.New()
   211  	fz.NilChance(0)
   212  	fz.NumElements(100, 1000)
   213  	for _, slicep := range slices {
   214  		fz.Fuzz(slicep)
   215  		slice := reflect.Indirect(reflect.ValueOf(slicep)).Interface()
   216  		frame := Slices(slice)
   217  		frame.Zero()
   218  		slicev := frame.Value(0)
   219  		zero := reflect.Zero(slicev.Type().Elem()).Interface()
   220  		for i := 0; i < slicev.Len(); i++ {
   221  			if !reflect.DeepEqual(zero, slicev.Index(i).Interface()) {
   222  				t.Errorf("index %d of slice %v not zero", i, slicep)
   223  			}
   224  		}
   225  	}
   226  }
   227  
   228  func TestUnsafeIndexPointer(t *testing.T) {
   229  	f := fuzzFrame(100)
   230  	c0 := f.Interface(0).([]int)
   231  	c1 := f.Interface(1).([]string)
   232  	for i := 0; i < f.Len(); i++ {
   233  		v0 := *(*int)(f.UnsafeIndexPointer(0, i))
   234  		if got, want := v0, c0[i]; got != want {
   235  			t.Errorf("got %v, want %v", got, want)
   236  		}
   237  		v1 := *(*string)(f.UnsafeIndexPointer(1, i))
   238  		if got, want := v1, c1[i]; got != want {
   239  			t.Errorf("got %v, want %v", got, want)
   240  		}
   241  	}
   242  }
   243  
   244  func TestSort(t *testing.T) {
   245  	f := fuzzFrame(1000)
   246  	if sort.IsSorted(f) {
   247  		t.Fatal("unlikely")
   248  	}
   249  	sort.Sort(f)
   250  	if !sort.IsSorted(f) {
   251  		t.Error("failed to sort")
   252  	}
   253  
   254  	n := f.Len()
   255  	g := Make(f, n*2, n*2)
   256  	Copy(g, f)
   257  	// make sure these are all empty strings so that the sort tests grouping
   258  	vals := f.Interface(1).([]string)
   259  	for i := range vals {
   260  		vals[i] = ""
   261  	}
   262  	Copy(g.Slice(n, n*2), f)
   263  	g = g.Prefixed(2)
   264  	sort.Sort(g)
   265  	if !sort.IsSorted(g) {
   266  		t.Error("failed to sort (grouped)")
   267  	}
   268  }
   269  
   270  var copySizes = [...]int{8, 32, 256, 1024, 65536}
   271  
   272  func benchmarkCopy(b *testing.B, copy func(dst, src Frame, i0, i1 int)) {
   273  	b.Helper()
   274  	for _, size := range copySizes {
   275  		src, dst := Make(testType, size, size), Make(testType, size, size)
   276  		for _, len := range []int{size / 2, size} {
   277  			bench := func(b *testing.B) {
   278  				b.ReportAllocs()
   279  				b.SetBytes(int64(int(typeOfInt.Size())*size + int(typeOfString.Size())*size))
   280  				b.ResetTimer()
   281  				for i := 0; i < b.N; i++ {
   282  					copy(dst, src, 0, len)
   283  				}
   284  			}
   285  			name := fmt.Sprintf("size=%d,len=%d", size, len)
   286  			b.Run(name, bench)
   287  		}
   288  	}
   289  }
   290  
   291  func BenchmarkCopy(b *testing.B) {
   292  	benchmarkCopy(b, func(dst, src Frame, i0, i1 int) {
   293  		Copy(dst.Slice(i0, i1), src)
   294  	})
   295  }
   296  
   297  func BenchmarkReflectCopy(b *testing.B) {
   298  	benchmarkCopy(b, func(dst, src Frame, i0, i1 int) {
   299  		for i := 0; i < dst.NumOut(); i++ {
   300  			reflect.Copy(dst.Value(i).Slice(i0, i1), src.Value(i))
   301  		}
   302  	})
   303  }
   304  
   305  func benchmarkAssign(b *testing.B, assign func(dst, src Frame)) {
   306  	b.Helper()
   307  	const N = 1024
   308  	src, dst := Make(testType, N, N), Make(testType, N, N)
   309  	b.ReportAllocs()
   310  	b.SetBytes(int64(typeOfInt.Size()))
   311  	b.ResetTimer()
   312  	for i := 0; i < b.N; i++ {
   313  		assign(dst, src)
   314  	}
   315  }
   316  
   317  func BenchmarkUnsafeAssign(b *testing.B) {
   318  	benchmarkAssign(b, func(dst, src Frame) {
   319  		*(*int)(dst.UnsafeIndexPointer(0, 0)) =
   320  			*(*int)(src.UnsafeIndexPointer(0, 0))
   321  	})
   322  }
   323  
   324  func BenchmarkAssign(b *testing.B) {
   325  	benchmarkAssign(b, func(dst, src Frame) {
   326  		dst.Index(0, 0).Set(src.Index(0, 0))
   327  	})
   328  }
   329  
   330  const Nsort = 2 << 19
   331  
   332  func makeInts(n int) (data []int, shuffle func()) {
   333  	rnd := rand.New(rand.NewSource(int64(n)))
   334  	slice := rnd.Perm(n)
   335  	return slice, func() {
   336  		rnd.Shuffle(n, func(i, j int) { slice[i], slice[j] = slice[j], slice[i] })
   337  	}
   338  }
   339  
   340  func BenchmarkSort(b *testing.B) {
   341  	slice, shuffle := makeInts(Nsort)
   342  	f := Slices(slice)
   343  	b.ReportAllocs()
   344  	b.ResetTimer()
   345  	_ = shuffle
   346  
   347  	for i := 0; i < b.N; i++ {
   348  		sort.Sort(f)
   349  		shuffle()
   350  	}
   351  }
   352  
   353  func BenchmarkSortSlice(b *testing.B) {
   354  	slice, shuffle := makeInts(Nsort)
   355  	b.ReportAllocs()
   356  	b.ResetTimer()
   357  	_ = shuffle
   358  	for i := 0; i < b.N; i++ {
   359  		sort.Slice(slice, func(i, j int) bool {
   360  			return slice[i] < slice[j]
   361  		})
   362  		shuffle()
   363  	}
   364  }
   365  
   366  func BenchmarkSortFrameSlice(b *testing.B) {
   367  	slice, shuffle := makeInts(Nsort)
   368  	f := Slices(slice)
   369  	b.ReportAllocs()
   370  	b.ResetTimer()
   371  	_ = shuffle
   372  	for i := 0; i < b.N; i++ {
   373  		sort.Slice(slice, func(i, j int) bool { return f.data[0].ops.Less(i, j) })
   374  		shuffle()
   375  	}
   376  }