когда-то давно распараллеливал на С++ при помощи CreateThread — было просто чудо!
работа идеально делилась на количество потоков — ресурсоёмкости не добавлялась
на С# паралелил при помощи Parallel.For — какая-то жуть — накидывает 5% нагрузки в лучшем случае
а иногда так глючит что распараллеленый код выполняется за большее количество времени
что за хрень из-за чего?
пример кода
double[][] tmp = new double[no_channel][];
//for(int ic=0;ic<no_channel;ic++){
Parallel.For(0, no_channel, (Action<int>)((ic) =>
{
tmp[ic] = new double[no_point];
fs[ic].Next(ref tmp[ic], src);
}));
//}
Next — ресурсоёмкая функция с циклами
no_channel — от 8 до 16 (процессор 4х ядерный)
В чём косяк может быть?
Можно ли как нибудь передать ссылочную переменную?
Здравствуйте, vvv848165@ya.ru, Вы писали:
VYR>когда-то давно распараллеливал на С++ при помощи CreateThread — было просто чудо! VYR>работа идеально делилась на количество потоков — ресурсоёмкости не добавлялась
VYR>на С# паралелил при помощи Parallel.For — какая-то жуть — накидывает 5% нагрузки в лучшем случае VYR>а иногда так глючит что распараллеленый код выполняется за большее количество времени
VYR>что за хрень из-за чего?
VYR>пример кода VYR> double[][] tmp = new double[no_channel][];
VYR> //for(int ic=0;ic<no_channel;ic++){ VYR> Parallel.For(0, no_channel, (Action<int>)((ic) => VYR> { VYR> tmp[ic] = new double[no_point]; VYR> fs[ic].Next(ref tmp[ic], src); VYR> })); VYR> //} VYR>Next — ресурсоёмкая функция с циклами VYR>no_channel — от 8 до 16 (процессор 4х ядерный) VYR>В чём косяк может быть?
Конкретные цифры есть -- однопоточный вариант супротив многопоточного?
VYR>Можно ли как нибудь передать ссылочную переменную?
Куда передать?
Нужно больше инф-ии о проблеме. Попробуйте поиграть с параметро MaxParallelDegree, и сделать его, например, 4 (по числу ядер).
Здравствуйте, vvv848165@ya.ru, Вы писали:
VYR>на С# паралелил при помощи Parallel.For — какая-то жуть — накидывает 5% нагрузки в лучшем случае VYR>а иногда так глючит что распараллеленый код выполняется за большее количество времени VYR>что за хрень из-за чего?
Дело в том, что Parallel.For — под капотом довольно сложная вещь.
Помимо собственно параллельного выполнения она:
-Умеет обрабатывать exceptions и останавливает рабочие потоки, если в одном из них произошла ошибка
-Поддерживает несколько вариантов останова цикла (CancellationToken, ParallelLoopState.Break/Stop)
-Пытается оптимально балансить нагрузку, разбивая входные данные на диапазоны для независимой обработки и даже реализуя упрощенный work-stealing между этими диапазонами
Понятное дело, все это вносит накладные расходы.
Также, думаю понятно, что каким-бы умным ни был алгоритм балансировки в Parallel.For, он не вытянет против самописного алгоритма,
который досконально знает особенности входных данных, использует выделенные потоки (а не потоки из тред-пула) и в идеальном случае может вообще не тратиться на синхронизацию.
VYR>когда-то давно распараллеливал на С++ при помощи CreateThread — было просто чудо! VYR>работа идеально делилась на количество потоков — ресурсоёмкости не добавлялась
Используй старый добрый System.Threading.Thread.
Здравствуйте, vvv848165@ya.ru, Вы писали: VYR>Можно ли как нибудь передать ссылочную переменную?
Непонятно, куда передавать. Непонятно, что такое fs[ic], и почему в Next нужно передавать ref double[].
Для начала проверьте, что код делает то, что нужно, а не генерирует мусор.
Затем можно бенчмаркнуть три варианта кода при помощи стандартных средств:
— линейный
— честно-параллельный, через ручной System.Threading.Thread
— Parallel.For
После этого можно уже делать какие-то выводы и выбирать направление дальнейших раскопок.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
S>Непонятно, куда передавать. Непонятно, что такое fs[ic], и почему в Next нужно передавать ref double[].
ссылку в лямда выражение (она просто так не передаётся)
а что с ref double[]? S>Для начала проверьте, что код делает то, что нужно, а не генерирует мусор.
код отлажен и проверен ...
no_channel — от 8 до 16 (процессор 4х ядерный)
src.LongLength = 8192
public void Init(string[] names)
{
no_channel = names.Length;
fs = new FilterSections[no_channel];
gain = new double[no_channel];
for (int i = 0; i < no_channel; i++)
{
fs[i] = new FilterSections();
gain[i] = 1.0d;
Coeff coeff = FilterCoefficient.GetCoefficients(names[i]);
fs[i].Init(coeff);
}
}
class FilterSections
{
private Filter[] filters = null;
private int no_filtres = 0;
public FilterSections()
{
}
public void Init(Coeff coef)
{
no_filtres=coef.no_filtres;
filters = new Filter[no_filtres];
for (int i = 0; i < no_filtres; i++)
filters[i] = new Filter();
for (int i = 0; i < no_filtres; i++)
filters[i].Init(coef.pre_gain[i], coef.DEN[i * 2 + 0], coef.DEN[i * 2 + 1],coef.NUM[i]);
}
public void Next(ref double[] y, double[] x)
{
int no=x.Length;
double[] val = new double[no];
for (int j = 0; j < no; j++)
val[j] = x[j];
for (int i = 0; i < no_filtres; i++)
filters[i].Next(ref val, val);
for (int j = 0; j < no; j++)
y[j] = val[j];
/*
int no_filtres_m_1 = no_filtres - 1;
filters[0].Next(ref val, x);
for (int i = 1; i < no_filtres_m_1; i++)
filters[i].Next(ref val, val);
filters[no_filtres_m_1].Next(ref y, val);
*/
}
}
public class Filter
{
private double v1 = 0.0d, v2 = 0.0d;
private double pregain, a2, a3, b2;
public Filter()
{
}
public void Init(double _pregain, double _a2, double _a3, double _b2)
{
pregain = _pregain; a2 = _a2; a3 = _a3; b2 = _b2;
}
public void Next(ref double[] y, double[] x)
{
for (long i = 0; i < x.LongLength; i++)
{
double v0 = pregain * x[i] - a2 * v1 - a3 * v2;
y[i] = v0 + b2 * v1 + v2;
v2 = v1; v1 = v0;
}
}
}
Здравствуйте, vvv848165@ya.ru, Вы писали:
VYR>Здравствуйте, Sinclair, Вы писали:
S>>Непонятно, куда передавать. Непонятно, что такое fs[ic], и почему в Next нужно передавать ref double[]. VYR> ссылку в лямда выражение (она просто так не передаётся) VYR> а что с ref double[]? S>>Для начала проверьте, что код делает то, что нужно, а не генерирует мусор. VYR> код отлажен и проверен ... VYR>no_channel — от 8 до 16 (процессор 4х ядерный) VYR>src.LongLength = 8192
VYR>public void Init(string[] names) VYR> { VYR> no_channel = names.Length; VYR> fs = new FilterSections[no_channel]; VYR> gain = new double[no_channel];
VYR> for (int i = 0; i < no_channel; i++) VYR> { VYR> fs[i] = new FilterSections(); VYR> gain[i] = 1.0d; VYR> Coeff coeff = FilterCoefficient.GetCoefficients(names[i]); VYR> fs[i].Init(coeff); VYR> } VYR> }
VYR>class FilterSections VYR> { VYR> private Filter[] filters = null; VYR> private int no_filtres = 0;
VYR> public FilterSections() VYR> { VYR> }
VYR> public void Init(Coeff coef) VYR> { VYR> no_filtres=coef.no_filtres; VYR> filters = new Filter[no_filtres];
VYR> for (int i = 0; i < no_filtres; i++) VYR> filters[i] = new Filter();
VYR> for (int i = 0; i < no_filtres; i++) VYR> filters[i].Init(coef.pre_gain[i], coef.DEN[i * 2 + 0], coef.DEN[i * 2 + 1],coef.NUM[i]); VYR> } VYR> public void Next(ref double[] y, double[] x) VYR> { VYR> int no=x.Length; VYR> double[] val = new double[no];
VYR> for (int j = 0; j < no; j++) VYR> val[j] = x[j];
VYR> for (int i = 0; i < no_filtres; i++) VYR> filters[i].Next(ref val, val);
VYR> for (int j = 0; j < no; j++) VYR> y[j] = val[j];
VYR> /* VYR> int no_filtres_m_1 = no_filtres — 1; VYR> filters[0].Next(ref val, x); VYR> for (int i = 1; i < no_filtres_m_1; i++) VYR> filters[i].Next(ref val, val); VYR> filters[no_filtres_m_1].Next(ref y, val); VYR> */ VYR> } VYR> }
VYR>public class Filter VYR> { VYR> private double v1 = 0.0d, v2 = 0.0d; VYR> private double pregain, a2, a3, b2;
VYR> public Filter() VYR> { VYR> }
VYR> public void Init(double _pregain, double _a2, double _a3, double _b2) VYR> { VYR> pregain = _pregain; a2 = _a2; a3 = _a3; b2 = _b2; VYR> }
VYR> public void Next(ref double[] y, double[] x) VYR> { VYR> for (long i = 0; i < x.LongLength; i++) VYR> { VYR> double v0 = pregain * x[i] — a2 * v1 — a3 * v2; VYR> y[i] = v0 + b2 * v1 + v2;
VYR> v2 = v1; v1 = v0; VYR> } VYR> } VYR> }
1)Просьба, используйте форматирование для кода -- тэги c#.
2)Если код числодробилка, то возможно параллелить его особого смысла нет, дольше времени уйдет на переключение контекстов\управление потоками.
S>>2)Если код числодробилка, то возможно параллелить его особого смысла нет, дольше времени уйдет на переключение контекстов\управление потоками.
VYR>Почему??? переключение контекстов??? VYR>на С++ числодробилки хорошо паралелились даже без SSE
Я не уверен\ не знаю, использует ли CLR SSE по умолчанию? Это раз. А два, ну какой-то managed overhead на управление потоками все же присутсвтует.
А можно все-таки цифры производительности без и с многопоточностью увидеть?
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, vvv848165@ya.ru, Вы писали:
S>>>2)Если код числодробилка, то возможно параллелить его особого смысла нет, дольше времени уйдет на переключение контекстов\управление потоками.
VYR>>Почему??? переключение контекстов??? VYR>>на С++ числодробилки хорошо паралелились даже без SSE
S>Я не уверен\ не знаю, использует ли CLR SSE по умолчанию? Это раз. А два, ну какой-то managed overhead на управление потоками все же присутсвтует. S>А можно все-таки цифры производительности без и с многопоточностью увидеть?
запускаю с периодичностью 60 ms загрузка проца с распараллеливанием 14% без 12.5 %
Здравствуйте, vvv848165@ya.ru, Вы писали:
VYR>когда-то давно распараллеливал на С++ при помощи CreateThread — было просто чудо! VYR>работа идеально делилась на количество потоков — ресурсоёмкости не добавлялась
VYR>на С# паралелил при помощи Parallel.For — какая-то жуть — накидывает 5% нагрузки в лучшем случае VYR>а иногда так глючит что распараллеленый код выполняется за большее количество времени
VYR>что за хрень из-за чего?
Из-за того, что Parallel.For большая абстракция, чем потоки. Когда она работает, всё отлично — чем больше абстракция, тем более отлично. А когда не работает, то чем больше абстракция, тем ты хрен поймёшь, что с ней не так.
Напиши на потоках, и посмотри, что получится. Я подозреваю, что функция слишком быстрая, или garbage collection мозги пудрит, но по крайней мере, когда ты напишешь на потоках (которые Thread, такие же практически, как в C++), будет виднее.
int aworkerThreads;
int acompletionPortThreads;
ThreadPool.GetAvailableThreads(out aworkerThreads,out acompletionPortThreads);
int mworkerThreads;
int mcompletionPortThreads;
ThreadPool.GetMaxThreads(out mworkerThreads, out mcompletionPortThreads);
aworkerThreads 1023 int
acompletionPortThreads 1000 int
mworkerThreads 1023 int
mcompletionPortThreads 1000 int
пробовал даже так — все ровно ощутимая добавка к загрузке
Здравствуйте, vvv848165@ya.ru, Вы писали:
1)Просто интересно, что именно должна делать строка >>v2 = v1; v1 = v0; если v1,v2 -филды объекта ипользуемого в n потоках одновременно.
2)зачем ref double[]. ref нужен если предпологаеться пере-аллоцировать массив внутри функции а не изменить содержимое, если если лямбда сопротивляеться (а с чего бы), значит вы делаете что то странное. лямбды в циклах любят сюрпризы, и иногда делают не совсем то что кажется.
3)LongLength бесполезен, снаружи int, и вообще если нужна производительность тут самое место для fixed unsafe.
4)Array.CopyTo делает то же что и половина циклов но несколько быстрее, кроме того фильтру не нужны два массива, можно тупо скопировать начальное состояние в выходной массив, и потом последовательно применить фильтры прямо к нему, передавая и итерируя только один массив, промежуточный который создаеться в кажном Next, не нужен.
Здравствуйте, Teolog, Вы писали:
T>Здравствуйте, vvv848165@ya.ru, Вы писали: T>1)Просто интересно, что именно должна делать строка
нужны два предыдущих внутренних отсчёта Direct form II https://ccrma.stanford.edu/~jos/fp/Direct_Form_II.html (для каждого фильтра свой — кофициенты разные)
>>>v2 = v1; v1 = v0; если v1,v2 -филды объекта ипользуемого в n потоках одновременно.
T>2)зачем ref double[]. ref нужен если предпологаеться пере-аллоцировать массив внутри функции а не изменить содержимое, если если лямбда сопротивляеться (а с чего бы), значит вы делаете что то странное. лямбды в циклах любят сюрпризы, и иногда делают не совсем то что кажется. T>3)LongLength бесполезен, снаружи int, и вообще если нужна производительность тут самое место для fixed unsafe. T>4)Array.CopyTo делает то же что и половина циклов но несколько быстрее, кроме того фильтру не нужны два массива, можно тупо скопировать начальное состояние в выходной массив, и потом последовательно применить фильтры прямо к нему, передавая и итерируя только один массив, промежуточный который создаеться в кажном Next, не нужен.
убрал ref и ненужное копирование — ситуация не поменялась
в метод лучше передавать два массива (исходный массив тоже нужен)
VVV>>пробовал даже так — все ровно ощутимая добавка к загрузке VVV>> po.MaxDegreeOfParallelism=4;
B>Ну разве ты не добиваешься загрузки? Если ты имеешь ввиду, что лучше не стало, значит у тебя где-то лок глобальный и задачи его по очереди ждут
!!! если б я добивался загрузки я бы нашёл 1000 других способов !!!
Здравствуйте, vvv848165@ya.ru, Вы писали:
VYR> ссылку в лямда выражение (она просто так не передаётся)
Прекрасно передаётся. VYR> а что с ref double[]?
Вы делаете много лишней работы.
Вот ваш код без мусора, лишних копирований и ref:
public void Init(string[] names)
{
fs = (from name in names select new FilterSections(FilterCoefficient.GetCoefficients(name));
}
class FilterSections
{
private readonly Filter[] _filters;
public FilterSections(Coeff coef)
{
_filters = new Filter[coef.no_filtres];
for (int i = 0; i < _filters.Length; i++)
_filters[i] = new Filter(coef.pre_gain[i], coef.DEN[i * 2 + 0], coef.DEN[i * 2 + 1], coef.NUM[i]);
}
public void Next(double[] y, double[] x)
{
foreach(var filter in _filters)
filter.Next(y, x);
}
}
public class Filter
{
private readonly double _pregain;
private readonly double _a2;
private readonly double _a3;
private readonly double _b2;
private double _v1, _v2;
public Filter(double pregain, double a2, double a3, double b2)
{
_pregain = pregain; _a2 = a2; _a3 = a3; _b2 = b2;
}
public void Next(double[] y, double[] x)
{
for (long i = 0; i < x.LongLength; i++)
{
double v0 = _pregain * x[i] - _a2 * _v1 - _a3 * _v2;
y[i] = v0 + _b2 * _v1 + _v2;
_v2 = _v1;
_v1 = v0;
}
}
}
...
{
Parallel.For(0, no_channel, ((i) =>
{
tmp[i] = new double[8192];
fs[i].Next(tmp[i], src);
}));
}
S>>Для начала проверьте, что код делает то, что нужно, а не генерирует мусор. VYR> код отлажен и проверен ...
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, vvv848165@ya.ru, Вы писали:
VYR>запускаю с периодичностью 60 ms загрузка проца с распараллеливанием 14% без 12.5 %
Судя по всему, у вас просто слишком мало нагрузки.
То, что вы описываете — это числмолотилка отрабатывает за 0мс, а 12.5% отъедает тот код, который у вас выполняется между вызовами.
Я вам настоятельно советую перестать мерить производительность по таск менеджеру, и начать использовать взрослые инструменты.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Написал свой аналог класса Parallel — всё тоже самое — может дело в лямда выражениях и переменных
class MyParallel
{
private struct Work {
public Action<int> body;
public int start;
public int stop;
}
public static void For(int fromInclusive, int toExclusive, Action<int> body)
{
int noCpu = Environment.ProcessorCount;
Thread[]threads=new Thread[noCpu];
Work[] work = new Work[noCpu];
int Step=(toExclusive-fromInclusive)/noCpu;
for (int i = 0, Index=fromInclusive; i < noCpu; i++,Index+=Step)
{
threads[i] = new Thread(ParameterizedThreadStart);
work[i] = new Work();
work[i].start = Index;
work[i].stop = Index + Step;
work[i].body = body;
}
work[noCpu - 1].stop = toExclusive;
for (int i = 0; i < noCpu; i++)
threads[i].Start(work[i]);
for (int i = 0; i < noCpu; i++)
threads[i].Join();
}
private static void ParameterizedThreadStart(object obj)
{
for (int i = ((Work)obj).start; i < ((Work)obj).stop; i++)
((Work)obj).body(i);
}
}
VYR>Написал свой аналог класса Parallel — всё тоже самое — может дело в лямда выражениях и переменных
У меня вот с таким вариантом использования твоего метода For дает загрузку 100% всех ядер, а что у тебя за Body, нет ли там каких-то синхронизирующих элементов случаем,
например какой-нибудь вывод в UI
For(1, 1000000, (i) =>
{
double x = 0;
for (int j = 0; j < 100000; j++)
{
x = x + Math.Sin(j);
}
});
”Жить стало лучше... но противнее. Люди которые ставят точку после слова лучше становятся сторонниками Путина, наши же сторонники делают акцент на слове противнее ( ложь, воровство, лицемерие, вражда )." (с) Борис Немцов
Здравствуйте, okon, Вы писали:
O>У меня вот с таким вариантом использования твоего метода For дает загрузку 100% всех ядер, а что у тебя за Body, нет ли там каких-то синхронизирующих элементов случаем,
на X... мне загрузка всех ядер ...
ты хоть вопрос читал???
Здравствуйте, vvv848165@ya.ru, Вы писали:
VYR>Здравствуйте, okon, Вы писали:
O>>У меня вот с таким вариантом использования твоего метода For дает загрузку 100% всех ядер, а что у тебя за Body, нет ли там каких-то синхронизирующих элементов случаем,
VYR>на X... мне загрузка всех ядер ... VYR>ты хоть вопрос читал???
а С# паралелил при помощи Parallel.For — какая-то жуть — накидывает 5% нагрузки в лучшем случае
а иногда так глючит что распараллеленый код выполняется за большее количество времени
Т.е. я так понял у тебя задача выполнить паралельно за меньшее количество времени, чем одним потоком ?
По "закону сохранения энергии" это возможно только если ядра будут нагружены в сумме > 100%, например 26% каждое ядро.
а если хочется быстрее завершить задачу то надо использовать все 100%.
Как я сейчас понимаю ты не хочешь в 4 раза быстрее выполнить задачу, а просто хочешь каждое ядро по 25% нагрузить ?
Если так , то любая расчетная нагрузка будет занимать 100%, пока не выполнится.
Если у тебя там есть не только расчеты но и работа с устройствами, то надо смотреть как лучше.
Если хочется чтобы ядро немного "отдыхало" делай там Thread.Sleep(100) например или await Task.Delay(100) если используешь async/await.
For(1, 1000000, async (i) =>
{
double x = 0;
for (int j = 0; j < 1000; j++)
{
x = x + Math.Sin(j);
await Task.Delay(1);
}
});
Например вот так
”Жить стало лучше... но противнее. Люди которые ставят точку после слова лучше становятся сторонниками Путина, наши же сторонники делают акцент на слове противнее ( ложь, воровство, лицемерие, вражда )." (с) Борис Немцов
если в С++ всё по честному (если распределил на 4 проца — то выполнится в 4 раза быстрее)
то в С# какой-то облом (если распределил на 4 проца то выполнится только в 3 раза быстрее (а то и в два))
Здравствуйте, vvv848165@ya.ru, Вы писали:
VYR>В чём может быть ещё потеря производительности
VYR>если в С++ всё по честному (если распределил на 4 проца — то выполнится в 4 раза быстрее)
VYR>то в С# какой-то облом (если распределил на 4 проца то выполнится только в 3 раза быстрее (а то и в два))
GC, скорее всего. For Each, а также new double[], вот это всё. Можешь расчехлить Concurrency Visualizer и посмотреть.
VYR>если в С++ всё по честному (если распределил на 4 проца — то выполнится в 4 раза быстрее) VYR>то в С# какой-то облом (если распределил на 4 проца то выполнится только в 3 раза быстрее (а то и в два))
А еще не раскрыто что делает
obj.fs[ic].Next(obj.tmp[ic], obj.src);
Что в этом методе Next делается ?
”Жить стало лучше... но противнее. Люди которые ставят точку после слова лучше становятся сторонниками Путина, наши же сторонники делают акцент на слове противнее ( ложь, воровство, лицемерие, вражда )." (с) Борис Немцов
Ты можешь выложить код в самодостаточном виде, чтобы компилировался и запускался? Чтобы просто взять да посмотреть, что происходит. Иначе вся эта тема — пальцем в небо и гадание на кофейной гуще. Не понимаю, как другим удаётся советовать что-то осмысленное, не видя работающего кода.