การตีความเกณฑ์มาตรฐานใน C, Clojure, Python, Ruby, Scala และอื่น ๆ [ปิด]


91

ข้อจำกัดความรับผิดชอบ

ฉันรู้ว่าเกณฑ์มาตรฐานเทียมนั้นชั่วร้าย พวกเขาสามารถแสดงผลลัพธ์สำหรับสถานการณ์แคบ ๆ ที่เฉพาะเจาะจงเท่านั้น ฉันไม่คิดว่าภาษาหนึ่งดีกว่าภาษาอื่นเพราะม้านั่งโง่ ๆ อย่างไรก็ตามฉันสงสัยว่าทำไมผลลัพธ์จึงแตกต่างกันมาก โปรดดูคำถามของฉันที่ด้านล่าง

คำอธิบายเกณฑ์มาตรฐานคณิตศาสตร์

Benchmark คือการคำนวณทางคณิตศาสตร์อย่างง่ายเพื่อหาคู่ของจำนวนเฉพาะที่แตกต่างกันด้วย 6 (เรียกว่าsexy primes ) เช่น sexy primes ต่ำกว่า 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 unstable amd64 guest, Windows 7 x64 host) CPU: 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);
}

ทับทิม:

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"

สกาล่า:

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 . .

ทุบตี (zsh):

#!/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

คำถาม

  1. ทำไมสกาล่าเร็วจัง เป็นเพราะการพิมพ์แบบคงที่หรือไม่? หรือแค่ใช้ JVM อย่างมีประสิทธิภาพ?
  2. ทำไม Ruby และ Python จึงแตกต่างกันมาก? ฉันคิดว่าทั้งสองไม่แตกต่างกันโดยสิ้นเชิง บางทีรหัสของฉันอาจผิด โปรดสอนฉัน! ขอบคุณ. UPDใช่นั่นเป็นข้อผิดพลาดในรหัสของฉัน Python และ Ruby 1.9 นั้นค่อนข้างเท่าเทียมกัน
  3. การเพิ่มผลผลิตที่น่าประทับใจระหว่างรุ่น Ruby
  4. ฉันสามารถเพิ่มประสิทธิภาพโค้ด Clojure โดยเพิ่มการประกาศประเภทได้หรือไม่? จะช่วยได้หรือไม่

6
@mgilson ขึ้นอยู่กับsqrt(n)แต่อาจต้องใช้เวลาในการคำนวณ นอกจากนี้รหัส C ของคุณจะพิมพ์ค่าราคาตามที่พบในขณะที่ภาษาอื่น ๆ ของคุณจะคำนวณเป็นรายการแล้วพิมพ์ออกมา แม้ว่า C จะเร็วที่สุดอย่างไม่น่าแปลกใจ แต่คุณอาจจะได้รับเร็วกว่า
Russ

2
(และแน่นอนว่าSieve of Eratosthenes .. แต่เกณฑ์มาตรฐานขนาดเล็กนี้ค่อนข้างเป็นการทดสอบความเครียดของการวนซ้ำและการดำเนินการทางคณิตศาสตร์อย่างไรก็ตามพวกเขายังไม่ "ยุติธรรม" เนื่องจากบางคนขี้เกียจมากกว่า)

2
ฉันเพิ่งใช้ทั้งเวอร์ชัน Go และเวอร์ชัน C ของคุณ (ซึ่งดูคล้ายกันมาก) และฉันก็มีความเร็วเท่ากันทั้งสองอย่าง ฉันลองรุ่น 100k เท่านั้น: C: 2.723s Go: 2.743s.
Sebastián Grignoli

3
คุณไม่จำเป็นต้องคำนวณsqrtเพื่อตรวจสอบนี้ คุณสามารถคำนวณกำลังสองของias infor (i = 2; i * i <= x; ++i) ...
ivant

3
ผมแนะนำให้คุณบันทึกย่อ Scala เหมาะisPrimeกับ@tailrecเพื่อให้แน่ใจว่าคุณกำลังใช้ recursion หาง เป็นเรื่องง่ายที่จะทำสิ่งที่ป้องกันการเรียกซ้ำของหางผิดพลาดและคำอธิบายประกอบนี้ควรเตือนคุณหากเกิดเหตุการณ์เช่นนั้น
Daniel C. Sobral

คำตอบ:


30

คำตอบคร่าวๆ:

  1. การพิมพ์แบบคงที่ของ Scala ช่วยได้เล็กน้อยที่นี่ซึ่งหมายความว่ามันใช้ JVM ได้อย่างมีประสิทธิภาพโดยไม่ต้องใช้ความพยายามมากเกินไป
  2. ฉันไม่แน่ใจในความแตกต่างของ Ruby / Python แต่ฉันสงสัยว่า(2...n).all?ในฟังก์ชั่นis-prime?นั้นน่าจะปรับให้เหมาะสมกับ Ruby ได้ดี (แก้ไข: ดูเหมือนจะเป็นเช่นนั้นจริงๆดูคำตอบของ Julian สำหรับรายละเอียดเพิ่มเติม ... )
  3. Ruby 1.9.3 ได้รับการปรับให้เหมาะสมกว่ามาก
  4. โค้ด Clojure สามารถเร่งได้มาก! ในขณะที่ Clojure เป็นแบบไดนามิกโดยค่าเริ่มต้นคุณสามารถใช้คำแนะนำประเภทคณิตศาสตร์ดั้งเดิม ฯลฯ เพื่อเข้าใกล้ความเร็ว Scala / Java บริสุทธิ์ในหลาย ๆ กรณีเมื่อคุณต้องการ

การเพิ่มประสิทธิภาพที่สำคัญที่สุดในรหัส 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)

ปล.โปรดทราบว่าคุณมีรหัสการพิมพ์อยู่ในเกณฑ์มาตรฐานของคุณในบางกรณี - ไม่ใช่ความคิดที่ดีเนื่องจากจะบิดเบือนผลลัพธ์โดยเฉพาะอย่างยิ่งหากการใช้ฟังก์ชันเช่นprintในครั้งแรกทำให้เกิดการเริ่มต้นระบบย่อย IO หรืออะไรทำนองนั้น!


2
ฉันไม่คิดว่า Ruby และ Python จำเป็นต้องเป็นจริงเสมอไป แต่ +1 อย่างอื่น ..

การพิมพ์ไม่ได้แสดงผลลัพธ์ที่เสถียรที่วัดได้ แต่สิ่งใหม่ของคุณis-prime?แสดงการปรับปรุง 2 เท่า ;)
defhlt

ไม่สามารถทำให้เร็วขึ้นได้หากมี mod ที่ไม่ได้เลือก?
Hendekagon

1
@Hendekagon - คง! ไม่แน่ใจว่าสิ่งนี้ได้รับการปรับให้เหมาะสมกับคอมไพเลอร์ปัจจุบันของ Clojure ได้ดีเพียงใดอาจมีช่องว่างสำหรับการปรับปรุง Clojure 1.4 ช่วยได้มากสำหรับเรื่องประเภทนี้โดยทั่วไป 1.5 น่าจะดีกว่านี้
mikera

1
(zero? (mod n i))น่าจะเร็วกว่า(= (mod n i) 0)
Jonas

23

นี่คือเวอร์ชัน 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 วินาที


2
การใช้unchecked-remainder-intหรือเพียงremแทนที่จะmodควบคู่ไปกับผลลัพธ์การพิมพ์แบบคงที่เพื่อเพิ่มประสิทธิภาพ 4 เท่า ดี!
defhlt

22

ฉันจะตอบแค่ # 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 ทำให้มันอยู่ที่. 3 สำหรับ 10K และ 21 สำหรับ 100K


1
เครื่องกำเนิดไฟฟ้าสร้างความแตกต่างอย่างมากที่นี่เนื่องจากช่วยให้ฟังก์ชันสามารถประกันตัวได้ในครั้งแรกFalse(การจับที่ดี)
mgilson

ฉันรอคอยที่จะให้พวกเขากลายเป็น PyPy ... มันจะยอดเยี่ยมมาก
mgilson

คุณช่วยตอบคำตอบของฉันใน PyPy ได้ไหม ฉันอยากรู้ว่ามันจะเร็วแค่ไหน
steveha

1
คุณพูดถูกทั้งเรื่องการทำซ้ำและxrange! ฉันแก้ไขแล้วและตอนนี้ Python และ Ruby แสดงผลลัพธ์เท่ากัน
defhlt

1
@steveha ฉันจะทำก็ต่อเมื่อคุณสัญญาว่าจะออกไปดาวน์โหลด PyPy ด้วยตัวเอง :)! pypy.org/download.htmlมีไบนารีสำหรับระบบปฏิบัติการทั่วไปทั้งหมดและตัวจัดการแพ็คเกจของคุณมีมันอย่างไม่ต้องสงสัย อย่างไรก็ตามสำหรับเกณฑ์มาตรฐานของคุณด้วยการlru_cacheใช้งานแบบสุ่มสำหรับ 2.7 ที่พบใน AS นั้น 100K จะทำงานใน 2.3 วินาที
Julian

16

คุณสามารถทำให้ 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-loop ซึ่ง JVM สามารถเปลี่ยนเป็นรหัสเครื่องที่เหมาะสมได้มากหรือน้อยดังนั้นจึงไม่ควรอยู่ห่างจาก C มากเกินไป รุ่น.

ดูเพิ่มเติม: วิธีเพิ่มประสิทธิภาพเพื่อความเข้าใจและการวนซ้ำใน Scala


2
ปรับปรุง 2x และลิงค์ที่ดี!
defhlt

btw ร่างกายวิธีนี้เหมือนกันi == n || n % i != 0 && isPrime(n, i + 1)ซึ่งสั้นกว่าแม้ว่าจะอ่านยากกว่าเล็กน้อย
ก็ตาม

1
คุณควรเพิ่ม@tailrecคำอธิบายประกอบเพื่อให้แน่ใจว่าจะทำการเพิ่มประสิทธิภาพนั้น
Daniel C. Sobral

8

นี่คือเวอร์ชันสกาล่าของฉันทั้งแบบขนานและแบบไม่ขนานเพื่อความสนุก: (ในการประมวลผลแบบดูอัลคอร์ของฉันเวอร์ชันคู่ขนานใช้เวลา 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))
  }
}

แก้ไข: ตามคำแนะนำของEmil Hฉันได้เปลี่ยนรหัสของฉันเพื่อหลีกเลี่ยงผลกระทบของการวอร์มอัพ 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)
  }
}

1
รหัสได้รับผลกระทบจากการวอร์มอัพ jvm หรือไม่? เช่นisSexyPrimeอาจได้รับการปรับให้เหมาะสม (เพิ่มเติม) เมื่อเรียกจากfindPrimesParและไม่มากนักเมื่อเรียกจากfindPrimes
Emil H.

@EmilH พอใช้. ฉันได้เปลี่ยนรหัสเพื่อหลีกเลี่ยงผลของการวอร์มอัพ io และ jvm
Eastsun

การขึ้นไปที่ sqrt (n) เท่านั้นเป็นการเพิ่มประสิทธิภาพที่ดี แต่ตอนนี้คุณกำลังเปรียบเทียบอัลกอริทึมอื่น
Luigi Plinge

7

ไม่ต้องคำนึงถึงเกณฑ์มาตรฐาน ปัญหาทำให้ฉันสนใจและฉันได้ทำการปรับแต่งอย่างรวดเร็ว สิ่งนี้ใช้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 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)))

ข้างต้นใช้เวลาแก้ไขเพียงสั้น ๆ ฉันตัดสินใจที่จะก้าวไปอีกขั้นและทำการทดสอบ primes โดยลองใช้ตัวหารเฉพาะเท่านั้นและขึ้นอยู่กับค่ารากที่สองของจำนวนที่ทดสอบเท่านั้น วิธีที่ฉันทำก็ต่อเมื่อคุณตรวจสอบหมายเลขตามลำดับดังนั้นมันจึงสามารถสะสมราคาทั้งหมดได้ตามลำดับ แต่ปัญหานี้ได้ตรวจสอบตัวเลขตามลำดับเรียบร้อยแล้ว

บนแล็ปท็อปของฉัน (ไม่มีอะไรพิเศษโปรเซสเซอร์คือ 1.5 GHz 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ดีแน่นอน สำหรับปัญหาบางประเภทเช่นการสร้างหมายเลข Fibonacci ต่อเนื่องมันสามารถเพิ่มความเร็วได้มากเพียงแค่เพิ่มมัณฑนากรบรรทัดเดียวในฟังก์ชัน! นี่คือลิงค์ไปยังการพูดคุยของ Raymond Hettinger ซึ่งครอบคลุมเนื้อหาlru_cacheประมาณ 26 นาทีใน. blip.tv/pycon-us-videos-2009-2010-2011/…
steveha

3
เมื่อใช้ lru_cache คุณจะใช้อัลกอริทึมอื่นแทนรหัสดิบ ดังนั้นประสิทธิภาพจึงเกี่ยวกับอัลกอริทึม แต่ไม่ใช่ภาษา
Eastsun

1
@Eastsun - ฉันไม่เข้าใจว่าคุณหมายถึงอะไร lru_cacheหลีกเลี่ยงการคำนวณซ้ำที่เพิ่งทำไปแล้วและนั่นคือทั้งหมด ฉันไม่เห็นว่า "จริงๆแล้วเรา [ing] อัลกอริทึมอื่น" เป็นอย่างไร และ Python มีอาการช้า แต่ได้ประโยชน์จากการมีของเจ๋ง ๆ เช่นlru_cache; ฉันไม่เห็นอะไรผิดปกติกับการใช้ส่วนที่เป็นประโยชน์ของภาษา และฉันบอกว่าเราไม่ควรเปรียบเทียบเวลาทำงานของคำตอบของฉันกับภาษาอื่น ๆ โดยไม่ทำการเปลี่ยนแปลงที่คล้ายกันกับภาษาอื่น ฉันไม่เข้าใจว่าคุณหมายถึงอะไร
steveha

@Eastsun ถูกต้อง แต่ในทางกลับกันควรอนุญาตให้ใช้ภาษาระดับสูงได้อย่างสะดวกสบายเว้นแต่จะได้รับข้อ จำกัด เพิ่มเติม lru_cache จะสละหน่วยความจำเพื่อความเร็วและปรับความซับซ้อนของอัลกอริทึม
Matt Joiner

2
หากคุณใช้อัลกอริทึมอื่นคุณสามารถลองใช้ Sieve of Eratosthenes รุ่นหลามผลิตคำตอบสำหรับ 100K ในภายใต้0.03วินาที (30มิลลิวินาที)
jfs

7

อย่าลืม Fortran! (ส่วนใหญ่ล้อเล่น แต่ฉันคาดหวังว่าจะมีประสิทธิภาพใกล้เคียงกับ 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

6

ฉันไม่สามารถต้านทานที่จะทำการเพิ่มประสิทธิภาพที่ชัดเจนที่สุดบางส่วนสำหรับเวอร์ชัน C ซึ่งทำให้การทดสอบ 100k ใช้เวลา 0.3 วินาทีในเครื่องของฉัน (เร็วกว่ารุ่น C 5 เท่าในคำถามทั้งที่รวบรวมด้วย 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:

  • 319ms C คอมไพล์ด้วย / Ox และเอาต์พุตเป็น> NIL:
  • 312ms C รวบรวมด้วย / Ox และตัวนับแบบคงที่
  • VM ไคลเอนต์ Java 324ms พร้อมเอาต์พุตเป็น> NIL:
  • VM ไคลเอนต์ Java 299ms พร้อมตัวนับแบบคงที่

และการรัน 1M (ผลลัพธ์ 16386):

  • 24.95s C รวบรวมด้วย / Ox และตัวนับแบบคงที่
  • 25.08s Java ไคลเอ็นต์ VM พร้อมตัวนับแบบคงที่
  • 24.86s Java เซิร์ฟเวอร์ VM พร้อมตัวนับแบบคงที่

แม้ว่าสิ่งนี้จะไม่ตอบคำถามของคุณ แต่ก็แสดงให้เห็นว่าการปรับแต่งเล็กน้อยอาจส่งผลกระทบต่อประสิทธิภาพ ดังนั้นเพื่อให้สามารถเปรียบเทียบภาษาได้จริง ๆ คุณควรพยายามหลีกเลี่ยงความแตกต่างของอัลกอริทึมทั้งหมดให้มากที่สุด

นอกจากนี้ยังให้คำแนะนำว่าทำไม Scala จึงดูค่อนข้างเร็ว มันทำงานบน Java VM และได้รับประโยชน์จากประสิทธิภาพที่น่าประทับใจ


1
เร็วกว่าที่จะไปที่ sqrt (x) แทน x >> 1 สำหรับฟังก์ชันตรวจสอบเฉพาะ
Eve Freeman

4

ใน Scala ให้ลองใช้ Tuple2 แทน List น่าจะเร็วกว่า เพียงแค่ลบคำว่า 'List' เนื่องจาก (x, y) เป็น Tuple2

Tuple2 มีความเชี่ยวชาญสำหรับ Int, Long และ Double ซึ่งหมายความว่าจะไม่ต้องใส่กล่อง / คลายประเภทข้อมูลดิบเหล่านั้น ที่มา Tuple2 . รายการไม่เชี่ยวชาญ แหล่งที่มาของรายการ


จากนั้นคุณไม่สามารถเรียกforallมันได้ ฉันยังคิดว่านี่อาจไม่ใช่รหัสที่มีประสิทธิภาพที่สุด (มากกว่าเพราะคอลเลกชันที่เข้มงวดขนาดใหญ่ถูกสร้างขึ้นสำหรับขนาดใหญ่nแทนที่จะใช้เพียงมุมมอง) แต่มันสั้น + สง่างามและฉันก็ประหลาดใจว่ามันทำงานได้ดีแค่ไหนแม้จะใช้ a สไตล์การใช้งานมากมาย
0__

คุณพูดถูกฉันคิดว่า 'forAll' อยู่ที่นั่น ยังคงมีการปรับปรุงครั้งใหญ่ในรายการและมันจะไม่เลวร้ายที่จะมี 2 สายนี้
Tomas Lazaro

2
มันเร็วกว่าแน่นอนโดยdef sexyPrimes(n: Int) = (11 to n).map(i => (i-6, i)).filter({ case (i, j) => isPrime(i) && isPrime(j) })ที่นี่เร็วกว่าประมาณ 60% ดังนั้นควรเอาชนะรหัส C :)
0__

อืมฉันได้รับประสิทธิภาพเพิ่มขึ้น 4 หรือ 5% เท่านั้น
Luigi Plinge

1
ฉันพบว่าcollectช้าลงอย่างมาก เร็วกว่าคือถ้าคุณกรองก่อนแล้วจึงแมป withFilterเร็วกว่าเล็กน้อยเนื่องจากไม่ได้สร้างคอลเลกชันระดับกลาง (11 to n) withFilter (i => isPrime(i - 6) && isPrime(i)) map (i => (i - 6, i))
Luigi Plinge

4

นี่คือรหัสสำหรับรุ่น 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.1GHz แคช 2MB L2 800MHz FSB แรม 4GB

รุ่น 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 วินาที ใช้ RAM น้อยกว่า 1.5 Mb

สิ่งหนึ่งที่แปลก: kubuntu 64bit แบบ dual core ของฉันไม่เคยถึงจุดสูงสุดในทั้งสองคอร์ ดูเหมือนว่า Go จะใช้เพียงคอร์เดียว แก้ไขด้วยการโทรไปที่runtime.GOMAXPROCS(4)

อัปเดต: ฉันใช้งานเวอร์ชันพาราเลลไลซ์สูงสุด 1 ล้านหมายเลข หนึ่งในคอร์ CPU ของฉันอยู่ที่ 100% ตลอดเวลาในขณะที่อีกแกนหนึ่งไม่ได้ใช้เลย (แปลก) ใช้เวลานานกว่า C และ Go เวอร์ชันปกติทั้งนาที :(

ด้วย 1000000 (1M แทน 100K):

C: 3m35.458s Go: 3m36.259s Go using goroutines:3 นาที 27.137 วินาที2m16.125s

รุ่น 100k:

C: 2.723s Go: 2.743s Go using goroutines: 1.706s


คุณใช้ btw กี่คอร์?
om-nom-nom

2
ฉันมี Asus u81a Intel Core 2 Duo T6500 2.1GHz แคช 2MB L2 800MHz FSB RAM 4GB
Sebastián Grignoli

คุณได้รวบรวมเวอร์ชัน C โดยเปิดใช้งานการเพิ่มประสิทธิภาพหรือไม่? คอมไพเลอร์ Go เริ่มต้นจะไม่อยู่ในบรรทัดและโดยปกติจะได้รับผลกระทบอย่างมากเมื่อเทียบกับ C ที่ปรับให้เหมาะสมในการเปรียบเทียบประเภทนี้ เพิ่ม-O3หรือดีกว่า
Matt Joiner

ฉันเพิ่งทำไม่ก่อนหน้านี้และรุ่น 100K ใช้เวลาเท่ากันโดยมีหรือไม่มี -O3
Sebastián Grignoli

สิ่งเดียวกันสำหรับรุ่น 1M บางทีการดำเนินการเฉพาะนี้ (เรากำลังทดสอบชุดย่อยที่เล็กมาก) ได้รับการปรับให้เหมาะสมตามค่าเริ่มต้น
Sebastián Grignoli

4

เพื่อความสนุกนี่คือ 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

ใน MacBook Air 1.8GHz Core i5 ของฉันผลการดำเนินงานคือ:

# 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)

ดูเหมือนว่า JIT ของ JVM จะให้ Ruby เพิ่มประสิทธิภาพที่ดีในกรณีเริ่มต้นในขณะที่การทำงานแบบมัลติเธรดจริงช่วยให้ JRuby ทำงานได้เร็วขึ้น 50% ในกรณีเธรด สิ่งที่น่าสนใจกว่านั้นก็คือ JRuby 1.7 ช่วยเพิ่มคะแนน JRuby 1.6 ได้ถึง 17%!


3

จากคำตอบของ x4uฉันเขียนเวอร์ชันสกาลาโดยใช้การเรียกซ้ำและฉันปรับปรุงโดยไปที่ sqrt แทน x / 2 สำหรับฟังก์ชันตรวจสอบเฉพาะ ฉันได้รับ ~ 250ms สำหรับ 100k และ ~ 600ms สำหรับ 1M ฉันไปข้างหน้าและไปที่ 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) ซึ่งได้รับ ~ 15ms สำหรับ 100k, 250ms สำหรับ 1M และ 6s สำหรับ 10M โดยใช้ตัวนับ (ไม่สนใจ I / O) ถ้าฉันเปิดเอาท์พุทจะใช้เวลาประมาณ 150ms สำหรับ 100k, 1s สำหรับ 1M และ 12s สำหรับ 10M ไม่สามารถใช้การเรียกหางซ้ำได้ที่นี่น่าเสียดายที่ฉันต้องแปลงกลับเป็นลูป

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")

2

คำตอบสำหรับคำถามของคุณ # 1 คือใช่ JVM นั้นรวดเร็วอย่างไม่น่าเชื่อและใช่การพิมพ์แบบคงที่ช่วยได้

JVM ควรเร็วกว่า C ในระยะยาวซึ่งอาจเร็วกว่าภาษาแอสเซมบลี "Normal" ด้วยซ้ำ - แน่นอนว่าคุณสามารถปรับแต่งแอสเซมบลีให้เหมาะสมเพื่อเอาชนะทุกสิ่งได้เสมอโดยการทำโปรไฟล์รันไทม์แบบแมนนวลและสร้างเวอร์ชันแยกต่างหากสำหรับ CPU แต่ละตัวคุณเพียงแค่ ต้องเก่งและมีความรู้อย่างน่าอัศจรรย์

สาเหตุของความเร็วของ Java คือ:

JVM สามารถวิเคราะห์โค้ดของคุณในขณะที่รันและปรับให้เหมาะสมด้วยมือตัวอย่างเช่นหากคุณมีวิธีการที่สามารถวิเคราะห์แบบคงที่ในเวลาคอมไพล์เพื่อให้เป็นฟังก์ชันที่แท้จริงและ JVM สังเกตว่าคุณมักจะเรียกมันด้วยสิ่งเดียวกัน พารามิเตอร์มันสามารถกำจัดการโทรได้อย่างสมบูรณ์และเพียงแค่ฉีดผลลัพธ์จากการโทรครั้งล่าสุด (ฉันไม่แน่ใจว่า Java ทำสิ่งนี้จริงหรือไม่ แต่มันก็ทำได้หลายอย่างเช่นนี้)

เนื่องจากการพิมพ์แบบคงที่ JVM สามารถรู้เกี่ยวกับโค้ดของคุณได้มากในเวลาคอมไพล์ซึ่งทำให้สามารถปรับแต่งบางสิ่งได้ล่วงหน้า นอกจากนี้ยังช่วยให้คอมไพเลอร์ปรับแต่งแต่ละคลาสทีละคลาสโดยไม่ต้องรู้ว่าคลาสอื่นวางแผนที่จะใช้มันอย่างไร นอกจากนี้ Java ยังไม่มีตัวชี้ตำแหน่งหน่วยความจำโดยพลการมันรู้ว่าค่าใดในหน่วยความจำที่อาจและไม่สามารถเปลี่ยนแปลงได้และสามารถปรับให้เหมาะสมได้

การจัดสรรฮีปนั้นมีประสิทธิภาพมากกว่า C มากการจัดสรรฮีพของ Java นั้นเหมือนกับการจัดสรรสแต็กของ C ในความเร็ว แต่มีความหลากหลายมากกว่า เวลาผ่านไปในอัลโกรไทม์ต่าง ๆ ที่ใช้ที่นี่มันเป็นศิลปะ - ตัวอย่างเช่นวัตถุทั้งหมดที่มีอายุการใช้งานสั้น (เช่นตัวแปรสแต็กของ C) จะถูกจัดสรรให้กับตำแหน่งว่างที่ "รู้จัก" (ไม่ต้องค้นหาจุดว่าง มีพื้นที่เพียงพอ) และทั้งหมดถูกปลดปล่อยเข้าด้วยกันในขั้นตอนเดียว (เช่นสแต็กป๊อป)

JVM สามารถรู้นิสัยใจคอเกี่ยวกับสถาปัตยกรรม CPU ของคุณและสร้างรหัสเครื่องโดยเฉพาะสำหรับ CPU ที่กำหนด

JVM สามารถเร่งความเร็วโค้ดของคุณได้นานหลังจากที่คุณจัดส่ง เช่นเดียวกับการย้ายโปรแกรมไปยัง CPU ใหม่สามารถเร่งความเร็วได้การย้ายไปยังเวอร์ชันใหม่ของ JVM ยังสามารถให้ประสิทธิภาพความเร็วที่ยอดเยี่ยมแก่ซีพียูที่ไม่ได้มีอยู่เมื่อคุณคอมไพล์โค้ดในตอนแรกสิ่งที่ c ไม่สามารถทำได้ทางกายภาพ ทำโดยไม่ต้อง recomiple

อย่างไรก็ตามการตอบสนองที่ไม่ดีส่วนใหญ่สำหรับความเร็ว java นั้นมาจากเวลาเริ่มต้นที่ยาวนานในการโหลด JVM (สักวันจะมีคนสร้าง JVM ลงใน OS และสิ่งนี้จะหายไป!) และความจริงที่ว่านักพัฒนาหลายคนเขียนไม่ดีจริงๆ รหัส GUI (โดยเฉพาะเธรด) ซึ่งทำให้ Java GUI มักไม่ตอบสนองและผิดพลาด ภาษาที่ใช้งานง่ายเช่น Java และ VB มีข้อผิดพลาดที่เพิ่มขึ้นเนื่องจากความสามารถของโปรแกรมเมอร์โดยเฉลี่ยมีแนวโน้มที่จะต่ำกว่าภาษาที่ซับซ้อนกว่า


การกล่าวว่าการจัดสรรฮีปของ JVM มีประสิทธิภาพมากกว่า C ที่ไม่สมเหตุสมผลเนื่องจาก JVM เขียนด้วย C ++
Daniel C. Sobral

5
@ DanielC ภาษา Sobral ไม่สำคัญเท่ากับการใช้งาน - โค้ดการใช้งาน "Heap" ของ Java ไม่มีอะไรเหมือนกับภาษา C Java's เป็นระบบหลายขั้นตอนแบบถอดเปลี่ยนได้ซึ่งสามารถปรับแต่งได้สูงสำหรับเป้าหมายที่หลากหลายด้วยความพยายามหลายปีในการวิจัยรวมถึงเทคนิคล้ำสมัยที่พัฒนาขึ้นในปัจจุบัน C ใช้ฮีปซึ่งเป็นโครงสร้างข้อมูลที่เรียบง่ายซึ่งพัฒนาขึ้นเมื่อนานมาแล้ว ระบบของ Java เป็นไปไม่ได้ที่จะนำไปใช้กับ C เนื่องจาก C อนุญาตให้ใช้พอยน์เตอร์ดังนั้นจึงไม่สามารถรับประกันการเคลื่อนไหว "ปลอดภัย" ของหน่วยความจำที่จัดสรรโดยพลการโดยไม่มีการเปลี่ยนแปลงภาษา (การแสดงผลไม่ใช่ C อีกต่อไป)
Bill K

Safeness ไม่เกี่ยวข้อง - คุณไม่ได้เรียกร้องมันเป็นความปลอดภัยมากขึ้นคุณอ้างว่ามันเป็นมีประสิทธิภาพมากขึ้น นอกจากนี้คุณยังอธิบายในความคิดเห็นว่าการทำงานของ C "ฮีป" ไม่มีผลกับความเป็นจริง
Daniel C. Sobral

คุณต้องเข้าใจความหมายของ "Safe" ของฉันผิด - Java สามารถย้ายบล็อกหน่วยความจำโดยพลการได้ตลอดเวลาเพราะรู้ว่าสามารถทำได้ C จึงไม่สามารถปรับแต่งหน่วยความจำทั้งหมดได้เนื่องจากอาจมีตัวชี้ที่อาจอ้างอิงได้ นอกจากนี้ AC heap มักจะใช้เป็นฮีปซึ่งเป็นโครงสร้างข้อมูล ฮีป C ++ ที่ใช้กับโครงสร้างฮีปเช่น C คือ (ดังนั้นชื่อ "ฮีป") ฉันไม่ได้ตรวจสอบ C ++ มาสองสามปีแล้วดังนั้นสิ่งนี้อาจไม่เป็นจริงอีกต่อไป แต่ก็ยังถูก จำกัด โดยไม่สามารถ จัดเรียงหน่วยความจำที่จัดสรรของผู้ใช้ขนาดเล็กอีกครั้งตามต้องการ
Bill K
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.