Почему у AMD-CPU такой глупый PAUSE-тайминг

Я разработал объект-монитор, подобный объекту Java для C++, с некоторыми улучшениями. Основное улучшение состоит в том, что есть не только спин-цикл для блокировки и разблокировки, но и для ожидания события. В этом случае вам не нужно блокировать мьютекс, но нужно указать предикат для функции wait_poll, и код неоднократно пытается заблокировать опрос мьютекса, и если он может заблокировать мьютекс, он вызывает предикат, который возвращает (или перемещает) пару логического типа и типа результата.

Ожидание семафора и / или объекта-события (Win32) в ядре может легко занять от 1.000 до 10.000 тактов, даже если вызов немедленно возвращается, потому что семафор или событие были установлены ранее. Таким образом, должен быть счетчик вращений с разумной связью с этим интервалом ожидания, например, вращение одной десятой минимального интервала, затрачиваемого в ядре.

С моим объектом-монитором я взял алгоритм пересчета spincount из glibc. И я тоже использую PAUSE-инструкцию. Но я обнаружил, что на моем процессоре (TR 3900X) команда паузы слишком быстрая. В среднем это около 0,78 нс. На процессорах Intel это намного более разумно - около 30 нс.

Это код:

      #include <iostream>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <immintrin.h>

using namespace std;
using namespace chrono;

int main( int argc, char **argv )
{
    static uint64_t const PAUSE_ROUNDS = 1'000'000'000;
    auto start = high_resolution_clock::now();
    for( uint64_t i = PAUSE_ROUNDS; i; --i )
        _mm_pause();
    double ns = (int64_t)duration_cast<nanoseconds>( high_resolution_clock::now() - start ).count() / (double)PAUSE_ROUNDS;
    cout << ns << endl;
}

Почему AMD взяла такой глупый тайминг ПАУЗЫ? PAUSE предназначена для циклов ожидания и вращения и должна точно соответствовать времени, которое требуется для того, чтобы содержимое строки кэша переключилось на другое ядро ​​и обратно.

1 ответ

Но я обнаружил, что на моем процессоре (TR 3900X) инструкция паузы выполняется слишком быстро. В среднем это около 0,78 нс. На процессорах Intel это намного разумнее, около 30 нс.

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

Это нужно для того, чтобы ЦП не тратил свои ресурсы (спекулятивно) на параллельное выполнение множества итераций цикла; что особенно полезно в ситуациях с гиперпоточностью, когда другой логический процессор в ядре может использовать эти ресурсы, но также полезно для сокращения времени, необходимого для выхода из цикла при изменении условия (поскольку у вас нет «N итераций» инструкций, поставленных в очередь до изменения условия).

Учитывая это; для чрезвычайно сложного ЦП, который может одновременно выполнять 200 инструкций, само по себе может произойти мгновенно, но вызвать на своем пути конвейерный пузырь «длительностью 200 циклов»; и для чрезвычайно простого процессора («в порядке» без спекулятивного выполнения) может/должен буквально ничего не делать (рассматривается как ).

PAUSE предназначена для циклов ожидания и ожидания и должна точно соответствовать времени, которое требуется содержимому кэш-линии для перехода к другому ядру и обратно.

Нет. Предположим, что строка кеша находится в «модифицированном» состоянии в кеше другого процессора, а инструкция после выглядит примерно так: ", что заставляет ЦП пытаться перевести строку кэша в "общее" состояние. Как долго ЦП должен тратить время на бездействие без причины после, но перед попыткой перевести строку кэша в "общее" состояние?

Примечание. Если вам действительно нужна крошечная временная задержка, вам следует взглянуть на инструкция. Однако вам не нужна временная задержка - вам нужен тайм-аут (например, «вращаться с ; до тех пор, пока говорит, что прошло определенное количество времени). Для этого у меня возникнет соблазн разбить его на внутренний цикл, который делает " и проверьте условие N раз», а затем внешний цикл, который «повторяет внутренний цикл, если срок еще не истек».

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