Я пытаюсь оптимизировать небольшую программу, которая вычисляет совершенные числа из данного показателя.
Программа работает (почти) отлично, но когда я открываю диспетчер задач, она по-прежнему работает на одном потоке. Это означает, что я должен делать что-то неправильно, но мое знание F # все еще находится в начальной стадии.
Я постараюсь поставить этот вопрос как можно яснее, но если я не сделаю этого, сообщите мне.
Совершенное число - это число, где сумма всех его делителей (кроме самого числа) равна самому числу (например, 6 совершенна, так как сумма его делителей 1, 2 и 3 равна 6).
Я использую простые числа, чтобы ускорить вычисление, то есть меня не интересуют (огромные) списки, где хранятся все делители. Для этого я использую формулу, согласно которой Евклид оказался верным: (2 * (мощность num - 1)) * (2 * (мощность num - 1)), где последнее является простым числом Мерсенна. Я использовал очень быстрый алгоритм из stackoverflow (by @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 # Power, которые принимают оба аргумента как bigints > F # Проблемы с питанием, которые принимают оба аргумента как bigints
Созданная функция Juliet (заимствованная здесь) выглядит следующим образом:
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
Если результат вычисления async игнорируется, поскольку я показываю его в пределах perfectNumbersAsync.
Приведенный выше код компилируется и запускается, однако он по-прежнему использует только одно ядро (хотя при вычислении 9-го совершенного числа он работает на 10 секунд быстрее). Я боюсь, что он должен что-то сделать с вспомогательными функциями PowShift и isPrime, но я не уверен. Должен ли я помещать код этих вспомогательных функций в блок асинхронизации perfectNumbersAsync? Это не улучшает читаемость...
Чем больше я играю с F #, тем больше я учусь ценить этот язык, но, как и в этом случае, мне иногда нужны некоторые эксперты:).
Заранее спасибо за чтение этого, я только надеюсь, что я сделал себе немного ясно...
Роберт.