Предисловие
Почти всю свою карьеру я работал в сфере оптимизации программного и аппаратного обеспечения и уже давно подумывал о написании книги о производительности программного обеспечения, но так и не смог начать. Для меня это просто казалось непосильной задачей. Поэтому, как и любую задачу по разработке программного обеспечения, я разбил ее на более мелкие, более управляемые части. Если я смогу сосредоточиться на написании нескольких страниц за раз, я в конечном итоге смогу объединить их в полноценную книгу. Это по-прежнему большой проект, но, по крайней мере, сейчас он не кажется таким уж ошеломляющим.
Этот набор глав будет посвящен производительности аппаратного и программного обеспечения, а зачастую и тому и другому. В каждой главе я представляю небольшой пример проблемы с реальным решением. Или обсудите некоторые приемы оптимизации и то, как я нахожу и исследую проблемы.
Глава Один
В этой главе я хотел бы обсудить довольно простую операцию — деление и, самое главное, ее производительность. Вы поймете, почему люди пытаются привести все к степени 2, когда div и mod становятся очень дешевыми, но с другой стороны, это может создать некоторые другие проблемы, которые мы не будем здесь обсуждать.
Мы собираемся протестировать std::div
с различными входными данными, посмотреть, что мы получим и что это может означать. Я буду использовать quick-bench.com как простой и полезный инструмент, но сначала давайте обсудим, какие вещи мы хотели бы протестировать, а затем придумаем реальный код:
- Задержка и пропускная способность для случайных значений.
- Будут ли иметь значение разные типы данных?
- Разделите на некоторые общие значения —
[1, 2, 3, 4, 10, 100, …]
. Да, мы также хотим увидеть стоимость деления на единицу, потому что это происходит в реальных сценариях. - Разрешить компилятору генерировать эффективный код для некоторых операций.
Пропускная способность задержки случайных значений
Давайте напишем тест, пока мы обсуждаем требования.
Некоторые основные требования к тестам, которые мы должны учитывать при первой попытке:
- Воспроизводимость и стабильность — одинаковые результаты при каждой работе с одними и теми же данными. В нашем случае мы хотим каждый раз запускать тест с одними и теми же случайными числами. Установка случайного начального числа в постоянное значение — один из способов добиться этого.
- Измеряем только то, что хотим. Мы хотим только измерить производительность операции деления. Поэтому нам нужно убедиться, что в скомпилированном двоичном файле нет лишнего кода, который мы не хотим измерять — сохраните случайные значения в массиве, чтобы убедиться, что мы также не измеряем генератор случайных чисел, и убедитесь, что набор данных соответствует L1$. (менее 32 КБ)
Судя по приведенным выше требованиям, наш тест должен выглядеть так:
- Инициализируйте случайное начальное число с некоторой константой.
- Сгенерируйте массив случайных 32-битных и 64-битных целых чисел, содержащий не более 8 КБ и 4 КБ элементов соответственно.
- Напишите функции сравнительного анализа, разделив два значения с наименьшими затратами между каждой операцией.
- Измерьте как пропускную способность, так и задержку.
Нам также нужно с чем сравнивать результаты, потому что все относительно, как и производительность. Мы будем использовать цикл задержки с одним циклом в качестве базовой линии для сравнения.
Полученные результаты
https://quick-bench.com/q/Hmcjr8KXMdt-_yM-IZL4zuOIyK4
Итак, теперь вы можете сравнить производительность деления с другими простыми операциями — оно до 48 раз медленнее, чем простые операции «и». Его пропускная способность как минимум в 8 раз ниже, что объясняет, почему люди предпочитают, чтобы размеры были степенью 2, и использовали битовые операции для выполнения операций div и mod.