2011-09-09 4 views
1

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

Как таковой я взаимодействую через дерево, а затем из конструктора я вызываю метод, который вызывает новую тему, запущенную через ExecutorService. Callable представляемый является:

@Override 
    public Void call() throws Exception { 
     // Get descendants for every node and save it to a list. 
     final ExecutorService executor = 
      Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 
     int index = 0; 
     final Map<Integer, Diff> diffs = mDiffDatabase.getMap(); 
     final int depth = diffs.get(0).getDepth().getNewDepth(); 
     try { 
      boolean first = true; 
      for (final AbsAxis axis = new DescendantAxis(mNewRtx, true); index < diffs.size() 
       && ((diffs.get(index).getDiff() == EDiff.DELETED && depth < diffs.get(index).getDepth() 
        .getOldDepth()) || axis.hasNext());) { 
       if (axis.getTransaction().getNode().getKind() == ENodes.ROOT_KIND) { 
        axis.next(); 
       } else { 
        if (index < diffs.size() && diffs.get(index).getDiff() != EDiff.DELETED) { 
         axis.next(); 
        } 

        final Future<Integer> submittedDescendants = 
         executor.submit(new Descendants(mNewRtx.getRevisionNumber(), mOldRtx 
          .getRevisionNumber(), axis.getTransaction().getNode().getNodeKey(), mDb 
          .getSession(), index, diffs)); 
        final Future<Modification> submittedModifications = 
         executor.submit(new Modifications(mNewRtx.getRevisionNumber(), mOldRtx 
          .getRevisionNumber(), axis.getTransaction().getNode().getNodeKey(), mDb 
          .getSession(), index, diffs)); 
        if (first) { 
         first = false; 
         mMaxDescendantCount = submittedDescendants.get(); 
         // submittedModifications.get(); 
        } 
        mDescendantsQueue.put(submittedDescendants); 
        mModificationQueue.put(submittedModifications); 
        index++; 
       } 
      } 

      mNewRtx.close(); 
     } catch (final AbsTTException e) { 
      LOGWRAPPER.error(e.getMessage(), e); 
     } 
     executor.shutdown(); 
     return null; 
    } 

Поэтому для каждого узла он создает новый отзывной, который пересекает дерево для каждого узла и подсчитывает потомков и модификаций (я на самом деле слияния двух деревьев-пересмотров вместе). Ну, mDescendantsQueue и mModificationQueue - BlockingQueues. Сначала я имел только потомкиQueue и снова пересекал дерево, чтобы получить модификации каждого узла (подсчет изменений, сделанных в поддереве текущего узла). Тогда я подумал, почему бы не сделать оба параллельно и реализовать конвейерный подход. К сожалению, производительность, казалось, уменьшалась каждый раз, когда я реализовал другой многопоточный «шаг».

Может быть, потому что XML-дерево, как правило, не так глубоко и параллелизм-Накладные слишком тяжелы: -/

Сначала я сделал все, что последовательное, который был самым быстрым: - пересекающее деревом - для каждого узла пересекаются потомки и вычисляются потомкиCount и modifyCount

После использования конвейерного подхода с BlockingQueues кажется, что производительность уменьшилась, но я на самом деле не делал никаких временных мер, и мне пришлось бы вернуть много изменений, чтобы вернуться назад :(Может быть, производительность увеличивается с увеличением количества процессоров, потому что сейчас у меня есть только Core2Duo для тестирования.

наилучшие пожелания,
Johannes

+0

Для чего нужен mNewRtx? Если это то, что либо не поддерживает параллелизм, либо использует синхронизацию для обработки, это, безусловно, повредит. Можете ли вы сказать, используются ли ваши два ядра все время? –

+0

Это транзакция чтения, которая может проходить через дерево. Некоторые методы синхронизированы, но я их не использую. Я думаю, что 2 ядра необходимо использовать, потому что фрагмент кода, который я опубликовал, выполняется в другом потоке (и он порождает больше потоков для каждого узла, но максимум 2 потока из-за моего ноутбука). Я думаю, что для создания множества потоков может возникнуть проблема. Но, не считая того, в будущем это может окупиться и масштабироваться с большим количеством ядер. Вот почему я сделал это в первую очередь. – Johannes

+0

Это * не * нерестится много потоков, потому что вы используете фиксированный пул потоков. Я уверен, что он использует оба ядра - но вы должны проверить, как * полностью * он их использует. –

ответ

0

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

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

+0

Просто подумал об этом так же, возможно, мне нужно избавиться от внутреннего 'Future'-Creation и просто вызвать два внутренних класса без« ExecutorService »и сохранить результаты в' BlockingQueue', как вы думаете? Значение Я просто хочу сделать второй обход параллельно первому, которому нужны определенные значения узлов, которые должны вычисляться «на лету». – Johannes

+0

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

+0

Дополнение к моему утверждению выше - вы можете начать видеть увеличение скорости обработки, если вы разбиваете дерево на поддеревья. Напр. обработайте половину дерева в одном потоке, вторую половину во втором потоке, затем присоедините их результаты до ходьбы. По моему мнению, такой подход будет лучше масштабироваться с дополнительными ядрами. – mcfinnigan

1

Возможно, это должно помочь: Amadahl's law, что в основном говорит о том, что увеличение производительности зависит (обратно пропорционально) от процента кода, который должен обрабатываться посредством синхронизации. Следовательно, даже увеличиваясь за счет увеличения количества вычислительных ресурсов, это не приведет к лучшему результату. В идеале, если отношение (синхронизированная часть к общей части) невелико, то с (числом процессоров +1) следует давать лучший результат (если вы не используете сеть или другой ввод-вывод, в этом случае вы можете увеличить размер бассейна). Итак, просто следуйте приведенным выше ссылкам и посмотрите, помогает ли это