каждый из методов вычисляет и меняет независимые друг от друга данные объекта Car.
Насколько опасным будет код если эти два метода распаллелить в Task.ContinueWhenAll ?
Нужна ли в данном случае синхронизация?
ET>каждый из методов вычисляет и меняет независимые друг от друга данные объекта Car. ET>Насколько опасным будет код если эти два метода распаллелить в Task.ContinueWhenAll ? ET>Нужна ли в данном случае синхронизация?
Классика.
Первый метод прочитал car и начал работать.
Второй метод прочитал car и начал работать.
Первый метод записал измененный car.
Второй метод записал свой car — и стер все изменения, записанные первым методом.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
ET>>Нужна ли в данном случае синхронизация? LVV>Классика. LVV>Первый метод прочитал car и начал работать. LVV>Второй метод прочитал car и начал работать. LVV>Первый метод записал измененный car. LVV>Второй метод записал свой car — и стер все изменения, записанные первым методом.
Что я делаю не так, почему не могу воспроизвести "классику"?
class Program
{
static void Main(string[] args)
{
const int iterationCount = 10000000;
var car = new Car();
var priceTask = Task.Run(
() =>
{
for (int i = 0; i < iterationCount; i++)
CalculatePrice(car);
});
var useTimeTask = Task.Run(
() =>
{
for (int i = 0; i < iterationCount; i++)
CalculateUseTime(car);
});
Task.WaitAll(priceTask, useTimeTask);
Console.WriteLine(car.Price);
Console.WriteLine(car.UseTime);
}
static void CalculatePrice(Car car)
{
car.Price += 1;
}
static void CalculateUseTime(Car car)
{
car.UseTime += 1;
}
}
class Car
{
public int Price;
public int UseTime;
}
Здравствуйте, e.thrash, Вы писали: ET>Нужна ли в данном случае синхронизация?
Нет (при условии, что оба потока не разделяют друг с другом какие-либо изменяемые данные).
Но есть нюансЪ: активная запись-чтение по соседним адресам приводит к накладным расходам на синхронизацию линеек кэша.
Воспроизвести можно, но нужно постараться.
Во-первых, на современных ноутбуках надо включать режим "максимальная производительность" и запитывать от сети. Иначе один и тот же код при последовательных запусках выдаёт 5-15% разницы, если утыкается в CPU и до 40% — если в память. Разумеется, это для коротких бенчмарков, для длинных разница сглаживается.
Во-вторых, надо угадать с аллокациями. Чтобы показать что проблема именно в линейке кэша, надо уложить два инстанса в емнип соседние 64 байт (для интелов). Не всегда удаётся, от запуска к запуску вариант TwoInstancesNear может скакать туда-сюда.
ET>>каждый из методов вычисляет и меняет независимые друг от друга данные объекта Car. ET>>Насколько опасным будет код если эти два метода распаллелить в Task.ContinueWhenAll ? ET>>Нужна ли в данном случае синхронизация? LVV>Классика.
Лебединое? Принц Датский? LVV>Первый метод прочитал car и начал работать.
Отлично. LVV>Второй метод прочитал car и начал работать.
Так-так. LVV>Первый метод записал измененный car.
Какой изменённый? LVV>Второй метод записал свой car — и стер все изменения, записанные первым методом.
Какой свой?
Профессор не слышал про указатели?
Или если нет звёздочки — то всё, копия?
Никогда не понимал такой уровень оптимизации на managed языках. Все равно же то, что написано будет прогоняться через jit, и последнее слово за ним. Или я чего-то не понимаю...
Здравствуйте, Sharov, Вы писали:
S>Никогда не понимал такой уровень оптимизации на managed языках. Все равно же то, что написано будет прогоняться через jit, и последнее слово за ним. Или я чего-то не понимаю...
JIT довольно предсказуем. В смысле, оптимизации примерно одни и те же, что в 4.0 x86, что на свежем ryuJit. На старом x64 местами была беда, но сейчас про это можно забыть с чистой совестью.
Ну и знать — не значит "нужно обязательно использовать". Скорее наоборот, знать — это видеть косяки _до_ того, как они выстрелят. Как в этом топике или в примерах с интерполяцией строк
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, Sharov, Вы писали:
S>>Никогда не понимал такой уровень оптимизации на managed языках. Все равно же то, что написано будет прогоняться через jit, и последнее слово за ним. Или я чего-то не понимаю... S>JIT довольно предсказуем. В смысле, оптимизации примерно одни и те же, что в 4.0 x86, что на свежем ryuJit. На старом x64 местами была беда, но сейчас про это можно забыть с чистой совестью.
Слишком гранулярная оптимизация для managed языков. Скорее всего я не прав, но моя точка зрения такая: вот мы оптимизируем на уровне линий кэша, вот у нас имеется сторонняя инфраструктура (jit+gc), нами не контролируемая. Каждая из этих структур также работает с кэшом и проч. периферией. Jit более менее понятен, ибо один раз компилирует "раз и навсегда", т.е. temporal locality у нас не пострадает. А вот допустим мы закладываемся на spatial locality в нашем коде, не учитывая инфраструктуру. А тут бац, и гц начинает свою работу ломая все наши предположения об выполнении нашего кода и всяческих spatial locality. Такое возможно или я написал бред?
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, Sinix, Вы писали:
S>Никогда не понимал такой уровень оптимизации на managed языках. Все равно же то, что написано будет прогоняться через jit, и последнее слово за ним. Или я чего-то не понимаю...
jit может пустить расчет каждого свойства в отдельном потоке или наоборот два потока в один схлопнуть?
Здравствуйте, Sharov, Вы писали:
S>Слишком гранулярная оптимизация для managed языков. Скорее всего я не прав, но моя точка зрения такая: вот мы оптимизируем на уровне линий кэша, вот у нас имеется сторонняя инфраструктура (jit+gc), нами не контролируемая.
Это _не_ оптимизация, это не пессимизация скорее.
Никакой магии тут нет. Точнее, не больше магии, чем в классике про branch prediction или в использовании быстрой сортировки вместо пузырьковой. Если грабли уже 100 раз хожены-перехожены, смысл ещё раз на них наступать?
S>А вот допустим мы закладываемся на spatial locality в нашем коде, не учитывая инфраструктуру.
Ну... закладываться-то можно, только её нужно ещё и обеспечить
Или храним данные в массиве, или вытаскиваем часть кода в unmanaged и рулим сами.
Как иначе-то?*
Здравствуйте, e.thrash, Вы писали:
ET>Здравствуйте, Sharov, Вы писали:
S>>Здравствуйте, Sinix, Вы писали:
S>>Никогда не понимал такой уровень оптимизации на managed языках. Все равно же то, что написано будет прогоняться через jit, и последнее слово за ним. Или я чего-то не понимаю...
ET>jit может пустить расчет каждого свойства в отдельном потоке или наоборот два потока в один схлопнуть?
S>Или храним данные в массиве, или вытаскиваем часть кода в unmanaged и рулим сами.
Ситуация: храним данные в массиве, работаем с массивом и т.д. и т.п, т.е. spatial locality обеспечена. Во время манипуляций с массивом, начинает свою работу gc и содержимое кэша херится. Такое возможно?
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, e.thrash, Вы писали:
ET>>Здравствуйте, Sharov, Вы писали:
S>>>Здравствуйте, Sinix, Вы писали:
S>>>Никогда не понимал такой уровень оптимизации на managed языках. Все равно же то, что написано будет прогоняться через jit, и последнее слово за ним. Или я чего-то не понимаю...
ET>>jit может пустить расчет каждого свойства в отдельном потоке или наоборот два потока в один схлопнуть?
S>Извините, но не понял вопрос.
изначально я спросил как распаралеллить расчет 2 непересекающихся свойств одного объекта, чтобы расчет был не 2 минуты, а одну.
вы написали S>>>Никогда не понимал такой уровень оптимизации на managed языках. Все равно же то, что написано будет прогоняться через jit, и последнее слово за ним.
Здравствуйте, Sharov, Вы писали:
S>Ситуация: храним данные в массиве, работаем с массивом и т.д. и т.п, т.е. spatial locality обеспечена. Во время манипуляций с массивом, начинает свою работу gc и содержимое кэша херится. Такое возможно?
Троллинг mode: c spatial locality всё по прежнему ок, в процессе массив переедет целиком (если переедет вообще). Вот с cache locality ой, да.
Короткий ответ: в этом случае замусоривание кэша — меньшая из ваших проблем.
Начнём с очевидного: полный stop the world — это очень больно, обидно и неактуально. <gcConcurrent> на свежих рантаймах по умолчанию выставлен в true, stop the world выполняется только для младших поколений, плюс, для эстетов завезли SustainedLowLatency. За подробностями — тынц и тынц.
Тем не менее, полная сборка мусора, даже в фоне и без блокировок — удовольствие всё равно дорогое и его следует по возможности избегать.
Хорошая новость: ситуация "работаем с массивом" редко приводит к вызову сборки мусора. Разве что вы аллоцируете память как не в себя. Но тогда, опять-таки, кэш — меньшая из ваших проблем
Здравствуйте, Sharov, Вы писали:
S>Я к тому, что оптимизация на таком уровне (линии кэша, кто кого вытолкнет и т.д.) особого смысла, кмк, не имеет.
Тут вопрос затраты/выигрыш. Ничего кроме знать и примерно представлять, как оно работает тут не требуется, так что можно и заморочиться. Ну да, это мелочи второго порядка, которые оказывают гораздо меньший эффект, чем, скажем, боксинг, но хорошо написанный код должен учитывать и эти моменты.
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, Sharov, Вы писали:
S>>Я к тому, что оптимизация на таком уровне (линии кэша, кто кого вытолкнет и т.д.) особого смысла, кмк, не имеет.
S>Тут вопрос затраты/выигрыш. Ничего кроме знать и примерно представлять, как оно работает тут не требуется, так что можно и заморочиться. Ну да, это мелочи второго порядка, которые оказывают гораздо меньший эффект, чем, скажем, боксинг, но хорошо написанный код должен учитывать и эти моменты.
Нет, это не мелочи для как минимум матричных и проч. мат операций, которые критичны к производительности. Но по отношению к другим возможным косякам, это да, мелочи. Т.е. все серьезные мат. библиотеки (от того же интела) писались и будут писаться на с\с++\фортран.
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, e.thrash, Вы писали:
ET>>Нужна ли в данном случае синхронизация?
S>Нет (при условии, что оба потока не разделяют друг с другом какие-либо изменяемые данные). S>Но есть нюансЪ: активная запись-чтение по соседним адресам приводит к накладным расходам на синхронизацию линеек кэша. S>Воспроизвести можно, но нужно постараться.
Гораздо вероятнее, что код в будущем изменится и неявное предположение, что параллельный код меняет разные части объекта, станет ложным.