Java 실행 속도 - Java silhaeng sogdo

나는 코딩테스트 준비를 위해 본인은 Java와 Python으로 주로 연습하는 편이다.
오늘 프로그래머스에서 '멀쩡한 사각형'이라는 문제를 풀고 python의 실행 속도가 자바에 비해 굉장히 느림을 한번 더 느낄 수 있었다.물론 로직은 같았다.
이해를 돕기 위해 실행 결과의 그림을 첨부 하겠다.

실행속도의 차이는 실감할 수있을 것이라 생각이 된다. 그렇다면 자바와 파이썬의 특징에 대해 설명하면서 왜이렇게 속도가 차이가 나는가에 대해 설명하겠다.


먼저, 자바는

일반적으로 C, C++보다 느리고, Python보다 수행 속도가 빠르다.

일괄 컴파일 방식 언어인 C, C++의 코드는 컴파일만 하면 바로 CPU에서 실행이 가능한 코드인 기계어로 변환된다. 즉 코드의 object파일을 바로 생성하여 이를 메모리에 적재하고 사용한다.

반면 자바 같은 경우 컴파일을 해서 바이트 코드(byte code)를 생성하고, jvm에서 그 바이트 코드(byte code)를 기계어(native code)로 변환하는 시간을 필요로 하기 때문에 일괄 컴파일 방식보다 수행 속도가 더 오래 걸린다. 추가적으로 자바는 컴파일 방식과 인터프리터 방식을 혼합한 JIT 방식을 사용한다.

아래 그림을 보면 조금 더 위 글의 의미가 와 닿을 것이라 생각한다.

Java 실행 속도 - Java silhaeng sogdo


그렇다면 파이썬은?

전부를 적을 수는 없겠지만 필자가 이해하고 알고있는 2가지 이유를 통해 파이썬이 느린 이유를 적어보겠다.

1. 파이썬은 동적 타입 언어이기 때문이다.

a = 1
b = 2
c = a + b

파이썬은 타입 선언의 생략이 가능하다. 그럼 이는 내부적으로 어떻게 동작하는가?

a에 1을 할당 ->
a의 타입을 판단(정수) ->
a의 값 = 1 ->
b에 2를 할당 ->
b의 타입을 판단(정수) ->
b의 값 = 2 ->
덧셈 루틴 호출 ->
a의 자료형 검색 ->
a는 정수 ->
b도 자료형 검색 ->
b도 정수 ->
덧셈 루틴에 입력 ->
정수형 결과값 도출 ->
c 생성 ->
c의 자료형을 정수로 설정 ->
c에 덧셈한 결과값을 설정

이 같이 간단한 코드도 자료형을 알기 위해 계속적인 검색과정이 필요하다. 느릴수 밖에...

2. 파이썬은 인터프리터 언어이다.

컴파일 언어는 위에서도 언급했듯이 메모리에 접근해 바로 os에서 기계언어로 해석이 가능하게 컴파일시 하나의 객체 파일을 생성하여 적재한다. 즉 컴파일러는 한꺼번에 컴파일을 하기 때문에 컴파일 시간은 오래걸리지만 목적프로그램을 실행할때는 컴파일을 하지 않아 속도가 월등히 빠르다. 추가적으로 컴파일타임에 오류검출이 가능하다는 장점도 있다~

반면 파이썬과 같은 인터프리터는 라인별로 컴파일을 하기 때문에 라인을 컴파일 하는 시간이 짧지만 프로그램을 실행하는 동안 컴파일 작업도 같이 하기 때문에 프로그램 자체의 속도는 느리다. 그럼에도 인터프리터 언어를 사용하는 이유는 다양한데, 뭐.. 편리성, 한 줄 단위로 컴파일을 하기에 디버깅시 유리하다는 점, OS별 이식성이 높다는 점이 있기에 사용된다.


결론

지금까지 자바와 파이썬의 특징을 언급하며 각 언어가 프로그램을 실행하는데 다른 언어에 비해 비교적 빠르거나 느린 이유를 알아보았다.

상황에 맞게 언어를 자유자재로 구현할 수 있는 개발자가 되어야 함을 느낄 수 있었다.

category Java 2020. 6. 1. 11:25

Java 실행 속도 - Java silhaeng sogdo

함수 실행시간을 확인하고싶을때 System.currentTimeMillis() 를 이용하여 알아 볼 수 있습니다.

구조는 간단합니다 함수 실행 전 시간과 실행후 시간의 차이로 얼마나 걸리는지 알 수 있습니다.

currentTimeMillis() 는 현재 시간을 구하며 요 함수를 함수 시작전과 후에 사용하여 차이로 함수의 실행시간을 구합니다.

함수는 간단하게 3초의 대기시간을 가지는 함수입니다.

public class test {
	public static void main(String[] args) {
		test t = new test();
		long startTime = System.currentTimeMillis();
		t.testMethod();
		long endTime = System.currentTimeMillis();
		System.out.println((endTime - startTime));
	}
	
	public void testMethod() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
/////Console/////
3001

나온 시간은 1000당 1초로 3.001초 걸렸다고 보면 되겠네요.


currentTimeMillis

먼저 currentTimeMillis 메서드를 사용해볼 수 있다. UTC인 1970년 1월 1일 자정부터 현재까지 카운트된 시간을 1/1000초인 밀리초(milliseconds) 단위로 표시한다. currentTimeMillis의 출력값을 Date로 변환하면 현재 날짜를 구할 수 있다.

long start = System.currentTimeMillis();
// ... 로직 생략
long end = System.currentTimeMillis();
System.out.println("수행시간: " + (end - start) + " ms");

사실 currentTimeMillis는 성능 측정을 위한 코드의 수행 시간을 기록하기에는 적합하지 않을 수 있다. wall-clock time이기 때문에 다른 작업의 영향을 받거나 시스템의 시간을 변경하는 경우 등 측정 결과에 영향을 줄 수 있는 요소들이 있기 때문이다.

따라서 날짜와 관련한 계산을 위해 사용하는 것이 적합하며, 코드의 수행 시간 측정은 이어서 살펴볼 nanoTime를 사용하는 것이 더 정확하다.

nanoTime

nanoTime은 기준이 되는 시점에서 경과된 시간을 나노초(nanoseconds) 단위로 측정한다. 앞서 살펴본 currentTimeMillis와 코드 구성은 같지만 시스템이나 wall-clock time에 관련이 없는 것이 차이점이다.

long start = System.nanoTime();
// ... 로직 생략
long end = System.nanoTime();
System.out.println("수행시간: " + (end - start) + " ns");

한 가지 주의할 점은 JVM(Java Virtual Machine)을 기준으로 측정하기 때문에 서로 다른 JVM에서는 측정 결과가 발생할 수 있다. 즉, 같은 서버에 측정하는 것은 이슈가 없으나, 서로 다른 서버에서 측정할 때는 측정 결과가 서로 다를 수 있다.

Instant

Instant 클래스는 타임라인의 한 시점을 나타낸다. 타임스탬프는 UTC인 1970년 1월 1일 자정을 0으로 하여, 그 이후 경과된 시간을 양수 또는 음수로 표현한다. Instant 클래스의 between 메서드를 이용하면 두 Instant 객체 사이의 기간을 얻을 수 있다. 반환값은 Duration인데 이를 toMillis 메서드를 이용해 밀리초(milliseconds)로 변환하면 된다.

Instant start = Instant.now();
// ... 로직 생략
Instant end = Instant.now();
System.out.println("수행시간: " + Duration.between(start, end).toMillis() + " ms");

StopWatch

다음으로 라이브러리를 이용하는 방법이다. 아파치의 commons-lang3 패키지에 포함되어 있는 StopWatch 클래스가 있다.

StopWatch stopWatch = new StopWatch();
stopWatch.start();
// ... 로직 생략
stopWatch.stop();
System.out.println("수행시간: " + stopWatch.getTime() + " ms");

스프링 프레임워크에서도 StopWatch 클래스를 제공한다. 참고로 아파치와 스프링 프레임워크의 StopWatch 클래스 모두 스레드 안전(thread-safe)하지 않는 점에 유의하자.

StopWatch stopWatch = new StopWatch();
stopWatch.start();
// ... 로직 생략
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());

스프링 프레임워크의 StopWatch 클래스에는 prettyPrint 메서드가 있는데, 이를 사용하면 메서드 이름처럼 예쁜(?) 출력을 볼 수 있다.

StopWatch '': running time = 3768817 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
003768817  100% 

사용법이 간단하고 수행 시간 측정을 위한 부수적인 메서드를 제공하기 때문에 StopWatch 클래스가 가장 활용도가 높다.