Элементы, неправильно исключенные из хэш-карты eBPF LRU

Я заметил, что элементы неправильно вытесняются в хеш-карте eBPF LRU (BPF_MAP_TYPE_LRU_HASH). В следующем коде я вставляю в хеш-карту LRU размера 8 и печатаю ее содержимое каждую секунду:

package main

import (
    "fmt"
    "github.com/cilium/ebpf"
    "log"
    "time"
)

func main() {
    spec := ebpf.MapSpec{
        Name:       "test_map",
        Type:       ebpf.LRUHash,
        KeySize:    4,
        ValueSize:  8,
        MaxEntries: 8,
    }

    hashMap, err := ebpf.NewMap(&spec)
    if err != nil {
        log.Fatalln("Could not create map:", err)
    }

    var insertKey uint32

    for range time.Tick(time.Second) {
        err = hashMap.Update(insertKey, uint64(insertKey), ebpf.UpdateAny)
        if err != nil {
            log.Printf("Update failed. insertKey=%d|value=%d|err=%s", insertKey, insertKey, err)
        }

        var key uint32
        var value uint64
        count := 0
        elementsStr := ""

        iter := hashMap.Iterate()

        for iter.Next(&key, &value) {
            elementsStr += fmt.Sprintf("(%d, %d) ", key, value)
            count++
        }

        log.Printf("Total elements: %d, elements: %s", count, elementsStr)

        insertKey++
    }
}

Когда я запускаю вышеуказанную программу, я вижу это:

2023/03/29 17:32:29 Total elements: 1, elements: (0, 0) 
2023/03/29 17:32:30 Total elements: 2, elements: (1, 1) (0, 0) 
2023/03/29 17:32:31 Total elements: 3, elements: (1, 1) (0, 0) (2, 2) 
2023/03/29 17:32:32 Total elements: 3, elements: (3, 3) (0, 0) (2, 2) 
...

Поскольку на карте восемь записей, я ожидал, что четвертая строка покажет четыре значения, но она показывает только три, потому что запись (1, 1) была исключена.

Если я изменяю max_entries на 1024, я замечаю, что эта проблема возникает после вставки 200-го элемента, но иногда это происходит и после этого. Это непоследовательно.

Эта проблема не ограничивается созданием/вставкой карты из пользовательского пространства, потому что я наблюдаю эту проблему в своей программе XDP, которая создает карту и вставляет в нее; приведенное выше воспроизводит проблему, которую я наблюдаю в своей реальной программе. В моей реальной программе, в которой также было 1024 элемента, я заметил, что эта проблема возникла после вставки 16-го элемента.

Я проверил это на наших производственных серверах с ядром Linux 5.16.7.

Я провожу тестирование на виртуальной машине Linux, обновляю ядро ​​до версии 6.2.8 и вижу, что политика вытеснения отличается. Например, когда max_entries равно 8, я наблюдаю следующее:

2023/03/29 20:38:02 Total elements: 1, elements: (0, 0)
2023/03/29 20:38:03 Total elements: 2, elements: (0, 0) (1, 1)
2023/03/29 20:38:04 Total elements: 3, elements: (0, 0) (2, 2) (1, 1)
2023/03/29 20:38:05 Total elements: 4, elements: (0, 0) (2, 2) (1, 1) (3, 3)
2023/03/29 20:38:06 Total elements: 5, elements: (4, 4) (0, 0) (2, 2) (1, 1) (3, 3)
2023/03/29 20:38:07 Total elements: 6, elements: (4, 4) (0, 0) (2, 2) (1, 1) (5, 5) (3, 3)
2023/03/29 20:38:08 Total elements: 7, elements: (4, 4) (0, 0) (2, 2) (1, 1) (6, 6) (5, 5) (3, 3)
2023/03/29 20:38:09 Total elements: 8, elements: (7, 7) (4, 4) (0, 0) (2, 2) (1, 1) (6, 6) (5, 5) (3, 3)
2023/03/29 20:38:10 Total elements: 1, elements: (8, 8)
...

Когда max_entries равно 1024, я замечаю, что после добавления 1025-го элемента общее количество элементов равно 897. Я не могу протестировать ядро ​​​​6.2.8 на наших рабочих серверах.

Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
2
0
118
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Хеш-карта LRU не гарантирует, что существует точно максимальное количество элементов, и реализация явно направлена ​​на обеспечение хорошей производительности с гораздо более чем 8 элементами. Что я вижу из довольно беглого взгляда на код:

  1. LRU разделен на две части: «активный список» и «неактивный список», с задачей, которая периодически перемещает элементы из одной в другую в зависимости от того, был ли к ним доступ в последнее время. Это не настоящий LRU (элементы не перемещаются в голову каждый раз, когда к ним обращаются).

  2. Когда карта заполнена и что-то нужно удалить, чтобы вставить новый элемент, код удалит до 128 элементов из неактивного списка за один проход; только если неактивный список пуст, он удаляет один элемент из активного списка.

  3. Существует также «локальный свободный список» выделенных элементов для каждого ЦП, ожидающих заполнения данными; когда он пуст, он пытается извлечь из глобального списка свободных, и если он пуст, он переходит к пути выселения. Целевой размер локального бесплатного списка — 4 элемента.

Таким образом, поведение в 6.2.8 кажется простым и последовательным: предположительно, все ваши ключи находятся в «неактивном списке» (что не слишком удивительно для шаблона доступа сканирующего типа, или, возможно, просто ни у одного из них не было шанса получить повышение). пока), и все они выбрасываются. Я не так понимаю версию 5.16, но, вероятно, это связано с локальным свободным списком и всеми обновлениями, работающими на одном и том же процессоре.

По сути, я думаю, что тип данных не предназначен для использования так, как вы его используете, и ошибка заключается в ваших ожиданиях. Если вы не согласны, думаю, вам придется обсудить это с разработчиками ядра.

В моей реальной программе я наблюдаю эту проблему, когда max_entries равно 1024. Каково минимальное число для max_entries, чтобы хэш-карта LRU была полезной? В приведенной выше программе я все еще вижу эту проблему, если max_entries равно 4096, хотя и в меньшей степени.

user2233706 30.03.2023 04:01

@user2233706 user2233706 Думаю, 256. Можете ли вы обновить свой вопрос, указав фактическое поведение, которое вы видите с 1024 max_entries? И ваше количество процессоров? Я не уверен, имеет ли это значение, но может.

hobbs 30.03.2023 04:07

(Если он по-прежнему удаляет все после 4 или 8 вставок, я бы назвал это ошибкой. Но если он заполняется, скажем, до 800, я бы сказал, что мой ответ по-прежнему актуален: это неточный LRU, и он работает в пакетном режиме для улучшения пропускная способность.)

hobbs 30.03.2023 04:12

В моей реальной программе максимальный размер составляет 1024, и я наблюдаю, что эта проблема возникает после добавления 16-й записи. Это на 5.16.7. Возможно, 6.2.8 ведет себя лучше, но я не могу протестировать это ядро ​​на наших реальных серверах.

user2233706 30.03.2023 15:46

@user2233706 user2233706 Основываясь на вашем редактировании, я бы сказал, что поведение на 6.2 соответствует ожиданиям (на самом деле у вас осталось ровно 1025 - 128 = 897 элементов). Если этого достаточно для вас, то у вас есть причина для «это приложение требует обновления ядра». Если поведение версии 6.2 для вас неприемлемо, то вам либо нужен редизайн, чтобы не зависеть от карт хеш-функции BPF LRU, либо вам нужно убедить вышестоящий linux принять более медленный «точный LRU» патч... и тогда вам нужно обновление ядра :)

hobbs 03.04.2023 18:11

Глядя на журналы, я могу предположить, что изменение поведения между 5.16 и 6.2 не было преднамеренным «исправлением», а скорее побочным эффектом github.com/torvalds/linux/commit/…, который перешел в 6.1.

hobbs 03.04.2023 18:46

Другие вопросы по теме