Оптимизация C: условное хранилище, чтобы избежать загрязнения строки кэша

В источнике libuv я нашел этот код:

  /* The if statement lets the compiler compile it to a conditional store.
   * Avoids dirtying a cache line.
   */
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;

Может кто-нибудь объяснить это немного?

Что такое строка кэша?

Кроме того, я предполагаю, что условное хранилище представляет собой некоторую инструкцию Assembler, которая что-то проверяет и, если она удалась, записывает некоторое значение. Правильно?

Когда такая конструкция имеет смысл? Я думаю, не всегда, потому что иначе компилятор всегда будет использовать условное хранилище, верно?

Ответ 1

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

В общем, каждый уровень иерархии памяти: регистры, L1, L2, L3... кеш, основная память и пространство подкачки имеют разные копии одной и той же информации. Убедитесь, что разные части системы (процессоры, DMA, видеоподсистема и т.д.) Видят одинаковое значение, хотя одна или несколько копий могут быть изменены, называется проблемой согласованности.

Общее решение приостанавливает копирование обновленного значения на разные уровни иерархии. Это называется флеш.

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

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

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

"Условное хранилище" - немного неясный термин. Он просто ссылается на скачок нуля вокруг нормального хранилища, который является кодом, который компилятор будет производить из инструкции if. В X86 он будет выглядеть примерно так:

    ;; assume edi holds value of pointer 'loop'
    ;; and flag is a constant offset for the 'stop_flag' field.
    cmp dword ptr [edi, flag], 0
    jz no_store
    mov [edi, flag], 0
no_store:
   ... code continues

Если оператор if отсутствует, у вас будет только последняя инструкция mov.

NB. Комментирующий отметил, что на важных архитектурах процессоров существуют отдельные инструкции условного перемещения/хранения. Я не видел, чтобы gcc произвел один.

ли это стоит оптимизация очень спорно. У условностей есть свои риски для промывки конвейера команд (другой вид флеша). Никогда не жертвуйте ясностью для скорости, не имея четких доказательств того, что это необходимо.

Ответ 2

"to cache" означает скрыть что-то. Функция кэша в вычислении заключается в том, чтобы скрыть расстояние до основной памяти, выбирая как можно больше доступа к основной памяти.

Это работает, только если вы использовали данные раньше, но вы еще не вытолкнули его из кеша, и никто не забрал его перед вами. Любой другой актер (другой процессор, IO-Bus,...) должен иметь возможность получать текущее значение и изменять его, даже если вы его кэшировали. Эта задача выполняется с использованием протоколов когерентности кэширования. Более высокая согласованность означает более высокую стоимость.

То, что ваш код пытается сделать, заключается в том, что компилятор испускает условное перемещение, поэтому ЦП проверяет значение 0 и записывает только, если нет 0. Там есть целый ряд условных команд перемещения в Intel/AMD IS и многие другие.

Итак, шаг для шага:

  • Test for 0: Если ваш процессор не имеет копии проверенных данных, он должен запросить его. Это намного хуже, чем раньше. Позвольте надеяться, что вы не попали в основную память.
  • Подготовьте для записи значение:
    • У вас есть данные: Замечательно, что вы уже сделали.
    • У вас нет данных: Кэш призывает своих братьев и более высокие уровни, чтобы сообщить им, что теперь он владеет этой штукой. Никто не может сохранить копию.
  • Введите значение: кеш сохраняет изменения и отмечает, что кешлайн (самая низкая степень детализации кеша) грязный, необходимо записать обратно.

Итак, стоит ли это? Это зависит.

Кроме того: зачем предоставлять инструкцию условного хранилища, если вы можете синтезировать их с помощью условного перехода и магазина? Преимущества заключаются в использовании меньших инструкций и отсутствии риска для промывки конвейера (частично выполняются следующие инструкции). UPDATE: похоже, что они не могут перейти из регистра/немедленно в память на x86/x86_64.