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 }