Java

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

용식 2010. 7. 30. 00:00
앞서 포스트에서는
결과 값을 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 세번째
package sort;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
* @author need4spd, need4spd@cplanet.co.kr, 2010. 7. 28.
*
*/
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.runTest();
}
public void runTest() {
//Data 초기화
List<TestDomain> list_1 = new ArrayList<TestDomain>();
List<TestDomain> list_2 = new ArrayList<TestDomain>();
List<TestDomain> list_3 = new ArrayList<TestDomain>();
List<TestDomain> list_4 = new ArrayList<TestDomain>();
List<TestDomain> list_5 = new ArrayList<TestDomain>();
List<TestDomain> list_6 = new ArrayList<TestDomain>();
List<TestDomain> list_7 = new ArrayList<TestDomain>();
List<TestDomain> initDatas = new ArrayList<TestDomain>();
Random random = new Random();
for(int limit = 0; limit < 7; limit++) {
for(int i = 0; i < 50000; i++) {
if(limit == 0) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_1.add(testDomain);
} else if (limit == 1) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_2.add(testDomain);
} else if (limit == 2) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_3.add(testDomain);
} else if (limit == 3) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_4.add(testDomain);
} else if (limit == 4) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_5.add(testDomain);
} else if (limit == 5) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_6.add(testDomain);
} else if (limit == 6) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_7.add(testDomain);
}
}
}
initDatas.addAll(list_1);
initDatas.addAll(list_2);
initDatas.addAll(list_3);
initDatas.addAll(list_4);
initDatas.addAll(list_5);
initDatas.addAll(list_6);
initDatas.addAll(list_7);
System.out.println("initData size : " + initDatas.size());
//Data 초기화 끝.
//우선 keyName으로 정렬한다.
// Comparator<TestDomain> keyNameCompartor = new Comparator<TestDomain>() {
// public int compare(TestDomain o1, TestDomain o2) {
// return o1.getkeyName().compareTo(o2.getkeyName());
// }
// };
//
// Collections.sort(initDatas, keyNameCompartor);
long startTime = System.currentTimeMillis();
Map<String,TestDomain> resultMap = new HashMap<String,TestDomain>();
long loopCnt = 0;
for(TestDomain initData : initDatas) {
if(resultMap.containsKey(initData.getkeyName())) {
TestDomain alreadyInput = resultMap.get(initData.getkeyName());
int orgCount = alreadyInput.getCount();
int newCount = initData.getCount();
alreadyInput.setCount(orgCount + newCount);
} else {
resultMap.put(initData.getkeyName(), initData);
}
loopCnt++;
}
long endTime = System.currentTimeMillis();
System.out.println("loopCnt : " + loopCnt);
System.out.println("sort........");
Comparator<TestDomain> countCompartor = new Comparator<TestDomain>() {
public int compare(TestDomain o1, TestDomain o2) {
int o1Cnt = o1.getCount();
int o2Cnt = o2.getCount();
return o2Cnt - o1Cnt;
}
};
List<TestDomain> resultList = new ArrayList<TestDomain>();
Set<String> keySet = resultMap.keySet();
for(String key : keySet) {
resultList.add(resultMap.get(key));
}
Collections.sort(resultList, countCompartor);
System.out.println("resultList size : " + resultList.size());
System.out.println("resultList : " + resultList);
System.out.println("수행시간 : " + (endTime - startTime) / 1000 +"초");
System.out.println("수행시간 : " + (endTime - startTime) + "ms");
}
private class TestDomain implements Comparable<TestDomain>{
String keyName = "";
int count = 0;
/**
* @return the count
*/
public int getCount() {
return count;
}
/**
* @param count the count to set
*/
public void setCount(int count) {
this.count = count;
}
public TestDomain(String keyName) {
this.keyName = keyName;
}
/**
* @return the key
*/
public String getkeyName() {
return keyName;
}
/**
* @param key the key to set
*/
public void setkeyName(String keyName) {
this.keyName = keyName;
}
@Override
public String toString() {
return "keyName : ["+keyName+"], count : [" + count + "]";
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TestDomain other = (TestDomain) obj;
String otherKey = other.getkeyName();
String thisKey = getkeyName();
return thisKey.equalsIgnoreCase(otherKey);
}
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(TestDomain o) {
return keyName.compareTo(o.getkeyName());
}
}
}
view raw 1.java hosted with ❤ by GitHub


결과를 담아서 사용 할 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
//INNER CLASS
private class KeywordMergeWorker implements Runnable {
private List<TestDomain> targetWorkingList = new ArrayList<TestDomain>();
private Map<String,TestDomain> resultMap = null;
public KeywordMergeWorker(List<TestDomain> targetWorkingList, Map<String,TestDomain> resultMap) {
this.targetWorkingList = targetWorkingList;
this.resultMap = resultMap;
}
@Override
public void run() {
long loopCnt = 0;
for(TestDomain initData : targetWorkingList) {
if(resultMap.containsKey(initData.getkeyName())) {
TestDomain alreadyInput = resultMap.get(initData.getkeyName());
int orgCount = alreadyInput.getCount();
int newCount = initData.getCount();
alreadyInput.setCount(orgCount + newCount);
} else {
resultMap.put(initData.getkeyName(), initData);
}
loopCnt++;
}
}
}
view raw 2.java hosted with ❤ by GitHub

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 병렬처리
package semina.secondday.improvecode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author need4spd, need4spd@cplanet.co.kr, 2010. 7. 28.
*
*/
public class Test4 {
public static void main(String[] args) throws InterruptedException {
Test4 test = new Test4();
test.runTest();
}
public void runTest() throws InterruptedException {
//Data 초기화
List<TestDomain> list_1 = new ArrayList<TestDomain>();
List<TestDomain> list_2 = new ArrayList<TestDomain>();
List<TestDomain> list_3 = new ArrayList<TestDomain>();
List<TestDomain> list_4 = new ArrayList<TestDomain>();
List<TestDomain> list_5 = new ArrayList<TestDomain>();
List<TestDomain> list_6 = new ArrayList<TestDomain>();
List<TestDomain> list_7 = new ArrayList<TestDomain>();
List<List<TestDomain>> initDatas = new ArrayList<List<TestDomain>>();
Random random = new Random();
for(int limit = 0; limit < 7; limit++) {
for(int i = 0; i < 50000; i++) {
if(limit == 0) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_1.add(testDomain);
} else if (limit == 1) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_2.add(testDomain);
} else if (limit == 2) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_3.add(testDomain);
} else if (limit == 3) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_4.add(testDomain);
} else if (limit == 4) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_5.add(testDomain);
} else if (limit == 5) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_6.add(testDomain);
} else if (limit == 6) {
TestDomain testDomain = new TestDomain(String.valueOf(i));
testDomain.setCount(random.nextInt(3000));
list_7.add(testDomain);
}
}
}
initDatas.add(list_1);
initDatas.add(list_2);
initDatas.add(list_3);
initDatas.add(list_4);
initDatas.add(list_5);
initDatas.add(list_6);
initDatas.add(list_7);
System.out.println("initData size : " + initDatas.size());
//Data 초기화 끝.
long startTime = System.currentTimeMillis();
Map<String,TestDomain> resultMap = new ConcurrentHashMap<String,TestDomain>();
List<Thread> runnedThreads = new ArrayList<Thread>();
for(int threadCount = 0; threadCount < 7; threadCount++) {
KeywordMergeWorker worker = new KeywordMergeWorker(initDatas.get(threadCount), resultMap);
Thread thd = new Thread(worker);
runnedThreads.add(thd);
}
for(Thread thread : runnedThreads) {
thread.start();
}
for(Thread thread : runnedThreads) {
thread.join();
}
long endTime = System.currentTimeMillis();
System.out.println("sort........");
Comparator<TestDomain> countCompartor = new Comparator<TestDomain>() {
public int compare(TestDomain o1, TestDomain o2) {
int o1Cnt = o1.getCount();
int o2Cnt = o2.getCount();
return o2Cnt - o1Cnt;
}
};
List<TestDomain> resultList = new ArrayList<TestDomain>();
Set<String> keySet = resultMap.keySet();
for(String key : keySet) {
resultList.add(resultMap.get(key));
}
Collections.sort(resultList, countCompartor);
System.out.println("resultList size : " + resultList.size());
System.out.println("resultList : " + resultList);
System.out.println("수행시간 : " + (endTime - startTime) / 1000 +"초");
System.out.println("수행시간 : " + (endTime - startTime) + "ms");
}
private class KeywordMergeWorker implements Runnable {
private List<TestDomain> targetWorkingList = new ArrayList<TestDomain>();
private Map<String,TestDomain> resultMap = null;
public KeywordMergeWorker(List<TestDomain> targetWorkingList, Map<String,TestDomain> resultMap) {
this.targetWorkingList = targetWorkingList;
this.resultMap = resultMap;
}
@Override
public void run() {
long loopCnt = 0;
for(TestDomain initData : targetWorkingList) {
if(resultMap.containsKey(initData.getkeyName())) {
TestDomain alreadyInput = resultMap.get(initData.getkeyName());
int orgCount = alreadyInput.getCount();
int newCount = initData.getCount();
alreadyInput.setCount(orgCount + newCount);
} else {
resultMap.put(initData.getkeyName(), initData);
}
loopCnt++;
}
}
}
private class TestDomain implements Comparable<TestDomain>{
String keyName = "";
int count = 0;
/**
* @return the count
*/
public int getCount() {
return count;
}
/**
* @param count the count to set
*/
public void setCount(int count) {
this.count = count;
}
public TestDomain(String keyName) {
this.keyName = keyName;
}
/**
* @return the key
*/
public String getkeyName() {
return keyName;
}
/**
* @param key the key to set
*/
public void setkeyName(String keyName) {
this.keyName = keyName;
}
@Override
public String toString() {
return "keyName : ["+keyName+"], count : [" + count + "]";
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TestDomain other = (TestDomain) obj;
String otherKey = other.getkeyName();
String thisKey = getkeyName();
return thisKey.equalsIgnoreCase(otherKey);
}
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(TestDomain o) {
return keyName.compareTo(o.getkeyName());
}
}
}
view raw 3.java hosted with ❤ by GitHub


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

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

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

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

오히려 더 느려졌네요..

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

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