Как можно задействовать все потоки процессора для вычисления чисел Фибоначчи в C#?

Как заставить программу, вычисляющую число Фибоначчи, использовать 100% ресурсов процессора, распределяя нагрузку между всеми доступными ядрами? Я уже пробовал использовать Parallel.For, но не удалось сохранить результаты предыдущих вычислений. Моя реализация выглядит следующим образом: <br/> <pre><code class="cs">static public class Operations
{
    static public BigInteger calculateFib(int iterations)
    {
        BigInteger a = 0;
        BigInteger b = 1;
        for (int i = 0; i &lt; iterations; i++)
        {
            BigInteger temp = a;
            a = b;
            b = temp + b;
        }
        return a;
    }
}
}</code></pre> <br/> Можете помочь мне оптимизировать код для многопоточного выполнения?
  • 25 января 2025 г. 14:34
Ответы на вопрос 4
Для вычисления чисел Фибоначчи с использованием всех доступных потоков процессора можно использовать параллельные вычисления. Ваша задача может быть трудной, поскольку простая последовательная версия вычисления Фибоначчи не может быть легко распараллелена. Однако можно использовать подход, который разбиает вычисление Фибоначчи на более мелкие задачи.

Вот пример многопоточного решения для вычисления чисел Фибоначчи с сохранением результатов предыдущих вычислений:

```csharp
using System;
using System.Collections.Concurrent;
using System.Numerics;
using System.Threading.Tasks;

public static class Fibonacci
{
    // Используем потокобезопасный словарь для хранения результатов
    private static ConcurrentDictionary<int, BigInteger> memoizedResults = new ConcurrentDictionary<int, BigInteger>();

    public static BigInteger CalculateFibonacci(int n)
    {
        // Проверяем, если результат уже вычислен
        if (memoizedResults.TryGetValue(n, out BigInteger result))
        {
            return result;
        }

        // Базовые случаи
        if (n == 0) return 0;
        if (n == 1) return 1;

        // Параллельный расчет для n-1 и n-2
        var task1 = Task.Run(() => CalculateFibonacci(n - 1));
        var task2 = Task.Run(() => CalculateFibonacci(n - 2));

        // Ждем завершения обеих задач и получаем результат
        BigInteger fib1 = task1.Result;
        BigInteger fib2 = task2.Result;

        // Сохраняем результат в словаре
        result = fib1 + fib2;
        memoizedResults[n] = result;

        return result;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        int n = 50; // Вычисляем 50-е число Фибоначчи
        BigInteger fib = Fibonacci.CalculateFibonacci(n);
        Console.WriteLine($"Fibonacci({n}) = {fib}");
    }
}
```

### Объяснение:

1. Используется `ConcurrentDictionary` для хранения предыдущих вычисленных результатов. Это позволяет избегать повторных вычислений и минимизирует время, необходимое для получения результатов.
  
2. Функция `CalculateFibonacci` запускает два асинхронных вычисления для `n-1` и `n-2` с использованием `Task.Run`. Это позволяет использовать несколько потоков для выполнения вычислений параллельно.

3. Когда оба вычисления завершаются, результаты складываются, и результат сохраняется в `memoizedResults` для дальнейшего использования.

### Важно
Обратите внимание, что такой подход может создать много задач, и при большом `n` он все равно может привести к большому количеству потоков, что может привести к снижению производительности. Для больших `n`, использование простого алгоритма с памятью (memoization) может быть более эффективным, чем полный параллелизм.

Также стоит учитывать, что подход с запущенными задачами имеет накладные расходы. Проверяйте производительность и сравнивайте с другими решениями, чтобы выбрать наиболее подходящее для ваших целей.
Я уже знаю про Parallel.For, но такой подход не позволяет мне сохранить результат предыдущих номеров

Ну вот в этом и смысл параллельных вычислений ))) Ваш алгоритм не может работать в несколько потоков в этом виде, именно потому что каждому следующему результату нужен предыдущий. Избавляйтесь от этого условия - и получится распараллелить
Чтобы код можно было распараллелить - в нём должны быть шаги, которые можно распараллелить. 

По аналогии с примером из реальной жизни - ну никак ты не распараллелишь:
1. Достать банку с огурчиками
2. Открыть крышку
3. Достать огурчик
4. Нарезать огурчик на маленькие кругляши
5. Положить кругляши на бутерброд

У тебя каждый следующий шаг требует результат работы всех предыдущих шагов.

В твоём варианте можно попробовать оптимизировать, добавив кэширование уже посчитанных значений (смотри OrderedDictionary). Чтобы какие-то можно было достать сразу из кэша или начать рассчитывать с 50%, а не с самого начала.
Я вижу следующие пути оптимизации. 
1) Мемоизировать (запомнить в массив) хотя-б первую сотню чисел. Тогда например для 120 чиссла
все считать не надо и можно делать перемотку вперед. Или мемоизировать с шагом. Тоже экономия.

2) Можно попробовать считать через золотое сечение (приближенно) если этого достаточно для
ответа.

3) Можно отказаться от java в пользу С++/GMP библиотек. Возможно расчет суммы будет быстрее.

Но вообще фибоначчи действительно не параллелится. В этом главная проблема.
Похожие вопросы