Перейти к содержанию
    

'volatile' или, все-таки, нет?

1 час назад, amaora сказал:

А чтобы заставить компилятор прочитать значение глобальной переменной снова, а не кэшировать в регистрах, достаточно вызвать функцию имеющую side effect.

И как же узнать когда это сделать, если выполняющийся процесс не знает когда его прервали? Когда и как он будет вызывать эту функцию?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

9 hours ago, jcxz said:

И как же узнать когда это сделать, если выполняющийся процесс не знает когда его прервали? Когда и как он будет вызывать эту функцию?

Обычная синхронизация на флажках, все так же только не надо volatile.

int ready_flag; // no volatile, only atomic read/write

event_handler()
{
	read(data);
	memcpy(buf, data, ...);

	fence();

	ready_flag = 1;
}

main()
{
  	do {
      
		if (ready_flag == 1) {
          
 			do_something(buf);
 			ready_flag = 0;
          
 			req_new_event(&event_handler);
		}
      
		// ...
      
		//fence();
 		os_wait(1); // it has memory fence inside
	}
	while (1);
}

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 часа назад, amaora сказал:

Обычная синхронизация на флажках, все так же только не надо volatile.


int ready_flag; // no volatile, only atomic read/write

event_handler()
{
	read(data);
	memcpy(buf, data, ...);

	fence();

	ready_flag = 1;
}

main()
{
  	do {
      
		if (ready_flag == 1) {
          
 			do_something(buf);
 			ready_flag = 0;
          
 			req_new_event(&event_handler);
		}
      
		// ...
      
		//fence();
 		os_wait(1); // it has memory fence inside
	}
	while (1);
}

 

Если у Вас fence() запрещает события, вызывающие event_handler(), а req_new_event() - снова их разрешает, то возможно так и будет работать. При ещё дополнительном условии: что компилятор считает req_new_event() как имеющую side effects (что совсем не обязательно).

Но это упрощённый случай, когда нет одновременной работы двух процессов, а они работают поочередно. Да и то если компилятор посчитает req_new_event() имеющей side effects.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

9 hours ago, jcxz said:

Если у Вас fence() запрещает события, вызывающие event_handler(), а req_new_event() - снова их разрешает, то возможно так и будет работать. При ещё дополнительном условии: что компилятор считает req_new_event() как имеющую side effects (что совсем не обязательно).

Но это упрощённый случай, когда нет одновременной работы двух процессов, а они работают поочередно. Да и то если компилятор посчитает req_new_event() имеющей side effects.

В простом случае, когда достаточно барьера компилятора (процессор не может менять порядок записи в память), fence() ничего не будет делать, ни одной инструкции не будет сгенерировано, это только указание компилятору. Хотя есть вариант и с пустой функцией из отдельного модуля компиляции, если LTO не включена.

Событие подразумевалось разовое, повторный вызов обработчика будет только если снова настроить событие вызвав req_new_event(). Можно и FIFO сделать в конфигурации 1-читатель 1-писатель, для этого будет достаточно операции атомарной записи int и fence().

В SMP системе fence() будет содержать инструкции барьера, чтобы остальные процессоры увидели изменения в заданном порядке.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

22 минуты назад, amaora сказал:

Событие подразумевалось разовое, повторный вызов обработчика будет только если снова настроить событие вызвав req_new_event(). Можно и FIFO сделать в конфигурации 1-читатель 1-писатель, для этого будет достаточно операции атомарной записи int и fence().

И что мешает оптимизатору перенести ready_flag в регистр внутри main()? И всё перестанет работать, хоть разовое событие хоть многоразовое.

Никакие инструкции барьеров от этого не спасут.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 minute ago, jcxz said:

И что мешает оптимизатору перенести ready_flag в регистр внутри main()? И всё перестанет работать, хоть разовое событие хоть многоразовое.

Никакие инструкции барьеров от этого не спасут.

В цикле в main есть fence() или, что более естественно там есть некий os_wait() которые даёт аналогичный результат. Для компилятора вызов os_wait() имеет побочный эффект, а значит от должен сделать все записи глобальных переменных до вызова и прочитать все снова после него. Всегда так делаю.

Надо только помнить, что если при компиляции включена LTO, то компилятор может очень глубоко (до дна) заглянуть в os_wait() и сделать какие-то выводы. Лучше заранее убедиться, что там внутри есть аналог fence(), какой-то inline asm с указанием на изменение памяти, например. Либо это отдельный модуль компиляции.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

16 минут назад, amaora сказал:

В цикле в main есть fence() или, что более естественно там есть некий os_wait() которые даёт аналогичный результат. Для компилятора вызов os_wait() имеет побочный эффект, а значит от должен сделать все записи глобальных переменных до вызова и прочитать все снова после него. Всегда так делаю.

Ваш вариант работать будет только если в req_new_event() есть volatile-доступы к памяти. Иначе - компилятор запросто может ready_flag = 0 (саму запись в память) перенести в позицию после req_new_event() .

Событие (и прерывание) будет возникать сразу после req_new_event() и последующее за ним ready_flag = 0 будет сносить нафиг флаг.

И никакой fence() или os_wait() ему тут не помешает. Не в них дело.

9 минут назад, AlexRayne сказал:

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

volatile там у amaora есть. Должен быть. По-крайней мере - для доступа к каким-то переменным внутри приведённых в коде функций (он же пишет про side effects - почитайте внимательнее).

Иначе (если его вообще нигде нет), работать вообще не будет.

Но вышеприведённый код будет работоспособным только если volatile (side effects) есть внутри req_new_event(). А это ничем не лучше, чем просто поставить модификатор volatile самой переменной ready_flag.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

4 minutes ago, jcxz said:

Ваш вариант работать будет только если в req_new_event() есть volatile-доступы к памяти. Иначе - компилятор запросто может ready_flag = 0 (саму запись в память) перенести в позицию после req_new_event() .

Событие (и прерывание) будет возникать сразу после req_new_event() и последующее за ним ready_flag = 0 будет сносить нафиг флаг.

И никакой fence() или os_wait() ему тут не помешает. Не в них дело.

Так они там и есть, это же работа с регистрами IO чтобы включить событие. А даже если нет, то ещё один fence() так же можно поставить (если им и без того не является сам req_new_event), чтобы запись 0 не перенеслась.

12 minutes ago, AlexRayne said:

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

18 минут назад, amaora сказал:

Так они там и есть, это же работа с регистрами IO чтобы включить событие. А даже если нет, то ещё один fence() так же можно поставить (если им и без того не является сам req_new_event), чтобы запись 0 не перенеслась.

Ну тогда Вы неявно сделали то же самое, что если просто поставить volatile для ready_flag - код точно такой же будет. Тогда непонятно - почему бы его просто не поставить и всё? :wink:

20 минут назад, amaora сказал:

В некоторых случаях порождает лишний код множественной записи-чтения там где этого не нужно.

В Вашем примере не будет никаких множественных записей чтений.

А если ещё ready_flag = 0; поставить сразу после проверки if (ready_flag == 1), то это потенциально ещё уменьшит количество загрузок адреса (например для ARM-систем) на одну. Так как не надо будет дважды вычислять адрес ready_flag.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

11 minutes ago, jcxz said:

Ну тогда Вы неявно сделали то же самое, что если просто поставить volatile для ready_flag - код точно такой же будет. Тогда непонятно - почему бы его просто не поставить и всё? :wink:

В Вашем примере не будет никаких множественных записей чтений.

А если ещё ready_flag = 0; поставить сразу после проверки if (ready_flag == 1), то это потенциально ещё уменьшит количество загрузок адреса (например для ARM-систем) на одну. Так как не надо будет дважды вычислять адрес ready_flag.

ready_flag это на одном уровне кода (про который говорим), а работа с регистрами на другом (там volatile почти всегда к месту), не надо все в одно мешать. Сделаете тогда может быть все объявления типов с volatile?

С флажком самый простой пример, чтобы показать что без и volatile можно сделать то же самое, причём часто все fence() уже есть в коде.

Множественные ненужные чтения получаются когда volatile прилепляют ко всему, по методике "если доступ из разных потоков то надо volatile". Вот это я и считаю неверным, а не само использование volatile.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

5 часов назад, amaora сказал:

ready_flag это на одном уровне кода (про который говорим), а работа с регистрами на другом (там volatile почти всегда к месту), не надо все в одно мешать. Сделаете тогда может быть все объявления типов с volatile?

Не надо передёргивать! Я говорю лишь о том, что добавление volatile к ready_flag в вашем примере, никак не повлияет на код (не увеличит количество записей/чтений). Но при этом в то же время не нужно думать - а есть ли volatile внутри соседних функций или нет?

Так что никакого смысла убирать volatile из ready_flag нет. Плюсов это не даёт никаких.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...