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 2021-02-07 05:23:33
Processing time 0.0189 sec