Моим следующим предположением о том, как сделать это еще быстрее, было, естественно, распараллелить код, анализируя несколько его частей одновременно. Поскольку ни один XML-элемент не опирался на предыдущий, я мог безопасно анализировать равное (или почти равное) количество элементов в каждом потоке, а затем объединять коллекции.
И хотя я знаю, что существуют определенные накладные расходы на запуск потоков (а в моем случае также на разделение работы на потоки), я все же ожидал, что параллельная версия превзойдет по производительности последовательную версию на моем ПК (32 логических ядра, 24 физических ядра). Но какое бы количество потоков я ни пробовал для тестирования, оно всегда было медленнее.
Код: Выделить всё
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
// Nullable = tag is not guaranteed to appear in an XML element.
// Empty tags () would instead produce string.Empty.
public struct RawParameterData {
public ulong Id;
public string Name;
public ulong? OwnerComponentId;
public string Type;
public string Value;
public bool? Implicit;
public ulong ParentId;
public bool? Custom;
public override string ToString() => $"{Id} {OwnerComponentId} {Name} {Type} {Value} {Implicit} {ParentId} {Custom}";
}
// Just a set of methods to parse different data types from span of raw bytes
public static unsafe class RawPointerExtensions {
private static readonly Vector128 V16Lt = Vector128.Create((byte)' {
var p = (byte*)basePtr;
var a = borders[i];
var b = borders[i + 1];
threadLists[i] = RawPointerParser.ParseRange(p, a, b);
});
foreach (var t in threadLists) results.AddRange(t);
return results;
}
}
private static unsafe List FindChunkStarts(byte* ptr, long n, int threadCount, long start) {
var end = n - 10;
var work = end - start;
var slice = work / threadCount;
var starts = new List(threadCount + 1) { start };
for (var i = 1; i < threadCount; i++)
starts.Add(AlignToItem(ptr, start + slice * i, end));
starts.Add(end);
return starts;
}
private static unsafe long AlignToItem(byte* basePtr, long pos, long max) {
var p = basePtr + pos;
var end = basePtr + max - 5;
var m0 = Vector256.Create(Pattern[0]);
while (p < end) {
var v = Avx.LoadVector256(p);
var eq = Avx2.CompareEqual(v, m0);
var mask = Avx2.MoveMask(eq);
if (mask != 0) {
while (mask != 0) {
var idx = BitOperations.TrailingZeroCount(mask);
var q = p + idx;
if (q[4] == Pattern[4])
return q - basePtr + 1;
mask &= mask - 1;
}
}
p += 32;
}
return max;
}
}
[MemoryDiagnoser]
public class SampleParserBenchmark {
public string FilePath = @"C:\Parameter.xml";
[Benchmark] public List RawPointer() => RawPointerParser.ProcessFile(FilePath);
[Benchmark] public List RawPointerParallel() => RawPointerParserParallel.ProcessFile(FilePath);
[Benchmark] public List RawPointerParallel8() => RawPointerParserParallel.ProcessFile(FilePath, 8);
[Benchmark] public List RawPointerParallel16() => RawPointerParserParallel.ProcessFile(FilePath, 16);
}
public class Program {
public static void Main() => BenchmarkRunner.Run();
}
Метод
Среднее
Ошибка
StdDev
Gen0
Gen1
Gen2
Выделено
RawPointer
21,28 мс
0,413 мс
0,701 мс
1812.5000
1781.2500
937.5000
40,17 МБ
RawPointerParallel
26,65 мс
0,508 мс
0,522 мс
1593,7500
1562,5000
687,5000
53,86 МБ
RawPointerParallel8
23,29 мс
0,550 мс
1,623 мс
1750,0000
1718,7500
812,5000
47,93 МБ
RawPointerParallel16
23,03 мс
0,556 мс
1,640 мс
1750.0000
1718.7500
750.0000
48,44 МБ
Хотя ожидается более высокое распределение, учитывая, что я работаю с массивом списков, это все еще даже близко не соответствует нашему последовательному подходу. И я озадачен, как такое может быть. Я пытался протестировать поиск фрагмента отдельно, и в среднем это заняло около 500 нс для 8 потоков, так что здесь дело не в этом.
Файл, о котором идет речь, если кто-то хочет повторить мой тест (16 МБ): https://drive.google.com/file/d/1vI6g9O ... sp=sharing
Вопросы: что приводит к замедлению работы моего параллельного подхода и что я могу сделать, чтобы устранить эти узкие места? Неужели самое эффективное решение — это просто потратить 20 мс на один поток?
Подробнее здесь: https://stackoverflow.com/questions/798 ... ork-slower