Я пытаюсь оптимизировать небольшую программу, которая вычисляет идеальные числа по заданному показателю.
Программа работает (почти) идеально, но когда я открываю диспетчер задач, он все еще работает в одном потоке.Это означает, что я, должно быть, делаю что-то не так, но мои знания F # все еще находятся в «начальной» фазе.
Я постараюсь изложить этот вопрос настолько ясно, насколько это возможно, но если мне не удастся это сделатьпоэтому, пожалуйста, дайте мне знать.
Совершенное число - это число, в котором сумма всех его делителей (кроме самого числа) равна самому числу (например, 6 является идеальным, поскольку сумма егоделителями 1, 2 и 3 являются 6).
Я использую простые числа для ускорения вычислений, то есть меня не интересуют (огромные) списки, в которых хранятся все делители.Для этого я использую формулу, которая оказалась правильной для Евклида: (2 * (степень числа - 1)) * (2 * (степень числа - 1)), где последний - простое число Мерсенна.Я использовал очень быстрый алгоритм из stackoverflow (автор @Juliet), чтобы определить, является ли данное число простым числом.
Поскольку я перечитывал несколько статей (я еще не купил хорошую книгу, так что позоря) в интернете я обнаружил, что последовательности работают лучше, чем списки.Вот почему я впервые начал создавать функцию, которая генерирует последовательность совершенных чисел:
let perfectNumbersTwo (n : int) =
seq { for i in 1..n do
if (PowShift i) - 1I |> isPrime
then yield PowShift (i-1) * ((PowShift i)-1I)
}
Вспомогательная функция PowShift реализована следующим образом:
let inline PowShift (exp:int32) = 1I <<< exp ;;
Я использую битовый сдвигоператор, так как основа всех степенных расчетов от 2, следовательно, это может быть легкий путь.Конечно, я все еще благодарен за вклад в ответ на вопрос, который я задал по этому вопросу: F # Проблемы с питанием, которые принимают оба аргумента как bigints> F # Проблемы с питанием, которые принимают оба аргумента как bigints
Созданная Джульеттой функция ( заимствованная здесь ) выглядит следующим образом:
let isPrime ( n : bigint) =
let maxFactor = bigint(sqrt(float n))
let rec loop testPrime tog =
if testPrime > maxFactor then true
elif n % testPrime = 0I then false
else loop (testPrime + tog) (6I - tog)
if n = 2I || n = 3I || n = 5I then true
elif n <= 1I || n % 2I = 0I || n % 3I = 0I || n % 5I = 0I then false
else loop 7I 4I;;
Используя этот код без параллельных операций, на моем ноутбуке требуется около 9 минут, чтобы найти 9-е совершенное число (который состоит из 37 цифр и может быть найден со значением 31 для показателя степени).Так как мой ноутбук имеет ЦП с двумя ядрами, и только одно из них работает с 50 процентами (полная загрузка для одного ядра), я подумал, что мог бы ускорить вычисления, параллельно вычисляя результаты.
Поэтому я изменил свойФункция perfectnumber выглядит следующим образом:
//Now the function again, but async for parallel computing
let perfectNumbersAsync ( n : int) =
async {
try
for x in 1.. n do
if PowShift x - 1I |> isPrime then
let result = PowShift (x-1) * ((PowShift x)-1I)
printfn "Found %A as a perfect number" result
with
| ex -> printfn "Error%s" (ex.Message);
}
Чтобы вызвать эту функцию, я использую небольшую вспомогательную функцию для ее запуска:
let runPerfects n =
[n]
|> Seq.map perfectNumbersAsync
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
Где результат асинхронного вычисления игнорируется, так как яотображение его в функции perfectNumbersAsync.
Приведенный выше код компилируется и запускается, однако он все еще использует только одно ядро (хотя при вычислении 9-го идеального числа он работает на 10 секунд быстрее).Я боюсь, что это связано с вспомогательными функциями PowShift и isPrime, но я не уверен.Должен ли я поместить код этих вспомогательных функций в асинхронный блок perfectNumbersAsync?Это не улучшает читабельность ...
Чем больше я играю с F #, тем больше я учусь ценить этот язык, но, как и в этом случае, иногда мне нужны некоторые эксперты:).
Заранее спасибо за прочтение, я только надеюсь, что прояснил себя немного ...
Роберт.