Этот вопрос пришел из недавнего интервью с другом Bytes.В конце концов, он также успешно получил предложение Byte.Я думаю, что многие люди могут не знать этот вопрос, поэтому я хочу поговорить о нем отдельно.
Ладно, давайте к делу.
Что такое ложный обмен
Во-первых, всем известно, что при разнице в скорости разработки ЦП и памяти скорость ЦП намного выше, чем скорость памяти, поэтому текущий ЦП обычно добавляет кеш, который, как часто говорят, устраняет разницу в производительности между разное оборудование.вопрос.
В этом случае очень просто добавить кеш, что неизбежно приведет к проблеме когерентности кеша, поэтому вводится протокол когерентности кеша. (Если вы не знаете, рекомендуется перейти на Baidu, а не расширяться здесь)
Кэш ЦП, как следует из названия, чем ближе к ЦП, тем быстрее кеш, меньше емкость, выше стоимость, а кеш, как правило, можно разделить на трехуровневый кеш L1, L2, L3 в зависимости от производительности. деление: L1>L2>L3 .
На самом деле данные хранятся в кеше согласноРядхранить, это называетсястрока кэша. Строки кэша обычно представляют собой целые степени 2 байта, как правило, в диапазоне 32-256 байт, а наиболее распространенный размер строки кэша составляет 64 байта.
Поэтому согласно этому способу хранения данные в кеше хранятся не в одной переменной, а в одной строке размещаются несколько переменных.
Примером, о котором мы часто говорим, являются массивы и связанные списки. Адреса памяти массивов непрерывны. Когда мы читаем элементы в массиве, ЦП также загружает следующие элементы массива в кеш для повышения эффективности. , но связанный список не будет, то есть переменные с последовательными адресами памяти могут быть помещены в строку кэша.
Когда несколько потоков одновременно изменяют несколько переменных в строке кэша, только один поток может одновременно обрабатывать строку кэша, что приведет к снижению производительности.Эта проблема называетсяложный обмен.
Почему может работать только один поток? Давайте возьмем настоящий каштан, чтобы проиллюстрировать эту ситуацию:
Предполагая, что кэшx,y
Две переменные, они уже находятся в разных кэшах L3 одновременно.
В настоящее время есть два потока A и B для одновременного изменения переменных, расположенных в Core1 и Core2.x
а такжеy
.
Если поток A изменяет кеш Core1x
Переменная, из-за протокола когерентности кеша соответствующий кеш в Core2x
Строка кэша переменной будет признана недействительной, и будет принудительно перезагружена переменная из основной памяти.
В этом случае частый доступ к основной памяти, по сути, сделает кэш недействительным, что приведет к падению производительности, что и является проблемой ложного разделения.
Как этого избежать?
Теперь, когда вы знаете, что такое ложный обмен, как этого избежать?
Изменить способ хранения строки? Даже не думай об этом.
Оставшийся возможный метод - заполнить, если в этой строке есть только мои данные, разве это не хорошо?
Это действительно так, и обычно есть два решения.
вставка байтов
До JDK8 проблемы ложного совместного использования можно было избежать путем заполнения байтов, как показано в следующем коде:
Вообще говоря, строка кэша имеет 64 байта, мы знаем, что лонг равен 8 байтам, после заполнения 5 лонгов всего 48 байт.
В Java заголовок объекта занимает 8 байт в 32-битной системе и 16 байт в 64-битной системе, поэтому заполнение 5 длинных типов может занять 64 байта, что является строкой кэша.
@Содержание аннотации
JDK8 и более поздние версии Java предоставляютsun.misc.Contended
Аннотация, проблема ложного обмена может быть решена с помощью аннотации @Contented.
После использования аннотации @Contented будет добавлено 128 байт заполнения, и его нужно включить-XX:-RestrictContended
вариант вступления в силу.
Следовательно, с помощью двух вышеуказанных методов вы обнаружите, что размер заголовка объекта и размер строки кэша связаны с количеством бит операционной системы.Аннотации JDK помогают решить эту проблему, поэтому рекомендуется максимально используйте аннотации.
Хотя проблема ложного разделения решена, этот метод заполнения также тратит впустую ресурсы кеша.Его размер составляет всего 8 байт, но используется только 64 байта пространства кеша, что приводит к пустой трате ресурсов кеша.
А мы знаем, что тайник маленький и дорогой, и выбор времени и места надо рассматривать на свое усмотрение.
практическое применение
Java предоставляет классы операций для нескольких атомарных переменных, таких какAtomicLong
,AtomicInteger
Они, через метод CAS, чтобы обновить переменные, но сбой будет бесконечными попытками вращения, что приведет к пустой трате ресурсов ЦП.
Чтобы устранить этот недостаток при высокой степени параллелизма, в JDK8 добавлен новыйLongAdder
Класс, его использование является практическим применением решения ложного разделения.
LongAdder
унаследовано отStriped64
, который поддерживаетCell
Массив, основная идея состоит в том, чтобы разделить конкуренцию одной переменной в многопоточности, если однаCell
проиграть конкуренцию, уйти в другое местоCell
CAS еще раз, чтобы попробовать еще раз.
Настоящая суть решения проблемы ложного обмена заключается вCell
массив, как видите,Cell
используемый массивContented
аннотация.
Выше мы упоминали, что адреса памяти массива являются смежными, поэтому элементы массива часто помещаются в строку кэша, что приводит к проблеме ложного совместного использования и влияет на производительность.
используется здесьContented
При заполнении можно избежать проблемы ложного совместного использования, так что элементы в массиве больше не разделяют строку кэша.
Хорошо, это все о сегодняшнем содержании, я Ай Сяосянь, я еще не определился со своим слоганом, но увидимся в следующий раз.