본문 바로가기

Java

[Java] equals의 활용. 더 나아가 코드 구현의 방법. - 2 -

앞서 포스트에서는
결과 값을 List<TestDomain>에 담기로 하였습니다.
그래서 중간 중간 나오는 결과를 List<TestDomain>에 담아 두는 로직이
들어가 있었습니다.

하지만, 그렇기 때문에 이미 합산 된 검색어를 찾기 위해서
다시 List에 대해 loop를 돌면서 해당 검색어를 가진 TestDomain을 찾아야 했습니다.
순차 탐색을 하기 때문에 최대 n만큼의 시간이 소요됩니다.

합산 되는 결과가 많아져서 List<TestDomain>의 크기가 커질 수록
탐색 시간도 길어질 것 입니다.

그러면 이 탐색시간을 줄이면 더욱 속도를 올릴 수 있을 것 같습니다.

여러가지 데이터를 탐색하는 알고리즘 중에서 상당히 빠른 방법이 있습니다.

바로 HashCode를 사용한 방법입니다.

이제 350000개의 7일치 검색어를 loop돌면서 (이건 어쩔 수 없다 치고요..)
각 검색어에 대한 검색 횟수의 합을 구한 후 List<TestDomain>에 넣는 것이 아니라
Map<String,TestDomain>에 넣어두겠습니다.

물론, 모든 결과가 Map<String,TestDomain>에 들어 간 이후에는 이를 다시
List<TestDomain>형으로 변경해줘야 합니다.

List<TestDomain>은 이미 사용되고 있는 메서드의 리턴형이기 때문에
이것이 바뀌게 되면 다른 클래스들을 수정해야하는 일이 생깁니다.
내부에서의 리팩토링만 하고 메서드의 signature는 그대로 유지하는 것입니다.

아무튼, 위와 같은 Map -> List 의 형변환을 하는 것이
불필요한 일일지도 모르겠습니다만..
한번 해보면 알겠죠..^^



Test.java 세번째

결과를 담아서 사용 할 Map객체를 생성하고
Map<String,TestDomain> resultMap = new HashMap<String,TestDomain>();

이를 사용해서 각 검색어의 검색횟수를 머지하고 있습니다.

결과는

initData size : 350000
loopCnt  : 350000
sort........
resultList size : 50000
수행시간 : 0초
수행시간 : 63ms

이렇게 나옵니다. 물론 PC마다 조금씩 다르겠지만...

앞선 포스트에서의 여러가지 방법보다 엄청나게 빠른 성능적 향상을 가져 올 수 있었습니다.

ms단위로 떨어졌네요..

가장 먼저 구현되어 있던 Test.java에 비해서
크게 복잡해지지도 않았고, 특별히 다른 라이브러리를 사용하지도 않았습니다.
하지만, 약간의 생각의 전환의 과정을 밟아오면서
조금조금씩 성능을 향상 시킬 수 있었습니다.

포스트를 마무리하기전에
위 내용을 멀티스레드로 구현해보겠습니다.

멀티스레드로 구현하면 더 빠를까요?


KeywordMergetWorker.java
Runnable 인터페이스를 구현한 KeywordMergeWorker입니다.

타겟이 되는 List와 결과를 담을 Map을 생성자에서 받아서
이 List에 대해 일을 하게 됩니다.
사실 여기서는 각각의 List에는 중복된 키워드가 들어있지 않기 때문에
각 KeywordMergeWorker는 단순히 List에 있는 TestDomain을 Map에 담아내는 역할만을
하게 됩니다. 물론 이 KeywordMergeWorker들이 여러개 생성되어 일을 하게 되는 경우에는
상황이 좀 다르겠죠..

그리고 결과를 담을 Map 객체를 병렬처리에 맞게 바꿔줘야 합니다.

Map<String,TestDomain> resultMap = new ConcurrentHashMap<String,TestDomain>();

각각의 KeywordMergeWorker가 자기의 List에서 TestDomain을 꺼내 위 Map에 있는지 없는지 확인 후
작업을 해야하기 때문에 각 thread간의 동기화가 중요합니다. 인위적으로 synchronized를 사용하는 것 보다는
기본적으로 제공하는 라이브러리를 사용하겠습니다.

각 5만개의 키워드를 가지고 있는 List를 전부 합쳐서 총 350000개의 키워드를 가지고 있던 initDatas도 수정해야 합니다.

각각의 스레드가 동일한 크기의 List를 가지고 일 하도록 하는 것도 병렬처리에서는 중요합니다.

총 7개의 스레드가 일을 한다고 가정 할 때
6개의 스레드가 1초만에 일을 끝내고, 1개의 스레드가 10분동안 일을 해야 한다면
결국  그 프로그램의 실행시간은 10분이 되기 때문입니다.

그리고 initDatas의 형을 List<List<TestDomain>> initDatas = new ArrayList<List<TestDomain>>(); 이렇게 수정하고
각 7일치의 List를 add하여 7개의 스레드가 5만개씩의 키워드 List를 가지고 작업을 할 수 있도록 하겠습니다.

실행부는
for(int threadCount = 0; threadCount < 7; threadCount++) {
            KeywordMergeWorker worker = new KeywordMergeWorker(initDatas.get(threadCount), resultMap);
            worker.run();
        }   

이렇게 되겠죠..

제 PC가 쿼드코어이지만 그냥 편의를 위해서
7개의 스레드를 생성하여 돌려보겠습니다


Test.java 병렬처리

병렬처리를 할 때 주의해야 할 것들은 거의 신경쓰지 않은 코드입니다.

내용이 그걸 얘기하려는건 아니니까요..

결과는 어떨까요? 더 빨라졌을까요?

initData size : 7
sort........
resultList size : 50000
수행시간 : 0초
수행시간 : 125ms

오히려 더 느려졌네요..

여러가지 이유가 있겠죠. CPU에  비해 과하게 많은 스레드라던가
동기화를 하기 위해 소요되는 시간이 더 많다던지 하는...

중요한건 병렬처리가 더 좋다 나쁘다에 대한 얘기가 아니라
정말 간단한 하나의 문제를 가지고도 이렇게 여러가지 생각을 해볼 수 있다는 것 입니다.