Java 프로세스의 종료시점 후킹

자신이 개발하고 있는 어플리케이션을 완전히 제어하고픈 개발자(나를 포함한..)에게 Java에 매우 좋은 API를 제공합니다. 바로 Java 프로세스의 종료 시점을 어떠한 상황에서든 잡아 내 그 시점에서 원하는 코드를 실행하게 할 수 있는 방법인데요.. 아래의 간단한 코드를 살펴 보는 것으로 시작하겠습니다.

public class TestMain {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("processing something in main(" + 
            Thread.currentThread().getName() + ") start...");
  
        Runtime rt = Runtime.getRuntime();
  
        rt.addShutdownHook(
            new Thread() {
                public void run() {
                    System.out.println("======================");
                    System.out.println("do arrange resource !!");
                    System.out.println("======================");
            }
        } );
  
        System.out.println("Waiting while 10 seconds or hit ^C for exit.");
  
        Thread.sleep(10000);
        System.out.println("processing something in main end...");
        System.exit(0);
  
        System.out.println("this code never run!");
    }
}

핵심은 바로 Runtime.getRuntime()으로 얻어진 Runtime 타입의 인스턴스입니다. 이 인스턴스에 addShutdownHook 매서드를 통해 자바 가상 머신이 종료하는 시점에서 실행하는 코드를 개발자가 유연하게(얼마까지 유연할지는 모르겠지만…) 붙일 수 있습니다. 자바 어플리케이션이 콘솔이여서 ^C를 눌러 강제로 종료시키든… 예외나 에러가 발생하든 Runtime의 addShutdownHook로 지정한 스레드의 실행(run) 로직은 반드시 실행된다는 점입니다.

주어진 좌표와 선분 사이의 주어진 거리에 위치하는 선분의 좌표 구하기

사용자 삽입 이미지
제목이 난해하니 먼저 그림부터 보였습니다. 주어진 선분이 있습니다. 이 선분의 시작점은 (X1, Y1)이고 끝점은 (X2, Y2)입니다. 그리고 주어진 좌표가 있으며 (a, b)입니다. 이 선분과 좌표에 대해서 거리 ln를 가지는 선분상의 좌표를 구하는 것에 대한 정리 포스트입니다. 즉, 위의 그림에서 파란색 점은 주어진 좌표이고 빨간 점을 구하겠다는 것입니다.

먼저 선분에 대한 아래와 같은 매개변수 방정식을 정합니다.

사용자 삽입 이미지
우리가 구해야할 점은 선분상의 점이니 위의 매개변수 방정식에서 x와 y가 바로 우리가 원하는 값입니다. 이 x와 y를 구하기 위해서는 매개변수 t를 구하면 됩니다. 아시겠지만 t가 주어진 선분위에 존재하려면 0~1사이의 값이여야 합니다. 이 값을 벗어나면 답은 없음… 입니다.

이 한가지 관계만 가지고는 않됩니다. 또 하나의 관계를 맺어줘야 합니다. 그 관계는 주어진 좌표(a, b)와 구하고자 하는 선분상의 점(x, y)사이의 거리가 값 ln이라는 사실로부터 다음과 같은 식을 얻을 수 있습니다.

사용자 삽입 이미지
이제 처음 선분에 대한 방정식을 위의 방정식의 x, y에 대입하고 t에 대해 정리를 하면 아래와 같은 t에 대한 2차 방정식이 도출되며 이 2차 방정식을 근의 공식을 통해 t를 구해 보면 다음과 같습니다.

사용자 삽입 이미지
이렇게 구한 t에 대해서 범위가 0~1사이 인지를 검사하고 이 범위에 있다면 이 t를 선분의 방정식에 대입하여 구한 (x, y)가 구하고자 하는 좌표입니다.

Java와 C의 zip 압축 연산에 대한 퍼포먼스 비교

먼저 C의 zip 압축은 Jean-loup Gailly님이 만들어 공개한 zlib 1.2.2를 사용했습니다. C/C++에서 데이터의 압축에서 사용하는 압축 라이브러리는 흔히 이 zlib를 사용합니다.  그리고 Java에서 압축은 기본적으로 제공하는 java.util.zip.Deflater 클래스를 사용했습니다. 테스트를 한 이유는 Java가 C/C++에 비해서 얼마나 느릴까… 하는 기대였습니다. =_=;

Java와 C/C++ 모두 사용한 압축 데이터는 0.2메가 정도되는 jpg 파일로 했습니다. 그리고 Java와 C/C++ 모두 결과는 압축 레벨을 3으로 했을때 동일한 결과와 크기였으며 원본 크기에 비해 75% 정도의 압축되었습니다. 결과는 다음 같습니다. 첫번째 이미지는 Java의 결과이고.. 두번째는 C/C++의 결과입니다.

사용자 삽입 이미지
와우!! Java가 C/C++에 비해 상당히 느릴것으로 기대했는데… 그렇지 않았습니다. C/C++와 성능은 거의 비슷한 것으로 생각됩니다. 두 경우 모두 최악의 경우 0.016초정도 소요됩니다. 다만…. Java의 경우 가끔씩 튀는 부분이 있었는데.. 0.031초 정도 소요되는 부분이 가끔 나옵니다. 아마도 Java의 gc기능 때문이 아닌가… 가볍게 짐작해봅니다. 하지만 이런 부분은 제외하면 정말 C/C++과 같은 Native 컴파일러 못지 않은 성능이라고 판단됩니다.

아래는 Java에서 퍼포먼스 테스트로 사용했던 코드입니다.

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import java.io.*;

public class TestMain {
    public static void main(String[] args) 
        throws IOException, DataFormatException
    {
        FileInputStream fis = new FileInputStream("d:/a.jpg");
        ByteArrayOutputStream baos = new ByteArrayOutputStream(fis.available());
        byte [] buffer = new byte[512];
        int cntRead;
        while((cntRead=fis.read(buffer)) != -1) {
            baos.write(buffer, 0, cntRead);
        }

        byte[] compressedBytes = null;
        for(int i=0; i<20; i++) { // 20 times repeat..
            System.out.println("Performance Test Start...");
            long stime = System.currentTimeMillis();
 
            compressedBytes = Compress(baos.toByteArray());
   
            long etime = System.currentTimeMillis();
            System.out.println("Performance Test Result : " 
                + (etime-stime)+" MS.");
        }   
 
         FileOutputStream fos = new FileOutputStream("d:/a_java.jpg.zip");
         ByteArrayInputStream bais = new ByteArrayInputStream(compressedBytes);
         while((cntRead=bais.read(buffer)) != -1) {
             fos.write(buffer, 0, cntRead);
         }
    }

    private static byte[] Compress(byte[] bytesToCompress) throws IOException
    {
        Deflater compressor = new Deflater(3);
        compressor.setInput(bytesToCompress);
        compressor.finish();
 
        ByteArrayOutputStream bos = 
            new ByteArrayOutputStream(bytesToCompress.length);
 
        byte[] buf = new byte[bytesToCompress.length + 100];
        while (!compressor.finished())
        {
            bos.write(buf, 0, compressor.deflate(buf));
        }

        bos.close();
 
        return bos.toByteArray();
    }
}

대략 살펴보시면 위의 코드에는 IO에 대한 Buffer 기능과 같은.. 여전히 최적화의 여지가 남아 있습니다. 더 이상 제가 갖고 있는 “자바는 느리다”라는 선입견이 상당 부분 깨진 느낌입니다.

텍스트 효과 단위기능 테스트

구글맵이나 네이버지도, 다음지도 등등 모든 포털 지도사이트에서 제공하는 지도 서비스는 지도 이미지를 256×256 픽셀 크기로 잘려진 타일이미지 형태로 서비스되고 있습니다. 자사만의 지도 타일이미지 렌더링 프로그램을 직접 개발한 곳도 있을테고…. 구글처럼 오픈소스 라이브러리를 이용해 구축한 곳도 있을테고….

현재  RIA 기반의 지도 솔루션 개발에 필요한 지도 타일 이미지 렌더러를 개발하고 있습니다. 오픈소스를 활용하여 개발할까… 생각도 해보았지만… 다양한 사용자가 원하는 자신만의 지도 스타일의 충족이라는 틈새 시장을 놓고 볼때… 오픈소스의 획일화된 지도 이미지 스타일은 제 기획 의도와 맞지 않다고 판단하여 직접 개발하기로 결정하게 되었습니다.

사용자 삽입 이미지
위의 결과 이미지는 현재 개발중인 지도 타일이미지 렌더러에서 텍스트 표현을 위해 고민해본 텍스트 효과입니다. 실제 종이 지도를 살펴보고 글자에 적용된 효과를 살펴보면서 위의 3가지 효과정도면 될듯하여 하나 하나 구현해 보았습니다.

추후 타일 이미지 렌더러… 개발이 완료되면 위의 텍스트 효과가 적용된 지도를 통해 좀더 분명한 결과를 알릴 수 있겠지만.. 지금 단계에서 현재 제 스스로가 무엇을 하고 있는지… 어느 방향으로 가고 있는지에 대해 한 조각이라도 구체화하기 위한 과정으로써의 글을 올려봅니다.

[C++] XML 파서, CMarkup

XML 데이터를 쓰고 읽기 위해서 인터넷을 검색하던 차에 속도를 강점으로 내세우면서 STL 만을 사용하여 플랫폼 이식에도 뛰어난 오픈소스를 찾았는데요. 바로 CMarkup 입니다. 다운로드 사이트는 http://www.firstobject.com/ 이구요. 사용해 보니 XML의 charset도 지원하여 더욱 믿음이 가는 오픈소스였습니다. XML을 처리할 일이 있다면 한번 사용해 보시기 바랍니다.

추후 이 오픈소스를 다시 사용할 때를 대비하여 사용 방법을 정리해 정리차원에서 올려봅니다.

컴파일 시 주의할 사항은… CMarkup은 MFC의 CString와 STL의 string에 대한 문자열 타입을 사용하며 기본적으로 CString을 사용합니다. 플랫폼에 독립적인 구성을 위해서 STL을 사용하는것이 좋기 때문에 전처리에서 MARKUP_STL를 정의해줘야 합니다. 이 전처리 정의는 프로젝트의 속성 페이지에서 해줌으로써 전역적으로 적용되도록 해야 합니다. 아래의 코드는 XML을 쓰는 예제 코드입니다. 윈도우즈 계열의 개발툴인 VS2008로 작성했습니다.

#include "stdafx.h"
#include "../Markup.h"

int _tmain(int argc, _TCHAR* argv[])
{
    CMarkup xml;

    xml.AddElem( "ORDER" );
    xml.AddChildElem( "ITEM" );
    xml.IntoElem();
    xml.AddAttrib("type", "A");
    xml.AddChildElem( "SN", "132487A-J" );
    xml.AddChildElem( "NAME", "crank casing" );
    xml.AddChildElem( "QTY", "1" );
    xml.OutOfElem();

    xml.AddChildElem( "ITEM" );
    xml.IntoElem();
    xml.AddAttrib("type", "C");
    xml.AddChildElem( "SN", "434417F-Y" );
    xml.AddChildElem( "NAME", "kully casing" );
    xml.AddChildElem( "QTY", "2" );
    xml.OutOfElem();

    std::string csXML = xml.GetDoc();

    printf("%s", csXML.c_str());

    return 0;
}

결과는 다음과 같습니다.

사용자 삽입 이미지
AddElement를 통해 엘리먼트를 만들고, 해당 엘리먼트의 자식을 추가하기 위해 AddChildElem을 사용하거나 먼저 IntoElem을 호출한 후 다시 AddElement를 사용합니다.

이제 반대로 위와 같은 XML 데이터를 읽어 보도록 하겠습니다. 먼저 위의 결과를 파일로 저장해 놓고 그 파일을 읽어 데이터를 추출하는 예제를 들어 보겠습니다. 예를 들어… 아이템(ITEM)의 이름(NAME)과 수량(QTY)을 읽어 보도록 하겠습니다. 위의 XML 문자열의 경우 아이템의 개수는 총2개이므로 2개가 검색될 것입니다. 다음이 이 예제와 부합되는 코드입니다.

CMarkup xml;
xml.Load("d:/data.xml");

while ( xml.FindChildElem("ITEM") )
{
    xml.IntoElem();

    xml.FindChildElem( "NAME" );
    std::string csSN = xml.GetChildData();
    xml.FindChildElem( "QTY" );
    int nQty = atoi(xml.GetChildData().c_str());

    xml.OutOfElem();

    printf("%s, %d\n", csSN.c_str(), nQty);
}

먼저 XML 데이터를 가지고 있는 파일을 Load 매서드를 이용해 읽습니다. 다시 반복문을 사용하여 루트 엘리먼트의 자식 ITEM 엘리먼트를 검색하기 위해 FindChildElem을 사용합니다. 그러면 해당되는 자식 엘리먼트가 추출됩니다. 해당되는 자식 엘리먼트의 자식 엘리먼트를 읽기 위해 IntoElem() 함수를 사용한 뒤에 원하는 엘리먼트(NAME, QTY)를 검색하기 위해 FindChildElem을 호출하고 실제로 값을 읽기 위해서 GetChildData 매서드를 호출합니다. 다 읽은 후 OutOfElem()을 호출합니다. 결과는 아래와 같습니다.

사용자 삽입 이미지