Потоки вложенных циклов foreach? - PullRequest
2 голосов
/ 07 августа 2009

Helllo!

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

Вот часть моего кода, и она хорошо работает (без многопоточности).

private double _bestScore = 0.0;

private void BruteForce()
{
  foreach (Stats head in _heads)
    foreach (Stats chest in _chests)
      foreach (Stats leg in _legs)
        foreach (Stats feet in _feets)
        {
          int stamina = head.sta + chest.sta + leg.sta + feet.sta;
          int power = head.power + chest.power + leg.power + feet.power;
          int armor = head.armor + chest.armor + leg.armor + feet.armor;
          int hit = head.hit + chest.hit + leg.hit + feet.hit;

          double total = stamina * _scaleSta + power * _scalePower + armor * _scaleArmor;

          if (total > _bestScore && hit >= 100)
          {
            _bestScore = total;

            // Store best setup for output when done with bruteforce
            _bestHead = head;
            _bestChest = chest;
            _bestLeg = leg;
            _bestFeet = feet;
          }
        }
}

Итак, есть ли способ улучшить это с помощью потоков?

Редактировать: Исправлена ​​опечатка и обновлен, чтобы включить другую статистику, хит. Хит должен достигать 100, чтобы стать возможной частью «лучшего счета». Это позволяет мне не проверять каждый отдельный слот, чтобы найти лучшее снаряжение. Хит - очень важный показатель до 100, но каждое очко после 100 бесполезно. Кроме того, эта часть моего кода имеет гораздо больше циклов foreach (28 вместо 4). Так что количество итераций много. Однако каждый список (_heads, _chests и т. Д.) Обычно содержит максимум 2 элемента.

Ответы [ 4 ]

3 голосов
/ 07 августа 2009

Если вы хотите простой способ добавления многопоточности, посмотрите на Parallel Extensions for .NET . По соображениям производительности вам нужно будет только вызвать вызов Parallel.For () для самого внешнего цикла.

1 голос
/ 07 августа 2009

Я почти уверен, что это сработает, отсутствие тестовых данных или компилятора делает меня не на 100% уверенным:

private void BestItems()
{
    _bestHead = GetBestItem(_heads);
    _bestChest = GetBestItem(_chests);
    _bestLeg = GetBestItem(_legs);
    _bestFeet = GetBestItem(_feets);
}


private Stats GetBestItem(List<Stats> items)
{
    double best = 0.0;
    Stats result = null;

    foreach stats item in items
    {
        double total = item.stamina * _scaleSta + item.power * _scalePower + item.armor * _scaleArmor;

        if (total > best)
        {
            result = item;
        }
    }

    return result;
}

Edit:

Шаги:

  1. Создайте список для каждого слота в порядке наиболее важного показателя (наименьшего первого)
  2. Используйте циклическое взвешивание, чтобы найти наименьшие значения попадания, которые соответствуют вашему рейтингу попаданий. (тебе понадобится это на каждый слот для моего следующего шага)
  3. Для каждого слота выберите предмет с лучшей статистикой, которая соответствует минимальному рейтингу попаданий в слоты.

вам понадобится много циклов один за другим, но это лучше, чем 2 ^ 28, я думаю: p

Edit2: Опять же, здесь все еще нет компилятора ... но это может сработать. Вы получите кучу потоков, хотя ...

Сведения о присоединении и ожидании потока см. Здесь (msdn) (посмотрите на мьютекс, монитор, ManualResetEvent, AutoResetEvent)

private void BruteForce()
{

  var threads = new List<Thread>;
  foreach (Stats head in _heads)
    foreach (Stats chest in _chests)
      foreach (Stats leg in _legs)
        foreach (Stats feet in _feets)
        {
            if (threads.Count <= 2)


            thread worker = new thread(addressof Process, new object() {head, chest, leg, feet, ...});
            worker.start();
            threads.add(worker);
        }

    foreach (Thread t in threads)
        t.join();  //this might not be the best as it might make the main thread wait for each thread one after the other, not when all finished.  A manual/auto reset is probably better here.
}


private void Process(params...)
{

    int stamina = head.sta + chest.sta + leg.sta + feet.sta;
    int power = head.power + chest.power + leg.power + feet.power;
    int armor = head.armor + chest.armor + leg.armor + feet.armor;
    int hit = head.hit + chest.hit + leg.hit + feet.hit;

    double total = stamina * _scaleSta + power * _scalePower + armor * _scaleArmor;

    lock _bestscore
    {
        if (total > _bestScore && hit >= 100)
        {
            _bestScore = total;

            // Store best setup for output when done with bruteforce
            _bestHead = head;
            _bestChest = chest;
            _bestLeg = leg;
            _bestFeet = feet;
        }
    }
}

РЕДАКТИРОВАТЬ 4: Угадайте, у кого еще нет компилятора рядом с ним? Что-то вроде этого должно гарантировать, что в любой момент у вас есть только 2 потока.

var threads = new Dictionary<Guid, Thread>;

private void BruteForce()
{
  foreach (Stats head in _heads)
    foreach (Stats chest in _chests)
      foreach (Stats leg in _legs)
        foreach (Stats feet in _feets)
        {
            while (threads.Count >= 2) {}    //im sure thread.join or equivelent can do this instead of a nasty loop :p

            var guid = Guid.NewGuid();
            thread worker = new thread(addressof Process, new object() {guid, head, chest, leg, feet, ...});
            worker.start();
            threads.add(guid, worker);
        }

    foreach (Thread t in threads)
        t.join();  //this might not be the best as it might make the main thread wait for each thread one after the other, not when all finished.  A manual/auto reset is probably better here.
}

private void Process(params...)
{
    int stamina = head.sta + chest.sta + leg.sta + feet.sta;
    int power = head.power + chest.power + leg.power + feet.power;
    int armor = head.armor + chest.armor + leg.armor + feet.armor;
    int hit = head.hit + chest.hit + leg.hit + feet.hit;

    double total = stamina * _scaleSta + power * _scalePower + armor * _scaleArmor;

    lock _bestscore
    {
        if (total > _bestScore && hit >= 100)
        {
            _bestScore = total;

            // Store best setup for output when done with bruteforce
            _bestHead = head;
            _bestChest = chest;
            _bestLeg = leg;
            _bestFeet = feet;
        }
    }

    _threads.remove(guid)
}
0 голосов
/ 07 августа 2009

Подход к этой проблеме состоит в том, чтобы развернуть некоторые циклы и создать несколько рабочих элементов, которые можно обрабатывать параллельно. Примерно так:

private struct Stat
{
   int sta;
   int power;
   int armor;
   int hit;
};
private List<Stat[]> workItems = new List<Stat[]>();
private const int NUMBER_OF_CPUS = 4;

private void BruteForce()
{

    //create some work load to split into separate threads
    //we do this by unnesting the first 3 loops.
    //maybe more unnesting is needed to get some more work items
    //for thread to process
    //at least one item per thread/CPU makes sense
    foreach (Stats head in _heads)
      foreach (Stats chest in _chests)
        foreach (Stats leg in _legs)
        {
          this.workItems .Add(new Stat[3] { head, chest, leg });
        }

Далее следует разделить эти рабочие элементы на куски, которые могут быть обработаны одним потоком. Вы бы выбрали количество потоков, равное количеству процессорных ядер. Ваша функция потока, по крайней мере, имеет параметр, обеспечивающий рабочие элементы потока.

Начало вашей функции потока может выглядеть следующим образом:

private void Process(object param)
        {
            List<Stat[]> threadWorkitems = (List<Stat[]>)param;

            foreach (Stat workItem in threadWorkitems)
            {
                Stats head = threadWorkitems[0];
                Stats chest = threadWorkitems[1];
                Stats leg = threadWorkitems[2];

                foreach (Stats feet in _feets)
                {
                    int stamina = head.sta + chest.sta + leg.sta + feet.sta;
                    int power = head.power + chest.power + leg.power + feet.power;
                    int armor = head.armor + chest.armor + leg.armor + feet.armor;
                    int hit = head.hit + chest.hit + leg.hit + feet.hit;

                    double total = stamina * _scaleSta + power * _scalePower + armor * _scaleArmor;

                    if (total > _bestScore && hit >= 100)
                    {

Чего не хватает в этом фрагменте, так это того, что вы должны собрать наилучшие результаты всех потоков, а затем сравнить их, чтобы найти свой фактический лучший набор.

Я надеюсь, что это дает некоторые подсказки.

Привет

0 голосов
/ 07 августа 2009

Вы, вероятно, обнаружите, что добавление нескольких потоков на самом деле замедлит процесс. Этот расчет очень медленный? Каковы ваши ожидаемые средние показатели для головы, груди, ног и ступней?

Возможно, вам лучше найти самые высокие значения для каждой из головок, сундуков и т. Д. И отбросить остальные, найдя лучшую комбинацию этих выбранных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...