Недавно я ответил на вопрос об оптимизации вероятного параллелизуемого метода для генерации каждой перестановки произвольных базовых чисел. Я опубликовал ответ, подобный списку Параллельный, плохой реализации, и кто-то почти сразу указал на это:
Это в значительной степени гарантированно дает вам ложный доступ и, вероятно, будет во много раз медленнее. (кредит gjvdkamp)
и они были правы, это была медленная смерть. Тем не менее, я исследовал эту тему и нашел интересные материалы и предложения для борьбы с ней. Если я правильно понимаю, когда потоки обращаются к непрерывной памяти (скажем, к массиву, который, вероятно, поддерживает этот ConcurrentStack
), вероятно, происходит ложное разделение.
Для кода ниже горизонтального правила a Bytes
:
struct Bytes {
public byte A; public byte B; public byte C; public byte D;
public byte E; public byte F; public byte G; public byte H;
}
Для моего собственного тестирования я хотел получить параллельную версию этого запуска и быть действительно быстрее, поэтому я создал простой пример, основанный на исходном коде. 6
как limits[0]
был ленивым выбором с моей стороны - мой компьютер имеет 6 ядер.
Блок с одним потоком Среднее время выполнения: 10 с0059мс
var data = new List<Bytes>();
var limits = new byte[] { 6, 16, 16, 16, 32, 8, 8, 8 };
for (byte a = 0; a < limits[0]; a++)
for (byte b = 0; b < limits[1]; b++)
for (byte c = 0; c < limits[2]; c++)
for (byte d = 0; d < limits[3]; d++)
for (byte e = 0; e < limits[4]; e++)
for (byte f = 0; f < limits[5]; f++)
for (byte g = 0; g < limits[6]; g++)
for (byte h = 0; h < limits[7]; h++)
data.Add(new Bytes {
A = a, B = b, C = c, D = d,
E = e, F = f, G = g, H = h
});
Параллельная, плохая реализация. Среднее время выполнения: 81s729ms, ~ 8700.
var data = new ConcurrentStack<Bytes>();
var limits = new byte[] { 6, 16, 16, 16, 32, 8, 8, 8 };
Parallel.For(0, limits[0], (a) => {
for (byte b = 0; b < limits[1]; b++)
for (byte c = 0; c < limits[2]; c++)
for (byte d = 0; d < limits[3]; d++)
for (byte e = 0; e < limits[4]; e++)
for (byte f = 0; f < limits[5]; f++)
for (byte g = 0; g < limits[6]; g++)
for (byte h = 0; h < limits[7]; h++)
data.Push(new Bytes {
A = (byte)a,B = b,C = c,D = d,
E = e,F = f,G = g,H = h
});
});
Параллельно,?? реализация Среднее время выполнения: 5s833ms, 92 утверждения
var data = new ConcurrentStack<List<Bytes>>();
var limits = new byte[] { 6, 16, 16, 16, 32, 8, 8, 8 };
Parallel.For (0, limits[0], () => new List<Bytes>(),
(a, loop, localList) => {
for (byte b = 0; b < limits[1]; b++)
for (byte c = 0; c < limits[2]; c++)
for (byte d = 0; d < limits[3]; d++)
for (byte e = 0; e < limits[4]; e++)
for (byte f = 0; f < limits[5]; f++)
for (byte g = 0; g < limits[6]; g++)
for (byte h = 0; h < limits[7]; h++)
localList.Add(new Bytes {
A = (byte)a, B = b, C = c, D = d,
E = e, F = f, G = g, H = h
});
return localList;
}, x => {
data.Push(x);
});
Я рад, что у меня была реализация, которая быстрее, чем однопоточная версия. Я ожидал, что результат приблизится к 10 с /6, или около 1,6 секунды, но это, вероятно, наивное ожидание.
Мой вопрос для параллелизированной реализации, которая на самом деле быстрее, чем однопоточная версия, есть ли дальнейшие оптимизации, которые могут быть применены к операции? Мне интересно об оптимизации, связанные с распараллеливанием, а не усовершенствования алгоритма, используемого для вычисления значений. В частности:
- Я знаю об оптимизации для хранения и заполнения как
struct
вместоbyte[]
, но это не связано с распараллеливанием (или это?) - Я знаю, что желаемое значение может быть оценено ленивым с помощью сумматора с волнообразным переносом, но так же, как оптимизация
struct
.