Подтвердить что ты не робот

Scala Фьючерсы медленны со многими ядрами

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

Задача в деталях

Задача состояла в том, чтобы создать произвольные логические формулы k-CNF с n различными переменными, случайным образом распределенными по m-предложениям, а затем посмотреть, как с помощью комбинации m/n вероятность того, что формула разрешима, падает на 50% для разных случайных распределений. Для этого я применил вероятностный алгоритм k-SAT, генератор предложения и некоторый другой код. Ядро - это функция, которая принимает n и m хорошо, как функция генератора, выполняет 100 фьючерсов и ждет результата. Функция выглядит следующим образом:

Код, о котором идет речь

def avgNonvalidClauses(n: Int, m: Int)(implicit clauseGenerator: ClauseGenerator) = {

    val startTime = System.nanoTime

    /** how man iteration to build the average **/
    val TRIES = 100

    // do TRIES iterations in parallel 
    val tasks = for (i <- 0 until TRIES) yield future[Option[Config]] {
        val clause = clauseGenerator(m, n)
        val solution = CNFSolver.probKSat(clause)
        solution
    }

    /* wait for all threads to finish and collect the results. we will only wait
     * at most TRIES * 100ms (note: flatten filters out all
     * None's) */
    val results = awaitAll(100 * TRIES, tasks: _*).asInstanceOf[List[Option[Option[Config]]]].flatten

    val millis = Duration(System.nanoTime - startTime, NANOSECONDS).toMillis
    val avg = (results count (_.isDefined)) /  results.length.toFloat

    println(s"n=$n, m=$m => $avg ($millis ms)")

    avg
  }

Проблема

На моей локальной машине я получаю эти результаты

[info] Running Main 
n=20, m=120 => 0.0 (8885 ms)
n=21, m=121 => 0.0 (9115 ms)
n=22, m=122 => 0.0 (8724 ms)
n=23, m=123 => 0.0 (8433 ms)
n=24, m=124 => 0.0 (8544 ms)
n=25, m=125 => 0.0 (8858 ms)
[success] Total time: 53 s, completed Jan 9, 2013 8:21:30 PM

На 64-ядерном сервере я получаю:

[info] Running Main 
n=20, m=120 => 0.0 (43200 ms)
n=21, m=121 => 0.0 (38826 ms)
n=22, m=122 => 0.0 (38728 ms)
n=23, m=123 => 0.0 (32737 ms)
n=24, m=124 => 0.0 (41196 ms)
n=25, m=125 => 0.0 (42323 ms)
[success] Total time: 245 s, completed 09.01.2013 20:28:22

Тем не менее, я полностью загружаю обе машины (средний сервер на нагрузке от 60 до 65), поэтому есть достаточное количество потоков. Почему это? Я делаю что-то совершенно неправильное?

На моей локальной машине установлен процессор AMD Phenom (tm) II X4 955 ", на котором используется процессор AMD Opteron (TM) 6272". Локальный процессор имеет 6800 богомипсов, серверов 4200. Таким образом, хотя локальный процессор на 1/3 быстрее, на сервере в 12 раз больше.

Дополнительные

Если у меня есть урезанный пример моего кода, толкаемого в github, чтобы вы могли попробовать его, если вы заинтересованы: https://github.com/Blattlaus/algodemo (It проект sbt с использованием Scala 2.10).

Обновление

  • Я уничтожил любую случайность, затравив генераторы случайных чисел на 42. Это ничего не меняет.
  • Я изменил набор тестов. Теперь результаты еще более изумительны (сервер в 5 раз медленнее!) Примечание: все выходы для среднего процента не разрешимых предложений - это из-за ввода. Это нормально и ожидается.
  • Добавлена ​​информация о процессорах
  • Я заметил, что вызовы Random.nextInt() на 10 раз меньше, чем на сервере. Я завернул все вызовы в помощнике, который измеряет время выполнения отпечатков на консоли, если они медленнее, чем 10 мс. На моей локальной машине я получаю несколько, и обычно это паузы 10-20 мс. На сервере я получаю много звонков с мурами, и они, как правило, выше 100 мс. Может ли это быть проблемой?
4b9b3361

Ответ 1

Вы уже поняли ответ, что проблема заключается в Random.nextInt(), который использует AtomicLong(). Если к ним часто обращаются из разных потоков, вы получите переполнение кэша, что будет хуже на вашем 64-ядерном компьютере, потому что кеши будут дальше друг от друга (электрически), и, следовательно, потребуется больше времени, чтобы получить необходимые блокировки строки кэша.

См. этот ответ stackoverflow для получения более подробной информации и решение о том, как избежать этой проблемы (которая в основном используется для создания генератора случайных чисел в потоке): Конфликт при одновременном использовании java.util.Random

Ответ 2

Операции с денормализованными плавающими числами могут занимать на порядок больше архитектуры x86. См:

Почему изменение 0.1f to 0 замедляет производительность на 10x?

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