Java Yavaş mı? : Java’nın Performansı Üzerine – II

Bir önceki yazıda kaldığımız yerden devam edelim.

Java Yavaş mıdır?

Java dil olarak yavaş değildir. Çünkü, Java hemen hemen native çalışır. Java’nın yorumlanan (interpreted) yapısının onun performansını hiç bir zaman native çalışan C/C++ gibi diller seviyesine çıkmasına izin vermeyeceği aslında eskimiş, çoktan demode olmuş, geçen yüzyılın bir iddiasıdır. JVMlerin barındırdığı Just In Time (JIT) derleyicileri sık çalışan Java kodlarını çalışma zamanında bulur ve native koda derler öyleki artık bu kodlara yapılan tüm çağrılar native olarak çalışır. Varsayılan değeri 10,000 olan “-XX:CompileThreshold” parametresi bu amaçla kullanılır. Java’nın 1.2 sürümüyle birlikte gelen ve 1.3 sürümünde varsayılan JVM olan HotSpot ismini zaten buradan almaktadır.

Ayrıca Java’nın yorumlanan (interpreted) yapısı ve  pek çok çalışma zamanı (run-time) iyileştirmesine imkan verir. Bu iyileştirmeler nihayetinde HotSpot’ın görevleri arasındadır. Dolayıısyla en azından algoritmik performansı göz önüne alırsak, Java’nın C/C++’tan daha yavaş olduğunu söylemek zordur. Bu anlamda Java’nın daha yavaş ya da daha hızlı olduğu durumsaldır, kullanılan algoritmaya da bağlıdır.

C++ ve Java Performans Karşılaştırması

Bu konuyu iki örnekle göstermek istiyorum. Aynı algoritmayı C++ ve Java’da tamamen aynı şekilde yazarak, algoritmik açıdan bu iki dilin performansları karşılaştırılabilir. Ama unutmayalım ki bu türden karşılaştırmalar uygulama performansı ile ilgili bir şey söylemedikleri gibi sadece algoritmik dil performansı hakkında fikir vermeye yöneliktir.

İlk algoritma asal sayıları bulmak için kullanılan, Sieve of Eratosthenes‘in bir ilerlemiş hali olan Sieve of Atkin algoritması olsun.

Önce SieveOfAtkin.cpp

/* 
 * File:   SieveOfAtkin.cpp
 * Author: akin
 * 
 * Created on October 7, 2015, 12:27 AM
 */

#include "SieveOfAtkin.h"

#include <iostream>
#include <cmath>
#include <vector>
#include <ctime>

using namespace SieveOfAtkin;
using namespace std;

int main() {
    int n;
    cout << "Enter the number up to which prime numbers are counted: " << endl;
    cin >> n;
    clock_t start = clock();
    int numberOfPrimes = countPrimesUsingSieveOfAtkins(n);
    clock_t end = clock();
    int time = (int) (1000 * ((float) end - start) / CLOCKS_PER_SEC);

    cout << "Number of primes up to " << n << " : " << numberOfPrimes << endl;
    cout << "Time: " << time << " ms." << endl;
    cout << endl;
    return 0;
}

namespace SieveOfAtkin {
    
    using namespace std;

    int countPrimesUsingSieveOfAtkins(int n) {
        bool* is_prime = new bool[n + 1];
        is_prime[2] = true;
        is_prime[3] = true;

        for (int i = 5; i <= n; i++)
            is_prime[i] = false;

        int limit = ceil(sqrt(n));

        for (int x = 1; x <= limit; x++) {
            for (int y = 1; y <= limit; y++) {
                int num = (4 * x * x + y * y);
                if (num <= n && (num % 12 == 1 || num % 12 == 5))
                    is_prime[num] = true;

                num = (3 * x * x + y * y);
                if (num <= n && (num % 12 == 7))
                    is_prime[num] = true;

                num = 3 * x * x - y * y;
                if ((x > y) && (num <= n) && (num % 12 == 11))
                    is_prime[num] = true;
            }
        }

        for (int i = 5; i <= limit; i++)
            if (is_prime[i])
                for (int j = i * i; j <= n; j += i)
                    is_prime[j] = false;

        int numberOfPrimes = 2;
        for (int i = 5; i <= n; i++)
            if (is_prime[i])
                numberOfPrimes++;

        return numberOfPrimes;
    }
}

Şimdi de SieveOfAtkin.java

package org.javaturk.performance.algorithm.prime;

import java.util.Scanner;

/**
 * http://www.sanfoundry.com/java-program-sieve-of-atkin-algorithm/
 * 
 * @author akin
 *
 */
public class SieveOfAtkin {

	public static void main(String[] args) {
		System.out.println("Please enter a number to which to list prime numbers:");
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();

		long start = System.currentTimeMillis();
		int numberOfPrimes = countPrimesAsUsingSieveOfAtkins(n);
		long end = System.currentTimeMillis();

		long miliSeconds = end - start;

		System.out.println("There are " + numberOfPrimes + " prime numbers up to " + n + ".");
		System.out.println("Time to find them: " + miliSeconds + " ms.");
	}

	private static int countPrimesAsUsingSieveOfAtkins(int n) {
		/** initialize the sieve **/
		boolean[] prime = new boolean[n + 1];
		prime[2] = true;
		prime[3] = true;
		int limit = (int) Math.ceil(Math.sqrt(n));

		/**
		 * put in candidate primes: integers which have an odd number of
		 * representations by certain quadratic forms
		 **/
		for (int x = 1; x <= limit; x++) {
			for (int y = 1; y <= limit; y++) {
				int num = 4 * x * x + y * y;
				if (num <= n && (num % 12 == 1 || num % 12 == 5))
					prime[num] = true;
				
				num = 3 * x * x + y * y;
				if (num <= n && num % 12 == 7)
					prime[num] = true;
				
				num = 3 * x * x - y * y;
				if ((x > y) && (num <= n) && (num % 12 == 11))
					prime[num] = true;
			}
		}

		for (int i = 5; i <= limit; i++)
			if (prime[i])
				for (int j = i * i; j <= n; j += i)
					prime[j] = false;

		int numberOfPrimes = 2;
		for (int i = 5; i <= n; i++)
			if (prime[i])
				numberOfPrimes++;

		return numberOfPrimes;
	}
}

Ben bu iki programı bendeki iki ayrı makinada defalarca test ettim. Makinalarım şu özelliklerde:

  • 16 GB RAM ve 8 çekirdekli i7 CPU’ya sahip, üzerinde El Capitan OS çalışan MacBook Pro
  • 8 GB RAM ve 8 çekirdekli i7 CPU’ya sahip, üzerinde Windows 10 çalışan PC

Bu iki programı çalıştırmak için Mac’de Java için JDK 1.8.0_25 JVM, C++ için de XCode 7.1 kullanılmıştır.(Sonradan eklendi: XCode 7.1 varsayılan durumda C++ derleyicisi olarak gcc (GCC) 4.2.x kullanıyor. Ben bu yazıyı yazdıktan sonra makinamdaki GCC’yi en son sürüme yükselttim dolayısıyla artık gcc (GCC) 4.9.2 var ama bu sürümle ölçümleri henüz tekrarlamadım, tekrarlayıp yayınlayacağım.) Çalışma zamanı için tamamen varsayılan yapılar kullanılmış, hiç bir parametre ya da flag verilmemiştir.

Farklı n girdisi için mili saniye cinsinden Java ve C++ programlarının çalışma süreleri şunlardır:

Program

10^6

10^7 10^8 10^9

2*10^9

SieveOfAtkin.java

16

84 1,025 18,507

47,752

SieveOfAtkin.cpp

18

218 2,461 34,871

82,059

 

Bendeki ve arkadaşlarımdaki Windows makinalarda da aynı JVM ve GNU C++ compilerıyla derleyerek yapılan çalışmalarda da yukarıdakine benzer sonuçlar elde ettim.

Sizden ricam, sizin de bu iki programı makinalarınızda çalıştırmanız ve aldığınız sonuçları burada yorum olarak bizlerle paylaşmanız.

Bir sonraki yazıda Monte Carlo simulasyonu ile Pi’ye yakınsamaya çalışacağız 🙂

Toplam görüntülenme sayısı: 814