기
반복문을 비교하는 포스트에 조금 문제가 있었다. 바로...
long startTime = new Date().getTime();
long endTime = new Date().getTime();
long elapsed = endTime - startTime;
윗 코드들, 즉 수행 시간을 재기 위해 사용한 getTime() 메소드들이
순수한 수행시간을 알 수 있는 메소드가 아니란 것이었다.
Date date = new Date();
System.out.println(date);
// 혹은
System.out.println(new Date());
Date 객체는 ( new 생성자를 통하여 ) 호출되는 시점을 저장한다. 따라서
위의 코드를 실행해보면 어떤 포맷에 맞춰진 시간이 출력된다.
(보통은 프로그램 실행하자마자 좌라락 처리되므로 사용자가 보기엔
Date() 가 호출될 시점의 시간이 아니라, 그냥 현재 시간이 출력되는 것처럼 보인다.)
getTime() 메소드는, Date 객체에 저장되어 있는 시간을 밀리초로 변환한다.
즉 getTime()을 사용하면 시간 타입을 순수한 정수로 바꿀수 있다.
따라서 endTime - startTime 이라는 간단한 수식을 사용할 수 있고...
그 값을 long elapsed 에 담으면,
startTime과 endTime 사이에 존재했던 순간의 시간을 알 수 있다.
승
말이 주저리주저리 길었는데...
(어떤 메소드를 써야되는데? 라고 생각했다면,
전(2)를 먼저 보세요.)
문제를 최대한 풀어서 정리해보겠다
아래의 코드를 보자..
public static void main(String[] args) {
WhichLoopIsFaster evaluate = new WhichLoopIsFaster();
evaluate.For();
evaluate.While();
evaluate.Iterator();
}
main 함수 실행시
For 반복문의 시간을 알기 위해 만든 For가 제일 먼저 호출되고,
그다음 While, 이터레이터가 그 뒤를 잇는다.
표면적으로는 누가 먼저 실행되더라도 상관이 없다.
왜냐하면 정말이지 단순하게
종료시점 ㅡ 시작시점 하는 로직이기 때문이다.
하지만...
public static void main(String[] args) {
WhichLoopIsFaster evaluate = new WhichLoopIsFaster();
evaluate.Iterator();
evaluate.While();
evaluate.For();
}
바뀐 점이 보이나요?
이렇게 순서를 바꾸면
항상 제일 느렸던 Iterator의 속도가 눈에 띌 정도로 빨라진다.
심지어 다른 두 메소드보다 빠른 경우도 나온다.
첫 반복문 포스트를 쓸 때는 전혀 생각지 못했다..
처음엔 문제의 원인을 제대로 파악하기가 어려웠다.
전
For(), While(), Iterator() 세가지 메소드를
각각 개별 클래스를 따로 만들어 실행을 따로따로 해보니
다행스럽게도(?) 테스트를 3번이나 했었는데 우연히도...
전 포스트의 결과와 동일하게
for와 while 이 속도가 비슷하고
Iterator가 제일 느리게 나왔다.
어쨌든, 이유를 알아보자.
맨 처음 떠올렸던 생각은
가비지컬렉터에 뭔가 문제가 있는게 아닐까? 였다.
그리고 나의 사랑 stackoverflow 에서 정확히 맞아떨어지지는 않지만 생각할 수 있는 단서를 찾을 수 있었다.
(링크: https://stackoverflow.com/questions/15632734/can-we-call-the-garbage-collector-explicitly)
최초의 가비지컬렉터가 할 일이라고는, 실행할 메소드가 담긴 객체의 내용들을
ㅡ그렇다.. 가비지 컬렉터가 쓰레기라고 생각하는 것은 바로 객체다ㅡ
담는 것만 하면 된다.
하지만 두번째 메소드부터는 상황이 좀 달라진다.
정말 무식하게 1500만 개의 배열에 값을 하나씩 넣고
그 값들을 하나하나 세아리는 일을 시켰기 때문에
Gabage Collector가 엄청난 데이터를 수거해야하고
힙메모리가 꽉 찰때마다 비워주는 처리과정이 하나 더 늘어나게 된 것이다.
따라서 맨 처음 호출되는 메소드는 항상 제일 빠를 수 밖에 없게 된 것이다.
두번째 메소드를 처리하는 도중
힙메모리가 가득차면 Garbage Collector가 호출되어 메모리를 비우고.. 또 차면 비우고..
또한 확실하지 않지만 수십번 테스트해본 결과
가비지컬렉터가 메소드 하나 끝낼 때마다
모든 힙메모리를 날려버리는 것이 아닌 듯하다.
최초로 호출하는 객체의 일부분은 힙메모리에 내비둔채 (안비우고)
두번째 호출하는 객체를 담은뒤 그 것만을 계속 비우고.. 두번째 호출된 객체의 일부분도 힙메모리에 그대로 둔채
세번째 호출하는 객체를 담은 뒤 또다시 그 객체만을 비우는 ....
이렇게 하다 결국 어느 순간에 판단하여 모든 가비지를 날려버리는 것 같은데,
그 순간이 언제일지는 나같은 우매한 인간은 판단이 힘들고
GC(Gabage Collector)님만이 아시는 일인 듯하다 (...)
전(2)
두번째 문제는 Date().getTime()의 사용이었다.
Date().getTime()은 내부적으로 System.currentTimeMillis() 을 호출한다. 즉,
Date().getTime() == System.currentTimeMillis()
둘은 동일하다.
System.currentTimeMillis() 는
1970년 1월 1일 자정부터 현재까지 카운트된 시간을 ms(milliseconds) 단위로 표시한다.
레퍼런스에서는 currentTimeMillis()를 아래와 같은 상황에서 사용한다고 한다.
================================================================
- 날짜와 관련한 시각 계산을 위해 사용(밀리초 단위: 1밀리초는 0.001초.)
- 시스템 시간을 참조함 (외부데이터)
- 일반적인 업데이트 간격은 인터럽트의 타이머(0.00001초, 10만분의 1초)에 따라 결정됨
- GetSystemTimeAsFileTime 메서드로 구현되어 있으며, OS가 관리하는 시간값을 가져옴.
================================================================
- 특정 시점들의 간격을 측정하는데 사용 (나노초 단위: 1나노초는 0.000001밀리초.)
- 시스템 시간과 관련없음 (무관함)
- QueryPerformanceCounter/QueryPerformanceFrequency API 로 구현됨.
(출처: https://hashcode.co.kr/questions/569/systemcurrenttimemillis-%EC%99%80-systemnanotime%EC%9D%98-%EC%B0%A8%EC%9D%B4%EA%B0%80-%EB%AD%94%EA%B0%80%EC%9A%94)
결(2)
객관적인 시간의 간격을 알려면
따로 따로 여러번 돌려보고,
그 평균값을 구하는 것이 합리적인 것 같다..
컴퓨터야 미안해.. 힘내 파이팅!!
그리고 앞으로 처리시간을 알고 싶을 땐 System.nanoTime() 을 사용해야겠다.
'Algorithm > 기타 정보' 카테고리의 다른 글
[java] Stack 구현 (0) | 2019.06.26 |
---|---|
2중 반복문 속도 비교 (WhileFor vs. ForFor) (0) | 2019.03.31 |
반복문 속도 비교 (while, for, iterator) (3) | 2019.03.12 |