Я знаю, что искусственные тесты являются злыми. Они могут показывать результаты только для очень узкой ситуации. Я не предполагаю, что один язык лучше другого из-за какой-то глупой скамьи. Однако мне интересно, почему результаты настолько разные. Пожалуйста, см. Мои вопросы внизу.
Бенчмарк - это простые математические вычисления, чтобы найти пары простых чисел, которые отличаются на 6 (так называемые сексуальные штрихи)
Например. сексуальные простые числа ниже 100 будут: (5 11) (7 13) (11 17) (13 19) (17 23) (23 29) (31 37) (37 43) (41 47) (47 53) (53 59) (61 67) (67 73) (73 79) (83 89) (97 103)
В таблице: время вычисления в секундах Запуск: все, кроме Factor, выполнялось в VirtualBox (нестабильный гостевой компьютер Debian amd64, хост для Windows 7 x64) Процессор: AMD A4-3305M
Sexy primes up to: 10k 20k 30k 100k
Bash 58.00 200.00 [*1] [*1]
C 0.20 0.65 1.42 15.00
Clojure1.4 4.12 8.32 16.00 137.93
Clojure1.4 (optimized) 0.95 1.82 2.30 16.00
Factor n/a n/a 15.00 180.00
Python2.7 1.49 5.20 11.00 119
Ruby1.8 5.10 18.32 40.48 377.00
Ruby1.9.3 1.36 5.73 10.48 106.00
Scala2.9.2 0.93 1.41 2.73 20.84
Scala2.9.2 (optimized) 0.32 0.79 1.46 12.01
[* 1] - Я боюсь представить, сколько времени займет это
С
int isprime(int x) {
int i;
for (i = 2; i < x; ++i)
if (x%i == 0) return 0;
return 1;
}
void findprimes(int m) {
int i;
for ( i = 11; i < m; ++i)
if (isprime(i) && isprime(i-6))
printf("%d %d\n", i-6, i);
}
main() {
findprimes(10*1000);
}
Ruby:
def is_prime?(n)
(2...n).all?{|m| n%m != 0 }
end
def sexy_primes(x)
(9..x).map do |i|
[i-6, i]
end.select do |j|
j.all?{|j| is_prime? j}
end
end
a = Time.now
p sexy_primes(10*1000)
b = Time.now
puts "#{(b-a)*1000} mils"
Scala:
def isPrime(n: Int) =
(2 until n) forall { n % _ != 0 }
def sexyPrimes(n: Int) =
(11 to n) map { i => List(i-6, i) } filter { _ forall(isPrime(_)) }
val a = System.currentTimeMillis()
println(sexyPrimes(100*1000))
val b = System.currentTimeMillis()
println((b-a).toString + " mils")
Scala opimized isPrime
(та же идея, что и в оптимизации Clojure):
import scala.annotation.tailrec
@tailrec // Not required, but will warn if optimization doesn't work
def isPrime(n: Int, i: Int = 2): Boolean =
if (i == n) true
else if (n % i != 0) isPrime(n, i + 1)
else false
Clojure:
(defn is-prime? [n]
(every? #(> (mod n %) 0)
(range 2 n)))
(defn sexy-primes [m]
(for [x (range 11 (inc m))
:let [z (list (- x 6) x)]
:when (every? #(is-prime? %) z)]
z))
(let [a (System/currentTimeMillis)]
(println (sexy-primes (* 10 1000)))
(let [b (System/currentTimeMillis)]
(println (- b a) "mils")))
Clojure оптимизирован is-prime?
:
(defn ^:static is-prime? [^long n]
(loop [i (long 2)]
(if (= (rem n i) 0)
false
(if (>= (inc i) n) true (recur (inc i))))))
Python
import time as time_
def is_prime(n):
return all((n%j > 0) for j in xrange(2, n))
def primes_below(x):
return [[j-6, j] for j in xrange(9, x+1) if is_prime(j) and is_prime(j-6)]
a = int(round(time_.time() * 1000))
print(primes_below(10*1000))
b = int(round(time_.time() * 1000))
print(str((b-a)) + " mils")
Фактор
MEMO:: prime? ( n -- ? )
n 1 - 2 [a,b] [ n swap mod 0 > ] all? ;
MEMO: sexyprimes ( n n -- r r )
[a,b] [ prime? ] filter [ 6 + ] map [ prime? ] filter dup [ 6 - ] map ;
5 10 1000 * sexyprimes . .
Bash (ЗШ):
#!/usr/bin/zsh
function prime {
for (( i = 2; i < $1; i++ )); do
if [[ $[$1%i] == 0 ]]; then
echo 1
exit
fi
done
echo 0
}
function sexy-primes {
for (( i = 9; i <= $1; i++ )); do
j=$[i-6]
if [[ $(prime $i) == 0 && $(prime $j) == 0 ]]; then
echo $j $i
fi
done
}
sexy-primes 10000
Грубые ответы:
(2...n).all?
в функции is-prime?
, скорее всего, будет хорошо оптимизирован в Ruby (EDIT: похоже, что это действительно так, см. Джулиан отвечает за более подробно...)Самая важная оптимизация в коде Clojure заключалась бы в использовании типизированных математических примитивов в is-prime?
, например:
(set! *unchecked-math* true) ;; at top of file to avoid using BigIntegers
(defn ^:static is-prime? [^long n]
(loop [i (long 2)]
(if (zero? (mod n i))
false
(if (>= (inc i) n) true (recur (inc i))))))
С этим улучшением я получаю Clojure завершение 10k за 0,635 секунд (т.е. второй самый быстрый в вашем списке, избивающий Scala)
PS обратите внимание, что у вас есть код печати внутри вашего теста в некоторых случаях - не очень хорошая идея, так как искажает результаты, особенно если использовать функцию типа print
в первый раз вызывает инициализацию подсистем IO или что-то в этом роде!
is-prime?
показывает улучшение в 2 раза. ;)
Я отвечу только на # 2, так как это единственное, что у меня есть что-то разумное, чтобы сказать, но для вашего кода на Python вы создаете промежуточный список в is_prime
, тогда как вы используете .map
в вашем all
в Ruby, который просто повторяется.
Если вы измените свой is_prime
на:
def is_prime(n):
return all((n%j > 0) for j in range(2, n))
они находятся на уровне.
Я мог бы оптимизировать Python дальше, но мой Ruby недостаточно хорош, чтобы знать, когда я дал больше преимуществ (например, использование xrange
заставляет Python побеждать на моей машине, но я не помню, если используемый вами диапазон Ruby создает весь диапазон в памяти или нет).
РЕДАКТИРОВАТЬ: Не слишком глупо, делая код Python похожим:
import time
def is_prime(n):
return all(n % j for j in xrange(2, n))
def primes_below(x):
return [(j-6, j) for j in xrange(9, x + 1) if is_prime(j) and is_prime(j-6)]
a = int(round(time.time() * 1000))
print(primes_below(10*1000))
b = int(round(time.time() * 1000))
print(str((b-a)) + " mils")
который не меняется намного больше, ставит его на 1,5 секунды для меня и, будучи лишним глупым, запускает его с PyPy, ставит его на 0,3 с для 10K и 21s для 100K.
False
(хороший улов).
Вот быстрая версия Clojure, используя те же основные алгоритмы:
(set! *unchecked-math* true)
(defn is-prime? [^long n]
(loop [i 2]
(if (zero? (unchecked-remainder-int n i))
false
(if (>= (inc i) n)
true
(recur (inc i))))))
(defn sexy-primes [m]
(for [x (range 11 (inc m))
:when (and (is-prime? x) (is-prime? (- x 6)))]
[(- x 6) x]))
Он работает примерно на 20 раз быстрее, чем ваш оригинал на моей машине. И здесь версия, которая использует новую библиотеку редукторов в 1.5 (требует Java 7 или JSR 166):
(require '[clojure.core.reducers :as r]) ;'
(defn sexy-primes [m]
(->> (vec (range 11 (inc m)))
(r/filter #(and (is-prime? %) (is-prime? (- % 6))))
(r/map #(list (- % 6) %))
(r/fold (fn ([] []) ([a b] (into a b))) conj)))
Это работает примерно в 40 раз быстрее, чем ваш оригинал. На моей машине это 100k за 1,5 секунды.
unchecked-remainder-int
или просто rem
вместо mod
вместе со статической типизацией приводит к увеличению производительности в 4 раза. Ницца!
Вы можете сделать Scala намного быстрее, изменив метод isPrime
на
def isPrime(n: Int, i: Int = 2): Boolean =
if (i == n) true
else if (n % i != 0) isPrime(n, i + 1)
else false
Не совсем лаконично, но программа работает в 40% случаев!
Мы вырезаем лишние Range
и анонимные объекты Function
, компилятор Scala распознает хвостовую рекурсию и превращает ее в цикл while, который JVM может превратить в более или менее оптимальный машинный код, поэтому он не должен быть слишком далеко от версии C.
i == n || n % i != 0 && isPrime(n, i + 1)
, который короче, хотя и немного сложнее для чтения
Вот моя версия scala как параллельная, так и не параллельная, просто для удовольствия: (В моем двухъядерном вычислении параллельная версия занимает 335 мс, а непараллельная версия занимает 655 мс).
object SexyPrimes {
def isPrime(n: Int): Boolean =
(2 to math.sqrt(n).toInt).forall{ n%_ != 0 }
def isSexyPrime(n: Int): Boolean = isPrime(n) && isPrime(n-6)
def findPrimesPar(n: Int) {
for(k <- (11 to n).par)
if(isSexyPrime(k)) printf("%d %d\n",k-6,k)
}
def findPrimes(n: Int) {
for(k <- 11 to n)
if(isSexyPrime(k)) printf("%d %d\n",k-6,k)
}
def timeOf(call : =>Unit) {
val start = System.currentTimeMillis
call
val end = System.currentTimeMillis
println((end-start)+" mils")
}
def main(args: Array[String]) {
timeOf(findPrimes(100*1000))
println("------------------------")
timeOf(findPrimesPar(100*1000))
}
}
EDIT: Согласно предложению Эмиля Х, я изменил свой код, чтобы избежать эффектов разогрева IO и jvm:
Результат показан в моем вычислении:
Список (3432, 1934, 3261, 1716, 3229, 1654, 3214, 1700)
object SexyPrimes {
def isPrime(n: Int): Boolean =
(2 to math.sqrt(n).toInt).forall{ n%_ != 0 }
def isSexyPrime(n: Int): Boolean = isPrime(n) && isPrime(n-6)
def findPrimesPar(n: Int) {
for(k <- (11 to n).par)
if(isSexyPrime(k)) ()//printf("%d %d\n",k-6,k)
}
def findPrimes(n: Int) {
for(k <- 11 to n)
if(isSexyPrime(k)) ()//printf("%d %d\n",k-6,k)
}
def timeOf(call : =>Unit): Long = {
val start = System.currentTimeMillis
call
val end = System.currentTimeMillis
end - start
}
def main(args: Array[String]) {
val xs = timeOf(findPrimes(1000*1000))::timeOf(findPrimesPar(1000*1000))::
timeOf(findPrimes(1000*1000))::timeOf(findPrimesPar(1000*1000))::
timeOf(findPrimes(1000*1000))::timeOf(findPrimesPar(1000*1000))::
timeOf(findPrimes(1000*1000))::timeOf(findPrimesPar(1000*1000))::Nil
println(xs)
}
}
isSexyPrime
может быть (более) оптимизирован при вызове из findPrimesPar
и не так сильно при вызове из findPrimes
Не обращайте внимания на контрольные показатели; проблема заинтересовала меня, и я сделал несколько быстрых трюков. Это использует декоратор lru_cache
, который запоминает функцию; поэтому, когда мы называем is_prime(i-6)
, мы в основном получаем эту первичную проверку бесплатно. Это изменение сокращает работу примерно вдвое. Кроме того, мы можем совершать вызовы range()
через только нечетные числа, сокращая работу примерно в два раза.
http://en.wikipedia.org/wiki/Memoization
http://docs.python.org/dev/library/functools.html
Для этого требуется Python 3.2 или новее получить lru_cache
, но может работать с более старым Python, если вы установите рецепт Python, который предоставляет lru_cache
. Если вы используете Python 2.x, вы должны использовать xrange()
вместо range()
.
http://code.activestate.com/recipes/577479-simple-caching-decorator/
from functools import lru_cache
import time as time_
@lru_cache()
def is_prime(n):
return n%2 and all(n%i for i in range(3, n, 2))
def primes_below(x):
return [(i-6, i) for i in range(9, x+1, 2) if is_prime(i) and is_prime(i-6)]
correct100 = [(5, 11), (7, 13), (11, 17), (13, 19), (17, 23), (23, 29),
(31, 37), (37, 43), (41, 47), (47, 53), (53, 59), (61, 67), (67, 73),
(73, 79), (83, 89)]
assert(primes_below(100) == correct100)
a = time_.time()
print(primes_below(30*1000))
b = time_.time()
elapsed = b - a
print("{} msec".format(round(elapsed * 1000)))
Вышеизложенное заняло очень мало времени для редактирования. Я решил сделать это еще на один шаг и сделать тест на простые числа, только попробуйте простые делители и только до квадратного корня от тестируемого числа. То, как я это делал, работает только в том случае, если вы проверяете цифры в порядке, чтобы он мог накапливать все простые числа; но эта проблема уже проверяла номера в порядке, чтобы это было хорошо.
На моем ноутбуке (ничего особенного, процессор - 1,5 ГГц AMD Turion II "K625" ), эта версия произвела ответ на 100K менее чем за 8 секунд.
from functools import lru_cache
import math
import time as time_
known_primes = set([2, 3, 5, 7])
@lru_cache(maxsize=128)
def is_prime(n):
last = math.ceil(math.sqrt(n))
flag = n%2 and all(n%x for x in known_primes if x <= last)
if flag:
known_primes.add(n)
return flag
def primes_below(x):
return [(i-6, i) for i in range(9, x+1, 2) if is_prime(i) and is_prime(i-6)]
correct100 = [(5, 11), (7, 13), (11, 17), (13, 19), (17, 23), (23, 29),
(31, 37), (37, 43), (41, 47), (47, 53), (53, 59), (61, 67), (67, 73),
(73, 79), (83, 89)]
assert(primes_below(100) == correct100)
a = time_.time()
print(primes_below(100*1000))
b = time_.time()
elapsed = b - a
print("{} msec".format(round(elapsed * 1000)))
Вышеприведенный код довольно легко писать в Python, Ruby и т.д., но будет больнее в C.
Вы не можете сравнивать номера в этой версии с номерами других версий, не переписывая другие, чтобы использовать аналогичные трюки. Я ничего здесь не пытаюсь доказать; Я просто подумал, что проблема была забавной, и я хотел посмотреть, какие легкие улучшения производительности я мог бы почерпнуть.
lru_cache
определенно lru_cache
. Для определенных классов задач, таких как генерация последовательных чисел Фибоначчи, это может значительно ускорить процесс, просто добавив в функцию один декоратор строк! Вот ссылка на доклад Рэймонда Хеттингера, который охватывает lru_cache
около 26 минут. Blip.tv/pycon-us-videos-2009-2010-2011/…
Не забудьте Фортран! (В основном шутит, но я бы ожидал, что сработает с C). Заявления с восклицательными знаками являются необязательными, но хорошими. (!
является символом комментария в fortran 90)
logical function isprime(n)
IMPLICIT NONE !
integer :: n,i
do i=2,n
if(mod(n,i).eq.0)) return .false.
enddo
return .true.
end
subroutine findprimes(m)
IMPLICIT NONE !
integer :: m,i
logical, external :: isprime
do i=11,m
if(isprime(i) .and. isprime(i-6))then
write(*,*) i-6,i
endif
enddo
end
program main
findprimes(10*1000)
end
Я не мог устоять перед некоторыми из наиболее очевидных оптимизаций для версии C, в результате которой тест 100k теперь принимает 0,3 секунды на моей машине (в 5 раз быстрее, чем версия C в вопросе, оба скомпилированы с MSVC 2010/Ox).
int isprime( int x )
{
int i, n;
for( i = 3, n = x >> 1; i <= n; i += 2 )
if( x % i == 0 )
return 0;
return 1;
}
void findprimes( int m )
{
int i, s = 3; // s is bitmask of primes in last 3 odd numbers
for( i = 11; i < m; i += 2, s >>= 1 ) {
if( isprime( i ) ) {
if( s & 1 )
printf( "%d %d\n", i - 6, i );
s |= 1 << 3;
}
}
}
main() {
findprimes( 10 * 1000 );
}
Вот идентичная реализация в Java:
public class prime
{
private static boolean isprime( final int x )
{
for( int i = 3, n = x >> 1; i <= n; i += 2 )
if( x % i == 0 )
return false;
return true;
}
private static void findprimes( final int m )
{
int s = 3; // s is bitmask of primes in last 3 odd numbers
for( int i = 11; i < m; i += 2, s >>= 1 ) {
if( isprime( i ) ) {
if( ( s & 1 ) != 0 )
print( i );
s |= 1 << 3;
}
}
}
private static void print( int i )
{
System.out.println( ( i - 6 ) + " " + i );
}
public static void main( String[] args )
{
// findprimes( 300 * 1000 ); // for some JIT training
long time = System.nanoTime();
findprimes( 10 * 1000 );
time = System.nanoTime() - time;
System.err.println( "time: " + ( time / 10000 ) / 100.0 + "ms" );
}
}
С Java 1.7.0_04 это выполняется почти так же быстро, как версия C. Клиентская или серверная VM не показывают большой разницы, за исключением того, что обучение JIT, по-видимому, помогает серверной VM немного (~ 3%), хотя она практически не влияет на клиентскую VM. Вывод в Java кажется медленнее, чем на C. Если в обеих версиях выход заменяется статическим счетчиком, версия Java работает немного быстрее, чем версия C.
Это мои времена для запуска 100k:
и 1M run (результаты 16386):
Хотя это не отвечает на ваши вопросы, это показывает, что небольшие настройки могут иметь заметное влияние на производительность. Поэтому, чтобы иметь возможность действительно сравнивать языки, вы должны стараться избегать всех алгоритмических различий как можно больше.
Он также дает подсказку, почему Scala выглядит довольно быстро. Он работает на виртуальной машине Java и, таким образом, выигрывает от впечатляющей производительности.
В Scala попробуйте использовать Tuple2 вместо List, он должен идти быстрее. Просто удалите слово "List", так как (x, y) является Tuple2.
Tuple2 специализируется на Int, Long и Double, что означает, что ему не нужно вставлять/распаковывать эти исходные типы данных. Источник Tuple2. Список не специализируется. Источник списка.
forall
. Я также подумал, что это может быть не самый эффективный код (в большей степени потому, что большая строгая коллекция создается для больших n
а не просто с использованием представления), но он, безусловно, короткий + элегантный, и я был удивлен, насколько хорошо он работал, несмотря на использование много функционального стиля.
На основе x4u answer я написал версию scala, используя рекурсию, и я улучшил ее, только перейдя к sqrt вместо x/2 для функция первичной проверки. Я получаю ~ 250 мс за 100 КБ и ~ 600 мс за 1М. Я пошел вперед и пошел к 10M в ~ 6s.
import scala.annotation.tailrec
var count = 0;
def print(i:Int) = {
println((i - 6) + " " + i)
count += 1
}
@tailrec def isPrime(n:Int, i:Int = 3):Boolean = {
if(n % i == 0) return false;
else if(i * i > n) return true;
else isPrime(n = n, i = i + 2)
}
@tailrec def findPrimes(max:Int, bitMask:Int = 3, i:Int = 11):Unit = {
if (isPrime(i)) {
if((bitMask & 1) != 0) print(i)
if(i + 2 < max) findPrimes(max = max, bitMask = (bitMask | (1 << 3)) >> 1, i = i + 2)
} else if(i + 2 < max) {
findPrimes(max = max, bitMask = bitMask >> 1, i = i + 2)
}
}
val a = System.currentTimeMillis()
findPrimes(max=10000000)
println(count)
val b = System.currentTimeMillis()
println((b - a).toString + " mils")
Я также вернулся и написал версию CoffeeScript (V8 JavaScript), которая получает ~ 15 мс для 100 тыс., 250 мс для 1М и 6 с для 10М, используя счетчик (игнорируя ввод-вывод). Если я включаю вывод, то он принимает ~ 150 мс для 100 кБ, 1 с для 1М и 12 с для 10М. Невозможно использовать хвостовую рекурсию здесь, к сожалению, поэтому мне пришлось преобразовать ее обратно в циклы.
count = 0;
print = (i) ->
console.log("#{i - 6} #{i}")
count += 1
return
isPrime = (n) ->
i = 3
while i * i < n
if n % i == 0
return false
i += 2
return true
findPrimes = (max) ->
bitMask = 3
for i in [11..max] by 2
prime = isPrime(i)
if prime
if (bitMask & 1) != 0
print(i)
bitMask |= (1 << 3)
bitMask >>= 1
return
a = new Date()
findPrimes(1000000)
console.log(count)
b = new Date()
console.log((b - a) + " ms")
Просто для удовольствия, вот параллельная версия Ruby.
require 'benchmark'
num = ARGV[0].to_i
def is_prime?(n)
(2...n).all?{|m| n%m != 0 }
end
def sexy_primes_default(x)
(9..x).map do |i|
[i-6, i]
end.select do |j|
j.all?{|j| is_prime? j}
end
end
def sexy_primes_threads(x)
partition = (9..x).map do |i|
[i-6, i]
end.group_by do |x|
x[0].to_s[-1]
end
threads = Array.new
partition.each_key do |k|
threads << Thread.new do
partition[k].select do |j|
j.all?{|j| is_prime? j}
end
end
end
threads.each {|t| t.join}
threads.map{|t| t.value}.reject{|x| x.empty?}
end
puts "Running up to num #{num}"
Benchmark.bm(10) do |x|
x.report("default") {a = sexy_primes_default(num)}
x.report("threads") {a = sexy_primes_threads(num)}
end
На моем 1.8GHz Core i5 MacBook Air результаты работы:
# Ruby 1.9.3
$ ./sexyprimes.rb 100000
Running up to num 100000
user system total real
default 68.840000 0.060000 68.900000 ( 68.922703)
threads 71.730000 0.090000 71.820000 ( 71.847346)
# JRuby 1.6.7.2 on JVM 1.7.0_05
$ jruby --1.9 --server sexyprimes.rb 100000
Running up to num 100000
user system total real
default 56.709000 0.000000 56.709000 ( 56.708000)
threads 36.396000 0.000000 36.396000 ( 36.396000)
# JRuby 1.7.0.preview1 on JVM 1.7.0_05
$ jruby --server sexyprimes.rb 100000
Running up to num 100000
user system total real
default 52.640000 0.270000 52.910000 ( 51.393000)
threads 105.700000 0.290000 105.990000 ( 30.298000)
Похоже, JVM JIT дает рубину хорошее повышение производительности в случае по умолчанию, в то время как истинная многопоточность помогает JRuby выполнять на 50% быстрее в поточном корпусе. Что еще более интересно, так это то, что JRuby 1.7 улучшает рейтинг JRuby 1.6 здоровыми 17%!
Здесь код для версии Go (golang.org):
package main
import (
"fmt"
)
func main(){
findprimes(10*1000)
}
func isprime(x int) bool {
for i := 2; i < x; i++ {
if x%i == 0 {
return false
}
}
return true
}
func findprimes(m int){
for i := 11; i < m; i++ {
if isprime(i) && isprime(i-6) {
fmt.Printf("%d %d\n", i-6, i)
}
}
}
Он работал так же быстро, как версия C.
Использование Asus u81a Intel Core 2 Duo T6500 2,1 ГГц, 2 МБ кэш-памяти второго уровня, частота FSB 800 МГц. Оперативная память 4 ГБ
Версия 100k: C: 2.723s
Go: 2.743s
С 1000000 (1M вместо 100K): C: 3m35.458s
Go: 3m36.259s
Но я думаю, что было бы справедливо использовать Go, встроенный в возможности многопоточности, и сравнить эту версию с обычной версией C (без многопоточности), просто потому, что почти легко сделать многопоточность с Go.
Обновление: я сделал параллельную версию, используя Goroutines в Go:
package main
import (
"fmt"
"runtime"
)
func main(){
runtime.GOMAXPROCS(4)
printer := make(chan string)
printer2 := make(chan string)
printer3 := make(chan string)
printer4 := make(chan string)
finished := make(chan int)
var buffer, buffer2, buffer3 string
running := 4
go findprimes(11, 30000, printer, finished)
go findprimes(30001, 60000, printer2, finished)
go findprimes(60001, 85000, printer3, finished)
go findprimes(85001, 100000, printer4, finished)
for {
select {
case i := <-printer:
// batch of sexy primes received from printer channel 1, print them
fmt.Printf(i)
case i := <-printer2:
// sexy prime list received from channel, store it
buffer = i
case i := <-printer3:
// sexy prime list received from channel, store it
buffer2 = i
case i := <-printer4:
// sexy prime list received from channel, store it
buffer3 = i
case <-finished:
running--
if running == 0 {
// all goroutines ended
// dump buffer to stdout
fmt.Printf(buffer)
fmt.Printf(buffer2)
fmt.Printf(buffer3)
return
}
}
}
}
func isprime(x int) bool {
for i := 2; i < x; i++ {
if x%i == 0 {
return false
}
}
return true
}
func findprimes(from int, to int, printer chan string, finished chan int){
str := ""
for i := from; i <= to; i++ {
if isprime(i) && isprime(i-6) {
str = str + fmt.Sprintf("%d %d\n", i-6, i)
}
}
printer <- str
//fmt.Printf("Finished %d to %d\n", from, to)
finished <- 1
}
Распараллеливаемая версия используется в среднем 2,743 секунды, в то же время, что и обычная версия.
Распараллеленная версия завершена за 1,706 секунды. Он использовал менее 1,5 МБ оперативной памяти.
Одна вещь: мой двухъядерный процессор kubuntu 64bit никогда не достигал пика в обоих ядрах. Похоже, что Go использовал только одно ядро. Исправлено с вызовом runtime.GOMAXPROCS(4)
Обновление: я выполнил параллелизированную версию до 1M номеров. Один из моих ядер процессора был на 100% все время, а другой не использовался вообще (нечетно). Это заняло целую минуту больше, чем C и обычные версии Go.: (С >
С 1000000 (1M вместо 100K):
C: 3m35.458s
Go: 3m36.259s
Go using goroutines:
3m27.137s 2m16.125s
Версия 100k:
C: 2.723s
Go: 2.743s
Go using goroutines: 1.706s
Ответ на ваш вопрос № 1 заключается в том, что Да, JVM невероятно быстр и дается статическая типизация.
JVM должен работать быстрее, чем C в конечном итоге, возможно, даже быстрее, чем обычный язык ассемблера. Конечно, вы всегда можете вручную оптимизировать сборку, чтобы бить что угодно, выполняя рутинное профилирование во время выполнения и создавая отдельную версию для каждого процессора, вы просто должны быть удивительно хорошими и узнаваемыми.
Причины для скорости Java:
JVM может анализировать ваш код во время его запуска и вручную оптимизировать его - например, если у вас есть метод, который можно было бы статически проанализировать во время компиляции, чтобы быть истинным, и JVM заметила, что вы часто вызывали его с теми же параметрами, он МОЖЕТ на самом деле полностью отказаться от вызова и просто ввести результаты из последнего вызова (я не уверен, что Java действительно делает это именно так, но он делает много всего, как это).
Из-за статической типизации JVM может много узнать о вашем коде во время компиляции, это позволяет заранее оптимизировать немного вещей. Он также позволяет компилятору оптимизировать каждый класс отдельно, не зная, как другой класс планирует его использовать. Также у Java нет произвольных указателей на расположение памяти, она ЗНАЕТ, какие значения в памяти могут и не могут быть изменены и могут быть оптимизированы соответственно.
Распределение кучи MUCH более эффективно, чем C, распределение кучи Java больше похоже на распределение стека C в скорости - но более универсальное. Много времени прошло в разных альтроитах, используемых здесь, это искусство - например, все объекты с коротким сроком службы (например, переменные стека С) распределяются по "известному" свободному местоположению (без поиска свободного места с достаточным пространством) и все они освобождаются вместе за один шаг (например, стек pop).
JVM может знать причуды о вашей архитектуре процессора и генерировать машинный код специально для данного CPU.
JVM может быстро ускорить ваш код после его отправки. Подобно тому, как перенос программы на новый процессор может ускорить ее, перенос ее в новую версию JVM также может дать вам огромные скорости, которые можно было бы использовать для процессоров, которые даже не существовали, когда вы первоначально скомпилировали свой код, что-то c физически не может делать без рекомендации.
Кстати, большая часть плохой репутации для скорости java исходит из длительного времени запуска для загрузки JVM (когда-нибудь кто-то построит JVM в ОС, и это уйдет!) и тот факт, что многие разработчики действительно плохо при написании кода GUI (особенно в потоковом режиме), который заставил Java GUI часто становиться невосприимчивым и глючным. Простые в использовании языки, такие как Java и VB, имеют свои недостатки, усиленные тем, что возможности среднего программиста имеют тенденцию быть ниже, чем более сложные языки.