Студопедия

КАТЕГОРИИ:

АстрономияБиологияГеографияДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРиторикаСоциологияСпортСтроительствоТехнологияФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника


Целостность памяти, временный доступ к памяти и volatile-поля




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

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

Вот пример:

internal sealed class CacheCoherencyProblem { private Byte uninitialized = 0; private Int32 m_value = 0;

// Этот метод выполняется одним потоком.

public void ThreadK) {

m_value = 5;

uninitialized =1;

// Этот метод выполняется другим потоком, public void Thread2() {

if (uninitialized == 1) {

// Эта строка может выполняться и отображать значение 0.

Console.WriteLine(m_value);

Теперь допустим, что экземпляр этого класса создается на компьютере с двумя процессорами. Первый процессор выполняет метод Thread 1, а второй — метод Tbread2. Представьте, что выполнение происходит следующим образом.

1. Поток второго процессора считывает из памяти байт, и этот байт располагается сразу перед байтами поля mjualue объекта. В этом случае считывается целая строка кеша, а значит, байты поля mjualue попадают в кеш второго процессора. С точки зрения программиста, процессор считал значение из этого поля прежде, чем программа запросила считывание этого поля.

2. Поток первого процессора выполняет метод Threadl, который присваивает полю m_value значение 5, но это изменение может произойти в кеше первого процессора. В конечном счете, это изменение будет записано в оперативную память. С точки зрения программиста, процессор записывает значение этого поля в память намного позже того момента, когда код запросил изменение поля.

3. Поток первого процессора продолжает выполнять метод Threadl и изменяет значение поля mjnitialized на 1. Это также происходит в кеше первого процессора, но байты поля mjnitialized находятся в другой строке кеша, и поэтому могут быть сброшены в оперативную память.

4. Поток второго процессора начинает выполнение метода Thread2, который сначала запрашивает значение поля mjnitialized. Поскольку' байт этого значения в кеше второго процессора нет, они считываются из оперативной памяти. Поле mjnitialized будет содержать значение 1, поэтому будет выполнено условие оператора if и его тело будет выполняться.

5. Поток второго процессора продолжает выполнять метод Thread2, который считывает значение поля m_value. Поскольку байты этого поля уже находятся в кеше второго процессора, они считываются оттуда, а не из оперативной памяти, и значение поля m_value считается равным 0!

Надеюсь, вы поняли, в чем проблема. Подведем итог: кеш процессоров улучшает производительность, но может привести к тому, что различные потоки будут использовать разные значения одной и той же переменной. Согласованность значений в кеше и оперативной памяти — серьезная проблема, и есть разные способы ее решения, но все они сильно снижают производительность. Лучше всего реализовать методы так, чтобы предотвратить одновременный доступ нескольких потоков к общим данным. Например, попытаться реализовать методы, которые обращаются только к параметрам и локальным переменным, чтобы другие потоки не имели доступа к этим переменным. Конечно, сама по себе переменная ссылочного типа обеспечивает безопасность потоков, но объект, на который она ссылается, — нет, поскольку иметь ссылку на него могут несколько потоков сразу. Более того, если несколько потоков обращаются к общим данным в режиме только для чтения, проблем не возникает. Проблема согласованности кеша существует только для общих данных, которые могут изменяться потоками.

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

Скорее всего, вы скажете, что никогда не видели, чтобы в вашей программе возникали проблемы из-за изменения порядка выполнения операторов, и это может оказаться чистой правдой. Это, без сомнения, так, если программа выполнялась на однопроцессорной системе или даже на компьютере с процессором Hyper-threading. Но в многопроцессорной системе или на компьютере с двухъядерным процессором неполадки такого рода вполне возможны. Кроме того, разные процессоры по-разному ведут себя в отношении согласованности кеша. Например, в процессорах х8б для обеспечения согласованности сделано многое. Поскольку архитектура хб4 обратно совместима с архитектурой х86, процессоры хб4 также обеспечивают согласованность кеша. Так что, если выполнять методы класса Cache-CoherencyProblem на компьютерах с процессорами х8б или хб4, проблем не будет. Метод Thread2 либо не вернет ничего, либо вернет значение 5.

Однако процессоры новой архитектуры (например, IA64) проектировались для полноценного использования того факта, что у каждого процессора собственный кеш, и для повышения производительности согласованность кеша практически не отслеживается. Процессоры 1Аб4 поддерживают обычные машинные команды для считывания значения из памяти в регистр и для записи значения регистра в память. Но есть и модифицированная версия команды чтения, которая считывает байты из памяти, а затем делает недействительным кеш процессора, чтобы следующая команда чтения извлекала значения из оперативной памяти. Это называют временным чтением (volatile read) или чтением с семантикой запроса (read with acquire semantics).

В архитектуре IA64 также есть видоизмененная версия команды записи, которая записывает байты из регистра в память и сбрасывает кеш процессора в основную память, так что остальные потоки, считывающие те же данные, получают самые последние значения. Это называют временной записью (volatile write) или записью с семантикой освобождения (write with release semantics).

В дополнение, в архитектуре IA64 предусмотрена команда защиты памяти (memory fence), которая сбрасывает кеш процессора в основную память, а затем объявляет его недействительным.

 


Поделиться:

Дата добавления: 2015-04-21; просмотров: 80; Мы поможем в написании вашей работы!; Нарушение авторских прав





lektsii.com - Лекции.Ком - 2014-2024 год. (0.005 сек.) Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав
Главная страница Случайная страница Контакты