Skip to content

Commit aab8492

Browse files
alxnlmb
authored andcommitted
map: Support batch APIs on per-CPU maps
As a follow up to #207, add support for PerCPU Hash and Array maps to the following methods: - BatchLookup() - BatchLookupAndDelete() - BatchUpdate() - BatchDelete() This provides a significant performance improvement by amortizing the overhead of the underlying syscall. In this change, the API contact for the batches is a flat slice of values []T: batch0cpu0,batch0cpu1,..batch0cpuN,batch1cpu0...batchNcpuN In order to avoid confusion and panics for users, the library is strict about the expected lengths of slices passed to these methods, rather than padding slices to zeros or writing partial results. An alternative design that was considered was [][]T: batch0{cpu0,cpu1,..cpuN},batch1{...},..batchN{...} []T was partly chosen as it matches the underlying semantics of the syscall, although without correctly aligned data it cannot be a zero copy pass through. Caveats: * Array maps of any type do not support batch delete. * Batched ops support for PerCPU Array Maps was only added in 5.13: https://lore.kernel.org/bpf/[email protected]/ Signed-off-by: Alun Evans <[email protected]> Co-developed-by: Lorenz Bauer <[email protected]>
1 parent 196faa0 commit aab8492

File tree

4 files changed

+494
-215
lines changed

4 files changed

+494
-215
lines changed

map.go

+106-45
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,53 @@ type BatchCursor struct {
10051005
}
10061006

10071007
func (m *Map) batchLookup(cmd sys.Cmd, cursor *BatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) {
1008+
if m.typ.hasPerCPUValue() {
1009+
return m.batchLookupPerCPU(cmd, cursor, keysOut, valuesOut, opts)
1010+
}
1011+
1012+
count, err := batchCount(keysOut, valuesOut)
1013+
if err != nil {
1014+
return 0, err
1015+
}
1016+
1017+
valueBuf := sysenc.SyscallOutput(valuesOut, count*int(m.fullValueSize))
1018+
1019+
n, err := m.batchLookupCmd(cmd, cursor, count, keysOut, valueBuf.Pointer(), opts)
1020+
if err != nil {
1021+
return n, err
1022+
}
1023+
1024+
err = valueBuf.Unmarshal(valuesOut)
1025+
if err != nil {
1026+
return 0, err
1027+
}
1028+
1029+
return n, nil
1030+
}
1031+
1032+
func (m *Map) batchLookupPerCPU(cmd sys.Cmd, cursor *BatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) {
1033+
count, err := sliceLen(keysOut)
1034+
if err != nil {
1035+
return 0, fmt.Errorf("keys: %w", err)
1036+
}
1037+
1038+
valueBuf := make([]byte, count*int(m.fullValueSize))
1039+
valuePtr := sys.NewSlicePointer(valueBuf)
1040+
1041+
n, sysErr := m.batchLookupCmd(cmd, cursor, count, keysOut, valuePtr, opts)
1042+
if sysErr != nil && !errors.Is(sysErr, unix.ENOENT) {
1043+
return 0, err
1044+
}
1045+
1046+
err = unmarshalBatchPerCPUValue(valuesOut, count, int(m.valueSize), valueBuf)
1047+
if err != nil {
1048+
return 0, err
1049+
}
1050+
1051+
return n, sysErr
1052+
}
1053+
1054+
func (m *Map) batchLookupCmd(cmd sys.Cmd, cursor *BatchCursor, count int, keysOut any, valuePtr sys.Pointer, opts *BatchOptions) (int, error) {
10081055
cursorLen := int(m.keySize)
10091056
if cursorLen < 4 {
10101057
// * generic_map_lookup_batch requires that batch_out is key_size bytes.
@@ -1033,29 +1080,13 @@ func (m *Map) batchLookup(cmd sys.Cmd, cursor *BatchCursor, keysOut, valuesOut i
10331080
if err := haveBatchAPI(); err != nil {
10341081
return 0, err
10351082
}
1036-
if m.typ.hasPerCPUValue() {
1037-
return 0, ErrNotSupported
1038-
}
1039-
keysValue := reflect.ValueOf(keysOut)
1040-
if keysValue.Kind() != reflect.Slice {
1041-
return 0, fmt.Errorf("keys must be a slice")
1042-
}
1043-
valuesValue := reflect.ValueOf(valuesOut)
1044-
if valuesValue.Kind() != reflect.Slice {
1045-
return 0, fmt.Errorf("valuesOut must be a slice")
1046-
}
1047-
count := keysValue.Len()
1048-
if count != valuesValue.Len() {
1049-
return 0, fmt.Errorf("keysOut and valuesOut must be the same length")
1050-
}
10511083

10521084
keyBuf := sysenc.SyscallOutput(keysOut, count*int(m.keySize))
1053-
valueBuf := sysenc.SyscallOutput(valuesOut, count*int(m.fullValueSize))
10541085

10551086
attr := sys.MapLookupBatchAttr{
10561087
MapFd: m.fd.Uint(),
10571088
Keys: keyBuf.Pointer(),
1058-
Values: valueBuf.Pointer(),
1089+
Values: valuePtr,
10591090
Count: uint32(count),
10601091
InBatch: sys.NewSlicePointer(inBatch),
10611092
OutBatch: sys.NewSlicePointer(cursor.opaque),
@@ -1075,9 +1106,6 @@ func (m *Map) batchLookup(cmd sys.Cmd, cursor *BatchCursor, keysOut, valuesOut i
10751106
if err := keyBuf.Unmarshal(keysOut); err != nil {
10761107
return 0, err
10771108
}
1078-
if err := valueBuf.Unmarshal(valuesOut); err != nil {
1079-
return 0, err
1080-
}
10811109

10821110
return int(attr.Count), sysErr
10831111
}
@@ -1088,29 +1116,24 @@ func (m *Map) batchLookup(cmd sys.Cmd, cursor *BatchCursor, keysOut, valuesOut i
10881116
// to a slice or buffer will not work.
10891117
func (m *Map) BatchUpdate(keys, values interface{}, opts *BatchOptions) (int, error) {
10901118
if m.typ.hasPerCPUValue() {
1091-
return 0, ErrNotSupported
1119+
return m.batchUpdatePerCPU(keys, values, opts)
10921120
}
1093-
keysValue := reflect.ValueOf(keys)
1094-
if keysValue.Kind() != reflect.Slice {
1095-
return 0, fmt.Errorf("keys must be a slice")
1096-
}
1097-
valuesValue := reflect.ValueOf(values)
1098-
if valuesValue.Kind() != reflect.Slice {
1099-
return 0, fmt.Errorf("values must be a slice")
1100-
}
1101-
var (
1102-
count = keysValue.Len()
1103-
valuePtr sys.Pointer
1104-
err error
1105-
)
1106-
if count != valuesValue.Len() {
1107-
return 0, fmt.Errorf("keys and values must be the same length")
1121+
1122+
count, err := batchCount(keys, values)
1123+
if err != nil {
1124+
return 0, err
11081125
}
1109-
keyPtr, err := marshalMapSyscallInput(keys, count*int(m.keySize))
1126+
1127+
valuePtr, err := marshalMapSyscallInput(values, count*int(m.valueSize))
11101128
if err != nil {
11111129
return 0, err
11121130
}
1113-
valuePtr, err = marshalMapSyscallInput(values, count*int(m.valueSize))
1131+
1132+
return m.batchUpdate(count, keys, valuePtr, opts)
1133+
}
1134+
1135+
func (m *Map) batchUpdate(count int, keys any, valuePtr sys.Pointer, opts *BatchOptions) (int, error) {
1136+
keyPtr, err := marshalMapSyscallInput(keys, count*int(m.keySize))
11141137
if err != nil {
11151138
return 0, err
11161139
}
@@ -1137,17 +1160,28 @@ func (m *Map) BatchUpdate(keys, values interface{}, opts *BatchOptions) (int, er
11371160
return int(attr.Count), nil
11381161
}
11391162

1163+
func (m *Map) batchUpdatePerCPU(keys, values any, opts *BatchOptions) (int, error) {
1164+
count, err := sliceLen(keys)
1165+
if err != nil {
1166+
return 0, fmt.Errorf("keys: %w", err)
1167+
}
1168+
1169+
valueBuf, err := marshalBatchPerCPUValue(values, count, int(m.valueSize))
1170+
if err != nil {
1171+
return 0, err
1172+
}
1173+
1174+
return m.batchUpdate(count, keys, sys.NewSlicePointer(valueBuf), opts)
1175+
}
1176+
11401177
// BatchDelete batch deletes entries in the map by keys.
11411178
// "keys" must be of type slice, a pointer to a slice or buffer will not work.
11421179
func (m *Map) BatchDelete(keys interface{}, opts *BatchOptions) (int, error) {
1143-
if m.typ.hasPerCPUValue() {
1144-
return 0, ErrNotSupported
1145-
}
1146-
keysValue := reflect.ValueOf(keys)
1147-
if keysValue.Kind() != reflect.Slice {
1148-
return 0, fmt.Errorf("keys must be a slice")
1180+
count, err := sliceLen(keys)
1181+
if err != nil {
1182+
return 0, fmt.Errorf("keys: %w", err)
11491183
}
1150-
count := keysValue.Len()
1184+
11511185
keyPtr, err := marshalMapSyscallInput(keys, count*int(m.keySize))
11521186
if err != nil {
11531187
return 0, fmt.Errorf("cannot marshal keys: %v", err)
@@ -1174,6 +1208,24 @@ func (m *Map) BatchDelete(keys interface{}, opts *BatchOptions) (int, error) {
11741208
return int(attr.Count), nil
11751209
}
11761210

1211+
func batchCount(keys, values any) (int, error) {
1212+
keysLen, err := sliceLen(keys)
1213+
if err != nil {
1214+
return 0, fmt.Errorf("keys: %w", err)
1215+
}
1216+
1217+
valuesLen, err := sliceLen(values)
1218+
if err != nil {
1219+
return 0, fmt.Errorf("values: %w", err)
1220+
}
1221+
1222+
if keysLen != valuesLen {
1223+
return 0, fmt.Errorf("keys and values must have the same length")
1224+
}
1225+
1226+
return keysLen, nil
1227+
}
1228+
11771229
// Iterate traverses a map.
11781230
//
11791231
// It's safe to create multiple iterators at the same time.
@@ -1552,3 +1604,12 @@ func NewMapFromID(id MapID) (*Map, error) {
15521604

15531605
return newMapFromFD(fd)
15541606
}
1607+
1608+
// sliceLen returns the length if the value is a slice or an error otherwise.
1609+
func sliceLen(slice any) (int, error) {
1610+
sliceValue := reflect.ValueOf(slice)
1611+
if sliceValue.Kind() != reflect.Slice {
1612+
return 0, fmt.Errorf("%T is not a slice", slice)
1613+
}
1614+
return sliceValue.Len(), nil
1615+
}

0 commit comments

Comments
 (0)