|
| 1 | +//go:build arm64 && !gccgo && !appengine |
| 2 | +// +build arm64,!gccgo,!appengine |
| 3 | + |
| 4 | +package roaring |
| 5 | + |
| 6 | +import ( |
| 7 | + "fmt" |
| 8 | + "math/rand" |
| 9 | + "sort" |
| 10 | + "testing" |
| 11 | +) |
| 12 | + |
| 13 | +const intersectBenchVariants = 8 |
| 14 | + |
| 15 | +func benchIntersectPair(shape string, n int, seed int64) (a, b []uint16) { |
| 16 | + r := rand.New(rand.NewSource(int64(42+n) + seed*7919)) |
| 17 | + switch shape { |
| 18 | + case "dense50": |
| 19 | + return genSortedUnique(r, n, 2*n), genSortedUnique(r, n, 2*n) |
| 20 | + case "coinflip": // disjoint 8-blocks, shuffled ownership: zero matches |
| 21 | + nb := (n + 7) / 8 |
| 22 | + own := make([]bool, 2*nb) |
| 23 | + for i := 0; i < nb; i++ { |
| 24 | + own[i] = true |
| 25 | + } |
| 26 | + r.Shuffle(len(own), func(i, j int) { own[i], own[j] = own[j], own[i] }) |
| 27 | + a = make([]uint16, 0, nb*8) |
| 28 | + b = make([]uint16, 0, nb*8) |
| 29 | + base := 0 |
| 30 | + for _, toA := range own { |
| 31 | + for x := 0; x < 8; x++ { |
| 32 | + if toA { |
| 33 | + a = append(a, uint16(base+x)) |
| 34 | + } else { |
| 35 | + b = append(b, uint16(base+x)) |
| 36 | + } |
| 37 | + } |
| 38 | + base += 8 |
| 39 | + } |
| 40 | + return a[:n], b[:n] |
| 41 | + case "skew8": // one side 8x longer, below the 64:1 galloping cutoff |
| 42 | + big := 8 * n |
| 43 | + if big > 32768 { |
| 44 | + big = 32768 |
| 45 | + } |
| 46 | + return genSortedUnique(r, n, 65536), genSortedUnique(r, big, 65536) |
| 47 | + case "overlap95": // ~95% shared elements: near-total match density |
| 48 | + a = genSortedUnique(r, n, 4*n) |
| 49 | + b = append([]uint16(nil), a...) |
| 50 | + for i := 10; i < n; i += 20 { |
| 51 | + b[i] ^= 1 |
| 52 | + } |
| 53 | + sort.Slice(b, func(i, j int) bool { return b[i] < b[j] }) |
| 54 | + out := b[:0] |
| 55 | + for i, v := range b { |
| 56 | + if i == 0 || v != b[i-1] { |
| 57 | + out = append(out, v) |
| 58 | + } |
| 59 | + } |
| 60 | + return a, out |
| 61 | + } |
| 62 | + panic("unknown shape") |
| 63 | +} |
| 64 | + |
| 65 | +var benchIntersectShapes = []string{"dense50", "coinflip", "skew8", "overlap95"} |
| 66 | +var benchIntersectSizes = []int{8, 16, 24, 64, 256, 4096} |
| 67 | + |
| 68 | +func BenchmarkIntersect2By2(b *testing.B) { |
| 69 | + for _, shape := range benchIntersectShapes { |
| 70 | + for _, n := range benchIntersectSizes { |
| 71 | + as := make([][]uint16, intersectBenchVariants) |
| 72 | + bs := make([][]uint16, intersectBenchVariants) |
| 73 | + for v := 0; v < intersectBenchVariants; v++ { |
| 74 | + as[v], bs[v] = benchIntersectPair(shape, n, int64(v)) |
| 75 | + } |
| 76 | + // andArray allocates exactly min(len1,len2); mirror that cap. |
| 77 | + mins := make([]int, intersectBenchVariants) |
| 78 | + for v := 0; v < intersectBenchVariants; v++ { |
| 79 | + mins[v] = len(as[v]) |
| 80 | + if len(bs[v]) < mins[v] { |
| 81 | + mins[v] = len(bs[v]) |
| 82 | + } |
| 83 | + } |
| 84 | + buffer := make([]uint16, n+8) |
| 85 | + scratch := make([]uint16, n+8) |
| 86 | + for _, impl := range []struct { |
| 87 | + name string |
| 88 | + fn func([]uint16, []uint16, []uint16) int |
| 89 | + }{ |
| 90 | + {"dispatch", intersection2by2}, |
| 91 | + {"scalar", localintersect2by2}, |
| 92 | + } { |
| 93 | + b.Run(fmt.Sprintf("%s/%d/%s", shape, n, impl.name), func(b *testing.B) { |
| 94 | + sink := 0 |
| 95 | + for i := 0; i < b.N; i++ { |
| 96 | + v := i % intersectBenchVariants |
| 97 | + sink += impl.fn(as[v], bs[v], buffer[:0:mins[v]]) |
| 98 | + } |
| 99 | + _ = sink |
| 100 | + }) |
| 101 | + } |
| 102 | + // iandArray geometry; both rows pay the same restore copy. |
| 103 | + if shape != "dense50" || (n != 16 && n != 4096) { |
| 104 | + continue |
| 105 | + } |
| 106 | + for _, impl := range []struct { |
| 107 | + name string |
| 108 | + fn func([]uint16, []uint16, []uint16) int |
| 109 | + }{ |
| 110 | + {"inplace", intersection2by2}, |
| 111 | + {"inplaceScalar", localintersect2by2}, |
| 112 | + } { |
| 113 | + b.Run(fmt.Sprintf("%s/%d/%s", shape, n, impl.name), func(b *testing.B) { |
| 114 | + sink := 0 |
| 115 | + for i := 0; i < b.N; i++ { |
| 116 | + v := i % intersectBenchVariants |
| 117 | + m := copy(scratch, as[v]) |
| 118 | + sink += impl.fn(scratch[:m], bs[v], scratch[:0:m]) |
| 119 | + } |
| 120 | + _ = sink |
| 121 | + }) |
| 122 | + } |
| 123 | + } |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +func BenchmarkIntersectCard2By2(b *testing.B) { |
| 128 | + for _, shape := range benchIntersectShapes { |
| 129 | + for _, n := range benchIntersectSizes { |
| 130 | + as := make([][]uint16, intersectBenchVariants) |
| 131 | + bs := make([][]uint16, intersectBenchVariants) |
| 132 | + for v := 0; v < intersectBenchVariants; v++ { |
| 133 | + as[v], bs[v] = benchIntersectPair(shape, n, int64(v)) |
| 134 | + } |
| 135 | + for _, impl := range []struct { |
| 136 | + name string |
| 137 | + fn func([]uint16, []uint16) int |
| 138 | + }{ |
| 139 | + {"dispatch", intersection2by2Cardinality}, |
| 140 | + {"scalar", localintersect2by2Cardinality}, |
| 141 | + } { |
| 142 | + b.Run(fmt.Sprintf("%s/%d/%s", shape, n, impl.name), func(b *testing.B) { |
| 143 | + sink := 0 |
| 144 | + for i := 0; i < b.N; i++ { |
| 145 | + v := i % intersectBenchVariants |
| 146 | + sink += impl.fn(as[v], bs[v]) |
| 147 | + } |
| 148 | + _ = sink |
| 149 | + }) |
| 150 | + } |
| 151 | + } |
| 152 | + } |
| 153 | +} |
0 commit comments