Ниже приводится вопрос для интервью:
Следующая однострочная строка генерирует и отображает список из первых 500 простых чисел. Как бы вы оптимизировали его, используя параллельный LINQ, сохраняя при этом ЕДИНОЕ ЗАЯВЛЕНИЕ C #:
MessageBox.Show(string.Join(",",
Enumerable.Range(2, (int)(500 * (Math.Log(500) + Math.Log(System.Math.Log(500)) - 0.5)))
.Where(x => Enumerable.Range(2, x - 2)
.All(y => x % y != 0))
.TakeWhile((n, index) => index < 500)));
Я попытался ввести в запрос AsParallel()
, а также ParallelEnumerable
, но не увидел ощутимых преимуществ на многоядерных машинах. Запрос по-прежнему интенсивно использует одно ядро ЦП, в то время как другие ядра проводят свободное время. Может ли кто-нибудь предложить улучшение, которое равномерно распределяет нагрузку на все ядра и, таким образом, сокращает время выполнения?
Для энтузиастов : следующая формула возвращает верхнюю границу, которая гарантированно будет больше, чем N простых чисел, т.е. если вы проверите до этого числа, вы обязательно найдете N простых чисел меньше его:
UpperBound = N * (Log(N) + Log(Log(N)) - 0.5) //Log is natural log
4 ответа
Это прекрасно работает на моей машине. Я никогда не видел, чтобы все мои ядра работали на 100% до сих пор. Спасибо, что дали мне повод поиграть :)
Я увеличивал числа, пока у меня не было достаточно медленного времени для измерения (20 000).
Ключевым параметром, который изменил меня, была установка ExecutionMode < / a> в ForceParallelism.
Поскольку я использую вариант слияния NotBuffered, я повторно сортирую его, когда закончу. В этом нет необходимости, если вас не волнует порядок результатов (возможно, вы помещаете результаты в HashSet).
DegreeOfParallelism и MergeOptions обеспечили лишь незначительный прирост производительности (если таковой был) на моей машине. В этом примере показано, как использовать все параметры в одном операторе Linq, что и было первоначальным вопросом.
var numbers = Enumerable.Range(2, (int)(20000 * (Math.Log(20000) + Math.Log(System.Math.Log(20000)) - 0.5)))
.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount)
.WithExecutionMode(ParallelExecutionMode.ForceParallelism)
.WithMergeOptions(ParallelMergeOptions.NotBuffered) // remove order dependancy
.Where(x => Enumerable.Range(2, x - 2)
.All(y => x % y != 0))
.TakeWhile((n, index) => index < 20000);
string result = String.Join(",",numbers.OrderBy (n => n));
8
на Environment.ProcessorCount
, чтобы сделать его общим. Благодаря тонну. Я приму это, если в ближайшее время у нас ничего не получится.
Enumerable.Range()
время сократилось с 8,5 до 0,2 секунды.
Вы можете проверить только SQRT значения, чтобы сделать это (обновленный код сверху)
var numbers = new[] {2, 3}.Union(Enumerable.Range(2, (int) (i*(Math.Log(i) + Math.Log(Math.Log(i)) - 0.5)))
.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount)
// 8 cores on my machine
.WithExecutionMode(ParallelExecutionMode.ForceParallelism)
.WithMergeOptions(ParallelMergeOptions.NotBuffered)
// remove order dependancy
.Where(x => Enumerable.Range(2, (int) Math.Ceiling(Math.Sqrt(x)))
.All(y => x%y != 0))
.TakeWhile((n, index) => index < i))
.ToList();
Но это безумие, когда у вас есть простой и очень быстрый алгоритм:
private static IEnumerable<int> GetPrimes(int k)
{
int n = (int)Math.Ceiling((k * (Math.Log(k) + Math.Log(Math.Log(k)) - 0.5)));
bool[] prime = new bool[n + 1];
prime[0] = prime[1] = false;
for (int i = 2; i < prime.Length; i++)
{
prime[i] = true;
}
for (int i = 2; i*i <= n; ++i) // valid for n < 46340^2 = 2147395600
if (prime[i])
{
for (int j = i*i; j <= n; j += i)
prime[j] = false;
yield return i;
}
}
Конечно, это не так хорошо, как LINQ, потому что это не модный способ решения проблемы, но вы должны знать, что он существует.
Math.Ceiling()
вместе с Math.Sqrt()
приведет к тому, что 2 и 3 будут рассматриваться как составные, поэтому их следует добавлять вручную в список вывода.
Union
из исходного кода
Stopwatch t = new Stopwatch();
t.Start();
var numbers = Enumerable.Range(2, (int)(500 * (Math.Log(500) + Math.Log(System.Math.Log(500)) - 0.5)))
.Where(x => Enumerable.Range(2, x - 2)
.All(y => x % y != 0))
.TakeWhile((n, index) => index < 500);
t.Stop();
MessageBox.Show(t.ElapsedMilliseconds.ToString());
MessageBox.Show(string.Join(",", numbers));
Он оценивается через 3 миллисекунды. Хороший запрос linq.
Для будущих читателей это то, чем я закончил. Это довольно быстро. На моей скромной машине он генерирует список из первых 20000 простых чисел меньше секунды.
Enumerable.Range(5, (int)(N * (Math.Log(N) + Math.Log(System.Math.Log(N)) - 0.5)))
.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount)
.WithExecutionMode(ParallelExecutionMode.ForceParallelism)
.WithMergeOptions(ParallelMergeOptions.NotBuffered) // remove order dependancy
.Where(x => Enumerable.Range(2, (int)Math.Ceiling(Math.Sqrt(x)))
.All(y => x % y != 0))
.TakeWhile((n, index) => index < N).Concat(new int[] { 2, 3 }.AsParallel()).OrderBy(x => x).Take(N);
.TakeWhile((n, index) => index < N)
на .TakeWhile((n, index) => index < N-2)
, а затем удалив .Take(N)
.
Похожие вопросы
Связанные вопросы
Новые вопросы
c#
C# (произносится как «see Sharp») — это высокоуровневый мультипарадигменный язык программирования со статической типизацией, разработанный Microsoft. Код C# обычно нацелен на семейство инструментов и сред выполнения Microsoft .NET, которое включает в себя .NET, .NET Framework, .NET MAUI и Xamarin среди прочих. Используйте этот тег для ответов на вопросы о коде, написанном на C#, или о формальной спецификации C#.