2010-09-02 2 views
16

Я бег этого SCALA кода на 32-битной четырехъядерной системе Core2:Почему мои фьючерсы scala более эффективны?

def job(i:Int,s:Int):Long = { 
    val r=(i to 500000000 by s).map(_.toLong).foldLeft(0L)(_+_) 
    println("Job "+i+" done") 
    r 
} 

import scala.actors.Future 
import scala.actors.Futures._ 

val JOBS=4 

val jobs=(0 until JOBS).toList.map(i=>future {job(i,JOBS)}) 
println("Running...") 
val results=jobs.map(f=>f()) 
println(results.foldLeft(0L)(_+_)) 

(Да, я делать знает, что есть много более эффективных способов суммировать ряд целых чисел; это просто дать CPU что-то делать).

В зависимости от того, что я поставил работу, чтобы, код выполняется в следующих случаях:

JOBS=1 : 31.99user 0.84system 0:28.87elapsed 113%CPU 
JOBS=2 : 27.71user 1.12system 0:14.74elapsed 195%CPU 
JOBS=3 : 33.19user 0.39system 0:13.02elapsed 257%CPU 
JOBS=4 : 49.08user 8.46system 0:22.71elapsed 253%CPU 

Я удивлен, что это не очень хорошо масштабируется за 2 фьючерсы «в игре». Я использую многопоточный код на C++ и не сомневаюсь, что получим хорошее масштабирование до 4 ядер и увижу> 390% загрузки процессора, если бы я закодировал подобные вещи с помощью TBB от Intel или boost::threads (это было бы значительно более подробным из курс).

Итак: что происходит и как я могу получить масштабирование до 4 ядер, которые я ожидал увидеть? Является ли это чем-то ограниченным в scala или JVM? Мне кажется, что я не знаю «где» работает фьючерс scala ... это поток, порожденный в будущем, или «Фьючерсы» предоставляют пул потоков, предназначенный для их запуска?

[Я использую SCALA 2.7.7 пакеты из Debian/Squeeze на системе Ленни с солнцем-java6 (6-20-0lennny1).]

Update:

Как было предложено в ответе Рекса я перекодировал, чтобы избежать создания объекта.

def job(i:Long,s:Long):Long = { 
    var t=0L 
    var v=i 
    while (v<=10000000000L) { 
    t+=v 
    v+=s 
    } 
    println("Job "+i+" done") 
    t 
} 
// Rest as above... 

Это было так быстро, что мне пришлось значительно увеличить количество итераций, чтобы работать в течение любого количества времени! Результаты:

JOBS=1: 28.39user 0.06system 0:29.25elapsed 97%CPU 
JOBS=2: 28.46user 0.04system 0:14.95elapsed 190%CPU 
JOBS=3: 24.66user 0.06system 0:10.26elapsed 240%CPU 
JOBS=4: 28.32user 0.12system 0:07.85elapsed 362%CPU 

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

Нажав немного дальше, на четырехъядерном гиперпотоке i7 последняя версия с JOBS=8 достигает ускорения x4.4 против JOBS = 1, при использовании процессора 571%.

+1

Вы просто нетерпеливы, желая будущего сегодня! Серьезно, Рекс ударил ноготь по голове, вы сравниваете сбор мусора, а не эффективность фьючерсов. –

+1

Хех ... слишком верно.Когда я представил этот вопрос, я долгое время не использовал Scala, и, вероятно, был слишком доверчив некоторым из них. – timday

+0

Уход за повторным запуском теста с помощью akka.dispatch.Future? –

ответ

14

Я предполагаю, что сборщик мусора делает больше работы, чем сама добавка. Таким образом, вы ограничены тем, чем может управлять сборщик мусора. Попробуйте снова запустить тест с чем-то, что не создает никаких объектов (например, используйте цикл while вместо диапазона/карты/свернуть). Вы также можете играть с параллельными параметрами GC, если ваше реальное приложение сильно ударит по GC.

+0

Да, похоже, это так; см. вторую версию кода и результаты в вопросе обновления. Первоначально проблема возникла в каком-то кодексе, сильно использующем BigInts, поэтому не так много шансов устранить создание объекта. Не оценил, насколько сильно это влияние может иметь ... Scala, похоже, устраняет необходимость в гораздо более явном обновлении кода, поэтому легко забыть, что он все еще там. – timday

+0

Не должен ли компилятор оптимизировать этот «новый»? –

+2

@ Elazar - В конце концов, со специализацией, возможно, что возможно (или что-то подобное) для запуска без создания объекта. На данный момент это неизбежно: код является общим, поэтому вам нужно создать объект для его работы, даже если это всего лишь оболочка над примитивом. –

2

Попробуйте

(i to 500000000 by s).view.map(_.toLong).foldLeft(0L)(_+_) 

Применения view предполагается (как я понял идентификатор), чтобы избежать повторной итерации и создание объекта, предоставляя простые обертки.

Обратите внимание, что вы можете использовать reduceLeft(_+_) вместо складки.

+0

Я все еще на 2.7.7; view не входит в Range для меня (надеюсь, что 2.8 появится в архиве Debian в один прекрасный день, я ленив строить из источника, а все книги Scala, которые у меня есть, равны 2,7). Рад видеть, что в этой области есть некоторые улучшения. (И да, я обычно предпочитаю сворачивать, если не будет веской причины, в этом случае это было потому, что нет никакой гарантии, что некоторые экстремальные параметры i/s для функции не приведут к одному или нулевому элементу для операции сокращения). – timday

+0

Да, я тоже это заметил. Вы можете загрузить Scala 2.8.1 в tgz, который вам просто нужно распаковать в любом месте (например, '/ usr/share /'). Затем создайте символические ссылки на скрипты в подпапке 'bin', а вы - золото. Мне действительно было бы интересно, как три варианта выполняются на 2.8.1 на вашем компьютере (у меня нет четырехъядерного ядра). – Raphael

Смежные вопросы