Предисловие

Почти всю свою карьеру я работал в сфере оптимизации программного и аппаратного обеспечения и уже давно подумывал о написании книги о производительности программного обеспечения, но так и не смог начать. Для меня это просто казалось непосильной задачей. Поэтому, как и любую задачу по разработке программного обеспечения, я разбил ее на более мелкие, более управляемые части. Если я смогу сосредоточиться на написании нескольких страниц за раз, я в конечном итоге смогу объединить их в полноценную книгу. Это по-прежнему большой проект, но, по крайней мере, сейчас он не кажется таким уж ошеломляющим.

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

Глава Один

В этой главе я хотел бы обсудить довольно простую операцию — деление и, самое главное, ее производительность. Вы поймете, почему люди пытаются привести все к степени 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.