U E D R , A S I H C RSS

Java/Mode Selection Performance Test

Servlet 에서, 또는 Switch - Case 등 많은 분기를 하는 방법에 대한 디자인 & 퍼포먼스 관점에서의 구경.

1. if-else

대강 다음의 코드 스타일일것이다.
~cpp 
public class IfElse {
    public void printPerformance(String[] modeExecute) {
        long start;
        long end;
        start = System.currentTimeMillis();
        for (int i = 0; i < ModeChoicePerformanceTest.LOOPING_COUNT; i++) {
            executeIfElse(modeExecute);
        }
        end = System.currentTimeMillis();
        System.out.println("if - else elapsed time :" + (end - start) + "ms ");
    }

    private void executeIfElse(String[] modeExecute) {
        for (int i = 0; i < modeExecute.length; i++) {
            executeWithIfElse(modeExecute[i]);
        }
    }

    public void executeWithIfElse(String mode) {
        if (mode.equals("One")) {
            doOne(1);
        } else if (mode.equals("Two")) {
            doTwo(1);
        } else if (mode.equals("Three")) {
            doThree(1);
        } else if (mode.equals("Four")) {
            doFour(1);
        } else if (mode.equals("Five")) {
            doFive(1);
        } else {
            doDefault(1);
        }
    }


    public void doOne(int i) {
        i = 10;
    }

    public void doTwo(int i) {
        i = 10;
    }

    public void doThree(int i) {
        i = 10;
    }

    public void doFour(int i) {
        i = 10;
    }

    public void doFive(int i) {
        i = 10;
    }

    public void doDefault(int i) {
        i = 100;
    }
}
이 방법은 일단 속도상으론 가장 빠르다. 하지만, 한편으로
Seminar:WhySwitchStatementsAreBadSmell에 걸리지 않을까? 근데.. 그에 대한 반론으로 들어오는것이 '이건 mode 분기이므로 앞에서의 Switch-Statement 에서의 예와는 다른 상황 아니냐. 어차피 분기 이후엔 그냥 해당 부분이 실행되고 끝이다.' 라고 말할것이다. 글쌔. 모르겠다.
한편으로 느껴지는 것으로는, switch 로 분기를 나눌 mode string 과 웹 parameter 와의 중복이 있을 것이라는 점이 보인다. 그리고 하나의 mode 가 늘어날때마다 해당 method 가 늘어나고, mode string 이 늘어나고, if-else 구문이 주욱 길어진다는 점이 있다. 지금은 메소드로 추출을 해놓은 상황이지만, 만일 저 부분이 메소드로 추출이 안되어있다면? 그건 단 한마디 밖에 할말이 없다. (단, 저 논문을 아는 사람에 한해서) GotoStatementConsideredHarmful.

두번째 - Method reflection

~cpp 

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class MethodFullReflection {
    public void printPerformance(String[] modeExecute) throws InvocationTargetException, IllegalAccessException {
        long start;
        long end;
        start = System.currentTimeMillis();
        for (int i = 0; i < ModeChoicePerformanceTest.LOOPING_COUNT; i++) {
            executeReflection(modeExecute);
        }
        end = System.currentTimeMillis();
        System.out.println("elapsed time :" + (end - start) + "ms ");

    }

    private void executeReflection(String[] modeExecute) throws InvocationTargetException, IllegalAccessException {
        for (int i = 0; i < modeExecute.length; i++) {
            Method method = null;
            try {
                method = this.getClass().getMethod("do" + modeExecute[i], new Class[]{int.class});
                method.invoke(this, new Object[]{new Integer(1)});
            } catch (NoSuchMethodException e) {
                this.doDefault(1);
            } catch (SecurityException e) {
                e.printStackTrace();  //To change body of catch statement use Options | File Templates.
            }
        }
    }

    private void doDefault(int i) {
        i = 100;
    }

    public void doOne(int i) {
        i = 10;
    }

    public void doTwo(int i) {
        i = 10;
    }

    public void doThree(int i) {
        i = 10;
    }

    public void doFour(int i) {
        i = 10;
    }

    public void doFive(int i) {
        i = 10;
    }

}
장점 : MODE 가 추가될때마다 doXXX 식으로 이름을 정해주고 이를 실행하면 된다. 조건 분기 부분의 코드가 증가되지 않고, 해당 Mode 가 추가될때마다 메소드 하나만 추가해주면 된다.
단점 : 자바에서는 Method Reflection & Invoke 가 엄청 느리다.; 속도는 밑의꺼 참조.

세번째. 위의 방법을 보완한 방법이다. 바로 일종의 Table Lookup.

~cpp 

import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

/**
 * User: Administrator Date: 2003. 7. 12. Time: 오전 12:48:38
 */
public class MethodTableLookupReflection {
    public void printPerformance(String[] modeExecute) throws InvocationTargetException, IllegalAccessException {
        long start;
        long end;
        start = System.currentTimeMillis();
        initReflectionMap(modeExecute);
        end = System.currentTimeMillis();
        System.out.println("reflection with method initialize table elapsed time :" + (end - start) + "ms ");

        start = System.currentTimeMillis();
        for (int i = 0; i < ModeChoicePerformanceTest.LOOPING_COUNT; i++) {
            executeReflectionWithMapping(modeExecute);
        }
        end = System.currentTimeMillis();
        System.out.println("reflection with method elapsed time :" + (end - start) + "ms ");

    }

    private static Map methodMap;


    private void executeReflectionWithMapping(String[] modeExecute) throws InvocationTargetException, IllegalAccessException {
        for (int i = 0; i < modeExecute.length; i++) {
            Method method = (Method) methodMap.get(modeExecute[i]);
            if (method != null)
                method.invoke(this, new Object[]{new Integer(1)});
            else
                doDefault(1);
        }
    }

    private void initReflectionMap(String[] methodNames) {
        methodMap = new HashMap();
        for (int i = 0; i < methodNames.length; i++) {
            try {
                methodMap.put(methodNames[i], this.getClass().getMethod("do" + methodNames[i], new Class[]{int.class}));
            } catch (NoSuchMethodException e) {
            } catch (SecurityException e) {
            }
        }

    }

    private void doDefault(int i) {
        i = 100;
    }

    public void doOne(int i) {
        i = 10;
    }

    public void doTwo(int i) {
        i = 10;
    }

    public void doThree(int i) {
        i = 10;
    }

    public void doFour(int i) {
        i = 10;
    }

    public void doFive(int i) {
        i = 10;
    }
}
이 방법은 위의 방법과 같은 장점을 지니면서 퍼포먼스를 거의 10배가량 향상시킨다.

네번째. Inner Class 에 대해 Command Pattern 의 사용.

~cpp 

import java.util.Map;
import java.util.HashMap;

/**
 * User: Administrator Date: 2003. 7. 12. Time: 오전 12:57:7
 */
public class InterfaceTableLookup {
    protected Map modeMap = new HashMap();

    public void printPerformance(String[] modeExecute) {
        long start;
        long end;
        start = System.currentTimeMillis();
        initModeMap();
        end = System.currentTimeMillis();
        System.out.println("interface table lookup init table elapsed time :" + (end - start) + "ms ");

        start = System.currentTimeMillis();
        for (int i = 0; i < ModeChoicePerformanceTest.LOOPING_COUNT; i++) {
            executeInnerclassMapping(modeExecute);
        }
        end = System.currentTimeMillis();
        System.out.println("interface table lookup elapsed time :" + (end - start) + "ms ");


    }

    private void executeInnerclassMapping(String[] modeExecute) {
        for (int i = 0; i < modeExecute.length; i++) {
            executeMode(modeExecute[i]);
         }
    }

    private void executeMode(String s) {
        IMode mode = (IMode) modeMap.get(s);
        if (mode == null) doDefault(1);
        else mode.execute(1);
    }

    public class ExOne implements IMode {
        public void execute(int i) {
            i = 10;
        }
    }

    public  class ExTwo implements IMode {
        public void execute(int i) {
            i = 10;
        }
    }

    public class ExThree implements IMode {
        public void execute(int i) {
            i = 10;
        }
    }

    public class ExFour implements IMode {
        public void execute(int i) {
            i = 10;
        }
    }

    public class ExFive implements IMode {
        public void execute(int i) {
            i = 10;
        }
    }

    private void initModeMap() {
        modeMap.put("One", new ExOne());
        modeMap.put("Two", new ExTwo());
        modeMap.put("Three", new ExThree());
        modeMap.put("Four", new ExFour());
        modeMap.put("Five", new ExFive());
    }
}
이 방법은 initModeMap 에서 매번 Mode에 대한 등록을 해줘야 한다. 퍼포먼스는 Method Reflection 보다 훨씬 빠르다.

마지막 방법 - interface & reflection

위의 방법에 initModeMap 을 reflection 으로 처리한 것이다.
~cpp 
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * User: Administrator Date: 2003. 7. 12. Time: 오전 1:2:16
 */
public class InterfaceTableLookupReflection {
    public void printPerformance(String[] modeExecute) throws NoSuchMethodException, InstantiationException, ClassNotFoundException, InvocationTargetException, IllegalAccessException {
        long start;
        long end;
        start = System.currentTimeMillis();
        initModeMapWithReflection(modeExecute);
        end = System.currentTimeMillis();
        System.out.println("interface reflection & table lookup init able elapsed time :" + (end - start) + "ms ");

        start = System.currentTimeMillis();
        for (int i = 0; i < ModeChoicePerformanceTest.LOOPING_COUNT; i++) {
            executeInnerclassMapping(modeExecute);
        }
        end = System.currentTimeMillis();
        System.out.println("interface reflection & table lookup elapsed time :" + (end - start) + "ms ");

    }

    private void executeInnerclassMapping(String[] modeExecute) {
        for (int i = 0; i < modeExecute.length; i++) {
            executeMode(modeExecute[i]);
         }
    }

    private void executeMode(String s) {
        IMode mode = (IMode) modeMap.get(s);
        if (mode == null) doDefault(1);
        else mode.execute(1);
    }

    protected Map modeMap = new HashMap();
    public final static String modeClassHeader = "Ex";
    String expectedClassNameHeader = this.getClass().getName() + "$" + modeClassHeader;

    private void initModeMapWithReflection(String[] modeExecute) throws InstantiationException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Class[] consParamClasses = new Class[]{this.getClass()};
        Object[] consParams = new Object[]{this};


        Class[] inners = this.getClass().getClasses();
        for (int i=0;i<inners.length;i++) {
            if (inners[i].getName().startsWith(expectedClassNameHeader)) {
                Constructor innerCons = inners[i].getDeclaredConstructor(consParamClasses);
                modeMap.put(modeExecute[i], innerCons.newInstance(consParams));
            }
        }
    }

    public void doDefault(int i) {
        i = 100;
    }

    public class ExOne implements IMode {
        public void execute(int i) {
            i = 10;
        }
    }

    public  class ExTwo implements IMode {
        public void execute(int i) {
            i = 10;
        }
    }

    public class ExThree implements IMode {
        public void execute(int i) {
            i = 10;
        }
    }

    public class ExFour implements IMode {
        public void execute(int i) {
            i = 10;
        }
    }

    public class ExFive implements IMode {
        public void execute(int i) {
            i = 10;
        }
    }
}
이 방법은 Mode 추가시 그냥 ExModeName 식으로 추가해주면 된다. 그러면서 Mode 조건 분기 부분이 변하지 않는다. Reflection으로 table lookup 채우는 부분이나 Mode 조건 분기 부분을 아에 상위 클래스로 추출할 수 있다. 퍼포먼스면에서는 의외로 앞에서 수동으로 map 을 채우는 방법과 같다. 유연성과 퍼포먼스 두가지가 적절히 어울어지는 방법이다.


이건 위의 테스트들을 한번에 실행시키기 위한 runner class.
~cpp 
import java.lang.reflect.InvocationTargetException;

/*
  평가하려는 조건들 :
  1. 해당 method 의 naming 으로 reflection call.
  2. switch - case 로 mode 구분.
  3. interface & class - command pattern 의 구현.

  평가요소들
  1. 해당 코드 작성대비 늘어나는 반복 작업들
  2. performance
  3. maintance
*/
public class ModeChoicePerformanceTest {
    public static final int LOOPING_COUNT = 1000000;

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, InstantiationException {
        String[] modeExecute = new String[]{"One", "Two", "Three", "Four", "Five", "hugu"};

        new IfElse().printPerformance(modeExecute);
        new MethodFullReflection().printPerformance(modeExecute);
        new MethodTableLookupReflection().printPerformance(modeExecute);
        new InterfaceTableLookup().printPerformance(modeExecute);
        new InterfaceTableLookupReflection().printPerformance(modeExecute);
    }
}

결과

~cpp 
if - else elapsed time :611ms 
elapsed time :61889ms 
reflection with method initialize table elapsed time :0ms 
reflection with method elapsed time :6459ms 
interface table lookup init table elapsed time :10ms 
interface table lookup elapsed time :741ms 
interface reflection & table lookup init able elapsed time :10ms 
interface reflection & table lookup elapsed time :731ms 
Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2009-05-27 07:09:19
Processing time 0.1030 sec