1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mirrors-cqengine

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Benchmark.md 34 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 28.11.2024 02:48 973f548

CQEngine Benchmark

Сравнение скорости поиска CQEngine с двумя типами итераций: наивной и оптимизированной, которые обсуждались ранее здесь.

Для тех, кто не знаком с микротестами, имейте в виду, что результаты ниже полезны только для сравнения относительной задержки подходов (и даже тогда с оговорками), а абсолютная задержка, вероятно, будет выше в производственных средах: Мифы, ложь и микротесты


Методика тестирования

Тестирование основано на примере онлайн-автодилера. У дилера есть веб-сайт с каталогом из 100 000 автомобилей. Таким образом, у него есть коллекция из 100 000 объектов Car. Сайту необходимо извлекать автомобили, соответствующие критериям, заданным в запросах.

В рамках теста проводится несколько отдельных тестов, которые имитируют поиск автомобилей на основе различных запросов и с использованием различных индексов CQEngine. Эти тесты измеряют время поиска (или задержку) для одного и того же запроса с использованием наивной итерации, оптимизированной итерации и CQEngine.

Тесты также измеряют задержку «Статистики CQEngine», которая относится к скорости, с которой CQEngine может подсчитать количество объектов, соответствующих запросу, используя ускоренный метод ResultSet.size(). Этот метод может быстро определить количество объектов по статистике, содержащейся в индексах в некоторых, но не во всех тестовых случаях.

Класс Car (источник) (http://github.com/npgall/cqengine/blob/master/code/src/test/java/com/googlecode/cqengine/testutil/Car.java) — это простой неизменяемый объект с атрибутами CQEngine, определёнными для его полей: carId, manufacturer, model, color, doors, price.

Класс CarFactory (источник) (http://github.com/npgall/cqengine/blob/master/code/src/test/java/com/googlecode/cqengine/testutil/CarFactory.java) содержит метод, который генерирует коллекцию автомобилей любого заданного размера. Метод создаёт коллекцию из набора из 10 шаблонов. Первый автомобиль — Ford Focus, второй — Ford Fusion и так далее. Метод присваивает уникальный монотонно возрастающий carId каждому автомобилю, который он создаёт. Когда запрашивается более 10 автомобилей, метод начинает снова проходить через шаблоны. Поэтому автомобиль 0 и автомобиль 10 — оба Ford Focus, автомобиль 1 и автомобиль 11 — оба Ford Fusion и т. д.

Таким образом, некоторые статистические данные о составе коллекции автомобилей:

Цвет % автомобилей Производитель % автомобилей Количество дверей % автомобилей
Красный 30% Ford 30% 5 50%
Зелёный 30% Honda 30% 4 20%
Синий 20% Toyota 30% 3 20%
Чёрный 10% BMW 10% 2 10%
Белый 10%

Учитывая, что тест создаёт коллекцию из 100 000 автомобилей, например: 30 000 из этих автомобилей будут произведены Ford, и 10 000 автомобилей будут конкретно моделью Ford Focus, равномерно распределённой по всей коллекции.

Тест проводился на машине Apple с процессором Intel Core i7 1,8 ГГц, работающей под управлением OS X 10.7.4 и Java 1.6.0_26. Все тесты проводились по 10 000 раз в качестве прогрева, прежде чем снова запустить их по 10 000 раз для измерения. Перед каждым измерением выполнялась сборка мусора. Исходный код логики тестовой среды можно найти здесь и здесь. Исходный код индивидуальных тестов упоминается в каждом тесте ниже.

Исходный вывод теста с разделением табуляцией доступен как... Таблица результатов

здесь.

Поскольку CQEngine использует ленивую оценку, он почти сразу возвращает ResultSet для каждого запроса. Для измерения производительности CQEngine эти тесты получают ResultSet от CQEngine, а затем перебирают все автомобили в наборе результатов, просто подсчитывая количество результатов. Как обсуждалось в некоторых примерах ниже, маловероятно, что реальное приложение будет делать это, и поэтому это несколько жёстко для CQEngine, преуменьшая его преимущества. Однако это кажется единственным способом сравнить производительность с итерацией на равных условиях в этом сценарии микротеста.

CQEngine является базовым в этих результатах, и обратите внимание на использование «быстрее» вместо «так же быстро, как». Если результат говорит, что CQEngine на 1600% быстрее, чем итерация, это означает, что CQEngine может обработать на 1700%, или в 17 раз больше запросов за единицу времени, чем итерация (это 1700%, то есть в 17 раз быстрее).

Тест однопоточный для измерения задержки, а не пропускной способности, поэтому используется только одно из ядер процессора 1,8 ГГц. Многопоточный доступ, который поддерживает CQEngine, даст значительно более высокую пропускную способность, и, таким образом, показатели пропускной способности значительно занижены в (до) количества ядер.

Тесты и результаты

UniqueIndex: получение по уникальному ключу

Получение одного объекта Car из коллекции на основе Car.carId со значением 500, которое однозначно идентифицирует автомобиль. UniqueIndex на Car.CAR_ID.

Этот пример демонстрирует поддержку CQEngine для постоянного времени извлечения независимо от размера коллекции.

Результаты теста

  • 2 967 359 запросов в секунду (на одном ядре процессора 1,8 ГГц).
  • 0,337 микросекунды на запрос.
  • CQEngine в 778 473,29% быстрее, чем наивная итерация.
  • CQEngine в 751 179,23% быстрее, чем оптимизированная итерация.

HashIndex: получение производителя «Ford» (большой набор результатов)

Извлечение и повторение объектов Car из коллекции на основе Car.manufacturer со значением «Ford». HashIndex на Car.MANUFACTURER.

Этот запрос соответствует 30 000 автомобилей; 30% коллекции из 100 000 автомобилей. Этот тест заставляет CQEngine перебирать все 30 000 автомобилей, хотя большинству приложений не потребуется так много результатов. Напротив, подходы к итерации могут потребовать повторения всей коллекции, поскольку совпадающие результаты могут быть расположены ближе к концу коллекции. CQEngine ResultSets намеренно поддерживают постраничную навигацию по результатам, и благодаря ленивой оценке, если приложение прекращает итерацию, никакие ненужные вычисления не будут выполнены. Тем не менее этот пример призван продемонстрировать производительность CQEngine при запросе и обработке большой части коллекции.

Результаты теста

  • 1256 запросов в секунду (на одном ядре процессора 1,8 ГГц).

  • 795,942 микросекунды на запрос.

  • CQEngine в 423,17% быстрее, чем наивная итерация.

  • CQEngine в 241,83% быстрее, чем оптимизированная итерация. Результаты бенчмарка

  • 4 341 запросов в секунду (на одном ядре процессора с тактовой частотой 1,8 ГГц);

  • 230,361 микросекунды на запрос;

  • CQEngine быстрее наивной итерации на 1 627,06 %;

  • CQEngine быстрее оптимизированной итерации на 1 324,17 %.


NavigableIndex: извлечение цены от и до

Извлечение и итерация объектов Car из коллекции на основе того, что Car.price больше или равен 3 000,00 и меньше 4 000,00. NavigableIndex по Car.PRICE.

Этот запрос соответствует 20 000 автомобилей; 20 % от коллекции из 100 000 машин.

  • Запрос CQEngine: between(Car.PRICE, 3000.0, true, 4000.0, false).
    • Примечание: true = включительно, false = исключительно.
  • SQL-эквивалент: SELECT * FROM cars WHERE price >= 3000.0 AND price < 4000.0.
  • Исходный код: здесь.

Результаты бенчмарка:

  • 1 478 запросов в секунду (на одном ядре процессора с тактовой частотой 1,8 ГГц);
  • 676,763 микросекунды на запрос;
  • CQEngine быстрее наивной итерации на 506,08 %;
  • CQEngine быстрее оптимизированной итерации на 325,89 %.

RadixTreeIndex: извлечение моделей, начинающихся с

Извлечение и итерация объектов Car из коллекции на основе того, что Car.model начинается с «P». RadixTreeIndex по Car.model.

Этот запрос соответствует 10 000 автомобилей; 10 % от коллекции из 100 000 машин. Обратите внимание: также см. ReversedRadixTreeIndex, который поддерживает запросы типа «заканчивается на». Он будет иметь идентичные характеристики производительности, как этот RadixTreeIndex, поэтому его не тестировали отдельно.

  • Запрос CQEngine: startsWith(Car.MODEL, "P").
  • SQL-эквивалент: SELECT * FROM cars WHERE model LIKE 'P%'.
  • Исходный код: здесь.

Результаты бенчмарка:

  • 2 788 запросов в секунду (на одном ядре процессора с тактовой частотой 1,8 ГГц);
  • 358,622 микросекунды на запрос;
  • CQEngine быстрее наивной итерации на 1357,81 %;
  • CQEngine быстрее оптимизированной итерации на 1110,12 %.

SuffixTreeIndex: извлечение моделей, содержащих

Извлечение и итерация объектов Car из коллекции на основе того, что Car.model содержит «g». SuffixTreeIndex по Car.model.

Этот запрос соответствует 10 000 автомобилей; 10 % от коллекции из 100 000 машин.

  • Запрос CQEngine: contains(Car.MODEL, "g").
  • SQL-эквивалент: SELECT * FROM cars WHERE model LIKE '%g%'.
  • Исходный код: здесь.

Результаты бенчмарка:

  • 3 053 запроса в секунду (на одном ядре процессора с тактовой частотой 1,8 ГГц);
  • 327,574 микросекунды на запрос;
  • CQEngine быстрее наивной итерации на 1860,53 %;
  • CQEngine быстрее оптимизированной итерации на 1605,31 %.

Без индексов: извлечение модели «Focus»

Извлечение и итерация объектов Car из коллекции на основе того, что Car.model имеет значение «Focus». Без индексов.

В этом примере демонстрируется неправильно настроенное приложение, которое не добавило никаких индексов. CQEngine может обрабатывать любой запрос, но без подходящих индексов это не позволяет ускорить запрос, и он может работать хуже итерации. Приложения должны убедиться, что они добавили подходящие индексы. Запрос соответствует 10 000 автомобилей; 10% от коллекции из 100 000 машин.

Результаты тестов:

  • 153 запроса в секунду (на одном ядре процессора с тактовой частотой 1,8 ГГц).
  • 6524,906 микросекунд на запрос.
  • Наивная итерация на 43,21% быстрее, чем CQEngine.
  • Оптимизированная итерация на 53,27% быстрее, чем CQEngine.

Неоптимальные индексы: поиск синих автомобилей марки Toyota с тремя дверьми

Извлекаем и перебираем объекты Car из коллекции на основе следующих условий:

  • значение поля Car.manufacturer равно «Toyota»;
  • значение поля Car.color равно Car.Color.BLUE;
  • значение поля Car.doors равно 3.

Индекс HashIndex для Car.DOORS. Индексов для Manufacturer или Color нет.

Этот пример демонстрирует обработку запросов CQEngine, для которых оптимальные индексы не существуют. Идеальная конфигурация для этого типа запросов должна иметь CompoundIndex для трёх полей выше, чтобы обеспечить постоянное время извлечения. Однако в этом случае был добавлен только HashIndex для Car.DOORS. CQEngine максимально использует доступные индексы. В этом случае он использует индекс Car.DOORS, чтобы уменьшить набор кандидатов с 100 000 до 20 000 автомобилей, а затем перебирает набор кандидатов, применяя фильтрацию «на лету», чтобы вернуть только те автомобили, которые соответствуют остальной части запроса. Этот запрос в конечном итоге соответствует 10 000 автомобилям; 10 % от коллекции из 100 000 автомобилей.

Обратите внимание, что получение статистики CQEngine в этом примере происходит медленнее, чем в других. Статистика CQEngine относится к вызову resultSet.size() для ResultSet, возвращаемого CQEngine, вместо определения размера путём фактического перебора и подсчёта результатов.

CQEngine часто может рассчитать, сколько объектов будет соответствовать запросу, фактически не перебирая никакие объекты, на основе внутренних подсчётов, доступных в записях внутри индексов. В этом примере, поскольку индексы недоступны для предоставления полной статистики, CQEngine возвращается к подсчёту объектов, применяя фильтрацию «на лету» к набору кандидатов, что влечёт за собой более высокие затраты.

Метод resultSet.contains() также можно ускорить аналогичным образом (применяя тесты на принадлежность множеству к отдельным множествам внутри индексов). В этом примере этот метод будет затронут аналогичным образом, внутренне возвращаясь к фильтрации «на лету» набора кандидатов.

Запрос CQEngine:

and(
    equal(Car.MANUFACTURER, "Toyota"),
    equal(Car.COLOR, Car.Color.BLUE),
    equal(Car.DOORS, 3)
)

SQL-эквивалент:

SELECT * FROM cars
WHERE
    manufacturer = 'Toyota'
    AND color = 'blue'
    AND doors = 3

Исходный код: здесь (http://github.com/npgall/cqengine/blob/master/code/src/test/java/com/googlecode/cqengine/benchmark/tasks/NonOptimalIndexes_ManufacturerToyotaColorBlueDoorsThree.java).

Результаты тестов:

  • 848 запросов в секунду (на одном ядре процессора с тактовой частотой 1,8 ГГц).
  • 1179,856 микросекунд на запрос.
  • CQEngine на 172,60 % быстрее, чем наивная итерация.
  • CQEngine на 136,61 % быстрее, чем оптимизированная итерация.

Оптимальные индексы — CompoundIndex: поиск синих автомобилей марки Toyota с тремя дверьми

Перебираем объекты Car из коллекции на основе следующих условий:

  • значение поля Car.manufacturer равно «Toyota»;
  • значение поля Car.color равно Car.Color.BLUE;
  • значение поля Car.doors равно 3. CompoundIndex для Car.MANUFACTURER, Car.COLOR, Car.DOORS.

Этот пример показывает улучшение производительности при добавлении оптимального индекса для того же запроса, что и в примере с неоптимальными индексами выше. Этот запрос соответствует 10 000 автомобилей; 10 % от коллекции из 100 000 автомобилей. Результаты бенчмарка

  • 4 735 запросов в секунду (на одном ядре процессора с частотой 1,8 ГГц).
  • 211,196 микросекунд на запрос.
  • CQEngine на 1 625,70 % быстрее, чем наивная итерация.
  • CQEngine на 1 283,20 % быстрее, чем оптимизированная итерация.

StandingQueryIndex: извлечение автомобилей марки Toyota синего цвета с количеством дверей не равным пяти

Извлечение и итерация объектов Car из коллекции на основе значения Car.manufacturer, равного «Toyota», значения Car.color, равного Car.Color.BLUE, и значения Car.doors, не равного 5.

Пример демонстрирует StandingQueryIndex. Этот тип индекса поддерживает набор объектов, соответствующих запросу или фрагменту запроса. По мере добавления и удаления объектов из коллекции он проверяет объекты, чтобы определить, соответствуют ли они запросу, добавляя их в этот набор или удаляя их из него по мере необходимости. Когда CQEngine видит запрос или фрагмент запроса, для которого существует индекс StandingQueryIndex, он может извлекать соответствующие объекты для этого запроса или фрагмента запроса со сложностью O(1). Этот запрос соответствует 10 000 автомобилей; 10 % от коллекции из 100 000 автомобилей.

  • Запрос CQEngine:
and(
    equal(Car.MANUFACTURER, "Toyota"),
    equal(Car.COLOR, Car.Color.BLUE),
    not(equal(Car.DOORS, 5))
)
  • Эквивалент SQL:
SELECT * FROM cars
WHERE
    manufacturer = 'Toyota'
    AND color = 'blue'
    AND doors <> 3

Результаты бенчмарка

  • 4 796 запросов в секунду (на одном ядре процессора с частотой 1,8 ГГц).
  • 208,515 микросекунд на запрос.
  • CQEngine на 1 607,43 % быстрее, чем наивная итерация.
  • CQEngine на 1 299,96 % быстрее, чем оптимизированная итерация.

Quantized HashIndex: получение по уникальному ключу

Получение одного объекта Car из коллекции на основе уникального идентификатора автомобиля Car.carId, равного 500. Квантованный HashIndex на Car.CAR_ID с коэффициентом сжатия 5.

Этот пример демонстрирует эффект квантования индексов. Квантование позволяет контролировать детализацию индексов. В этом примере индекс был создан с помощью IntegerQuantizer с коэффициентом сжатия 5. Это означает, что каждые 5 последовательных carId будут сгруппированы вместе и сохранены как одна запись в индексе. Это уменьшает размер индекса (снижает нагрузку на память) и вместо этого обменивает его на немного более высокую загрузку ЦП во время извлечения. Индекс должен будет фильтровать результаты, полученные из записи на лету, чтобы убедиться, что они действительно соответствуют запросу.

  • Запрос CQEngine: equal(Car.CAR_ID, 501).
  • Эквивалент SQL: SELECT * FROM cars WHERE carId = 501.
  • Исходный код: здесь.

Результаты бенчмарка

  • 1 639 344 запроса в секунду (на одном ядре процессора с частотой 1,8 ГГц).
  • 0,61 микросекунды на запрос.
  • CQEngine в 481 984,43 раза быстрее, чем наивная итерация.
  • CQEngine в 467 645,90 раза быстрее, чем оптимизированная итерация.

Квантованный NavigableIndex: получение CarId в диапазоне

Получение трёх объектов Car из коллекции на основе идентификаторов автомобилей Car.carId в диапазоне от 500 до 502. Квантованный NavigableIndex на Car.CAR_ID с коэффициентом сжатия 5.

Этот пример показывает, что квантование можно применять к навигационным индексам с минимальными накладными расходами при запросах типа range. CQEngine применяет фильтрацию на лету. Фильтрация только квантованных записей в начале и/или конце диапазонов

  • Запрос CQEngine: between(Car.CAR_ID, 500, 502)
  • SQL-эквивалент: SELECT * FROM cars WHERE carId BETWEEN 500 AND 502
  • Исходный код: здесь

Результаты бенчмарка

  • 1 116 071 запросов в секунду (на одном ядре процессора с частотой 1,8 ГГц)
  • 0,896 микросекунды на запрос
  • CQEngine быстрее наивной итерации на 330 187,5%
  • CQEngine быстрее оптимизированной итерации на 325 727,79%

Скорость вставки объектов / накладные расходы на индексацию

В предыдущих тестах измерялась производительность CQEngine при извлечении объектов. В этом разделе оценивается скорость и накладные расходы при построении индексов для объектов.

Исходный код для этих тестов можно найти здесь. Оригинальный вывод из теста доступен в виде таблицы здесь.

Тест проводился по той же методике, что обсуждалась в разделе методологии, на том же компьютере Apple с процессором 1,8 ГГц.

Тест выполнялся со сборкой из 100 000 объектов автомобилей, измеряя время, затраченное на построение следующих типов индексов для коллекции.

Обычно накладные расходы на индексирование всей коллекции возникают только тогда, когда объекты добавляются в IndexedCollection массово. Впоследствии объекты могут быть добавлены в IndexedCollection и удалены из неё напрямую, так что возникают только накладные расходы для каждого объекта.

Следует отметить, что если все индексы добавляются в IndexedCollection до добавления объектов массово, то все индексы будут построены за один проход; таким образом, если несколько индексов добавляются заранее, накладные расходы будут меньше суммы указанных.

Результаты

Индекс Микросекунды для индексации коллекции из 100 000 объектов Микросекунды на объект Количество вставок в секунду (одно ядро 1,8 ГГц)
UniqueIndex на CarId 24 032,8 0,24 4 166 667
HashIndex на CarId 874 702,26 8,747 114 324
Квантованный HashIndex на CarId (коэффициент сжатия 10) 85 204,84 0,852 1 173 709
HashIndex на Manufacturer 28 487,08 0,284 3 521 127
HashIndex на Model 27 310,68 0,273 3 663 004
CompoundIndex на Manufacturer, Color, Doors 109 761,58 1,097 911 577
NavigableIndex на Price 30 393,94 0,303 3 300 330
RadixTreeIndex на Model 58 100,02 0,581 1 721 170
SuffixTreeIndex на Model 27 386,04 0,273 3 663 004

Выводы

  • Требуется 24 миллисекунды, чтобы массово проиндексировать 100 000 объектов (4 166 667 вставок в секунду на одном ядре процессора 1,8 ГГц), когда связанный атрибут имеет уникальное значение для каждого объекта и используется уникальный индекс.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/mirrors-cqengine.git
git@api.gitlife.ru:oschina-mirror/mirrors-cqengine.git
oschina-mirror
mirrors-cqengine
mirrors-cqengine
master