tweak ImportValue benchmark

With timestamps, we probably want to at least check larger BSI fields,
so we add that. Also, tweak the interpretation of b.N (making each
N count for 10,000 bits) so we can see allocation load at all. But we
also reduce the sparse set to be about one bit per 19 bits, because
if we do one per 70,000, and are doing field-at-a-time imports, we're
getting hundreds of imports to try to match a target of, say, around
a million values.

We also sort the inputs, because ImportValue is about to start requiring
that, since the API does it anyway.

Also, extend this to be available on Fields, because field.ImportValue
is ALSO doing things which could be inefficient or expensive.
This commit is contained in:
Seebs 2021-05-18 14:03:53 -05:00
parent 7fe37a83f4
commit 671a0cf5c6
2 changed files with 73 additions and 17 deletions

View file

@ -203,7 +203,7 @@ type TestField struct {
}
// NewTestField returns a new instance of TestField d/0.
func NewTestField(t *testing.T, opts FieldOption) *TestField {
func NewTestField(t testing.TB, opts FieldOption) *TestField {
path, err := testhook.TempDirInDir(t, *TempDir, "pilosa-field-")
if err != nil {
t.Fatal(err)
@ -230,7 +230,7 @@ func NewTestField(t *testing.T, opts FieldOption) *TestField {
}
// OpenField returns a new, opened field at a temporary path.
func OpenField(t *testing.T, opts FieldOption) *TestField {
func OpenField(t testing.TB, opts FieldOption) *TestField {
f := NewTestField(t, opts)
return f
}
@ -509,6 +509,38 @@ func TestBSIGroup_importValue(t *testing.T) {
} // loop
}
// benchmarkImportValues is a helper function to explore, very roughly, the cost
// of setting values using the special setter used for imports.
func benchmarkFieldImportValues(b *testing.B, qcx *Qcx, bitDepth uint64, f *TestField, cfunc func(uint64) uint64) {
batches := makeBenchmarkImportValueData(b, bitDepth, cfunc)
for _, req := range batches {
err := f.importValue(qcx, req.ColumnIDs, req.Values, &ImportOptions{})
if err != nil {
b.Fatalf("error importing values: %s", err)
}
}
}
// Benchmark performance of setValue for BSI ranges.
func BenchmarkField_ImportValue(b *testing.B) {
depths := []uint64{4, 8, 16, 32}
for _, bitDepth := range depths {
f := OpenField(b, OptFieldTypeInt(0, 1<<bitDepth))
defer f.Close()
qcx := f.idx.holder.txf.NewQcx()
defer qcx.Abort()
name := fmt.Sprintf("Depth%d", bitDepth)
b.Run(name+"_Sparse", func(b *testing.B) {
benchmarkFieldImportValues(b, qcx, bitDepth, f, func(u uint64) uint64 { return (u + 19) & (ShardWidth - 1) })
})
b.Run(name+"_Dense", func(b *testing.B) {
benchmarkFieldImportValues(b, qcx, bitDepth, f, func(u uint64) uint64 { return (u + 1) & (ShardWidth - 1) })
})
}
}
func TestIntField_MinMaxForShard(t *testing.T) {
f := OpenField(t, OptFieldTypeInt(-100, 200))
defer f.Close()

View file

@ -1020,34 +1020,58 @@ func BenchmarkFragment_SetValue(b *testing.B) {
}
}
func makeBenchmarkImportValueData(b *testing.B, bitDepth uint64, cfunc func(uint64) uint64) []ImportValueRequest {
b.StopTimer()
column := uint64(0)
// we don't average an alloc-per-bit, so we use a much larger N to get
// meaningful data from -benchmem
n := b.N * 10000
batches := make([]ImportValueRequest, 0, (n/ShardWidth)+1)
mask := int64(1<<bitDepth) - 1
prev := uint64(0)
var values []int64
var columns []uint64
for i := 0; i < n; i++ {
values = append(values, int64(i)&mask)
columns = append(columns, column)
column = cfunc(column)
if column < prev {
req := ImportValueRequest{ColumnIDs: columns, Values: values}
columns = []uint64{}
values = []int64{}
batches = append(batches, req)
}
prev = column
}
if len(columns) > 0 {
req := ImportValueRequest{ColumnIDs: columns, Values: values}
batches = append(batches, req)
}
b.StartTimer()
return batches
}
// benchmarkImportValues is a helper function to explore, very roughly, the cost
// of setting values using the special setter used for imports.
func benchmarkImportValues(b *testing.B, tx Tx, bitDepth uint64, f *fragment, cfunc func(uint64) uint64) {
column := uint64(0)
b.StopTimer()
columns := make([]uint64, b.N)
values := make([]int64, b.N)
for i := 0; i < b.N; i++ {
values[i] = int64(i)
columns[i] = column
column = cfunc(column)
}
b.StartTimer()
err := f.importValue(tx, columns, values, bitDepth, false)
if err != nil {
b.Fatalf("error importing values: %s", err)
batches := makeBenchmarkImportValueData(b, bitDepth, cfunc)
for _, req := range batches {
err := f.importValue(tx, req.ColumnIDs, req.Values, bitDepth, false)
if err != nil {
b.Fatalf("error importing values: %s", err)
}
}
}
// Benchmark performance of setValue for BSI ranges.
func BenchmarkFragment_ImportValue(b *testing.B) {
depths := []uint64{4, 8, 16}
depths := []uint64{4, 8, 16, 32}
for _, bitDepth := range depths {
name := fmt.Sprintf("Depth%d", bitDepth)
f, idx, tx := mustOpenBSIFragment(b, "i", "f", viewBSIGroupPrefix+"foo", 0)
_ = idx
b.Run(name+"_Sparse", func(b *testing.B) {
benchmarkImportValues(b, tx, bitDepth, f, func(u uint64) uint64 { return (u + 70000) & (ShardWidth - 1) })
benchmarkImportValues(b, tx, bitDepth, f, func(u uint64) uint64 { return (u + 19) & (ShardWidth - 1) })
})
f.Clean(b)
f, idx, tx = mustOpenBSIFragment(b, "i", "f", viewBSIGroupPrefix+"foo", 0)