요즘 회사에서 또다시 차트 라이브러리를 제작중.
(프로젝트명은 내마음대로 “Ant chart”. 사실 난 오리콘 차트 같은걸로 하고 싶었는데 왜색이 짙다는 의견을 수렴해서 더 짧은걸로 바꿨다. 빌보드 차트역시 선상에 몰랐지만 너무 길어서…. 역시 네이밍을 위해 짧은게 좋다.)
목표는 1000만건의 데이타를 광속으로 찍어주는 라이브러리다. 뭐 사실 그림을 그리리는 퍼포먼스에 영향을 미치는 종류는 대략 3개정도 밖에 되지 않는다. 스캐터플롯과 라인플롯, 바 플롯
나머지 히스토그램이니 파이니 박스 플롯이니 하는것들은 실제 데이타를 가공해서 출력(일반적으로 그룹핑)하니 출력을 위한 데이터는 줄어들어 버려서 상대적으로 차이가 덜하다.
뭐 어쨋거나 이런짓 저런짓 하면서 좀더 빨리 찍어줄 방법이 없을까 테스트를 해본 결과를 올린다.
일단 GDI+의 경우는 매트릭스에 의한 변환(이동, 회전, 확대)을 사용할수 있는데 그건 안썼다.
일단 차트라는건 데이타를 정확하게 표현하는게 중요한데 데이타를 표현하는 점(이하 마크)은 사람들이 명확하게 보기 위해서 데이타의 범위에 관계 없이 일정한 크기를 갖는다.(5*5 크기의 원이거나 사각형이거나) 문제는 매트릭스 변환중에 하나의 확대/축소 변환으로 데이타 범위에 대한 계산을 생략해 버리면 이 마크의 크기도 변해 버린다.
게다가 일반적인 좌표축과 윈도우 좌표체계는 y축의 방향이 반대다. 이걸 역시 확대변환(y축에 -1)로 변환하면…. 심지어 문자열 출력할때도 뒤집혀 버린다.
뭐 하여간 해결하지 못한 이런저런 이유로 GDI+에서 제공하는 변환은 사용하지 못하고 위치계산 범위계산 등등은 어쩔수없이 직접 코딩을 해야 할것 같다.(뭐.. 그리 복잡하진 않지만)
그리고 마크를 출력할때(자꾸 마크의 출력을 강조하는 이유는 1000만건의 데이타를 출력한다는건 1000만개의 마크를 찍는다는 의미라서) 처음에는 DrawRectanlge 등의 백터이미지에 해당하는 메소드를 호출하는 방법을 생각했었는데 나중에 아예 마크자체를 비트맵으로 만들어 두고 그걸 찍는게 생각났다. 그래서 테스트!
위의 그림이 결관데 일단 테스트는 대략 4만건 정도의 X,Y 두개의 필드가 있는 데이타를 사용했다.
위에서 부터
GDI+ 의 CachedBitmap 이란 객체로 마크를 만들어서 찍은 경우
GDI+ 의 Bitmap 객체로 마크를 만들어두고 찍은 경우
Graphics 객체의 DrawRactangle, FillRactangle 을 사용한 경우
GraphicsPath 객체를 사용해서 백터이미지를 버퍼에 보아서 일정 개수 만큼씩(만개) 찍어준 경우.
이다.
가장 속도가 빠른건 CachedBitmap을 사용한 경우.
GDI+의 문서를 읽어보니 Bitmap 객체와의 차이점은 현재 출력을 위해 사용하는 윈도우 디바이스 객체에 종속되서 생성되는 객체라고 한다. Bitmap은 독립적인 객체라 실제 찍어줄때는 다시 변환하는 과정을 거쳐서 CachedBitmap 보다 느리다고 한다.
실제로 CachedBitmap 의 경우 Bitmap 객체를 생성자의 인자로 받아 생성하게 된다.
그리고 뒤의 두개는 백터 이미지 출력방식.
그냥 하나 하나 그려주는 방법보단 역시 버퍼에 모았다가 한번에 그려주는 방식이 빠르긴 하다.
근데 GraphicsPath를 사용할때의 문제점은… 마크 하나하나가 독립적으로 그려져야 하는데 GDI+에서 제공하는 인터페이스상 그렇게 그릴수가 없다.
GraphicsPath 의 사용법은 AddRectangle을 호출해서 사각형을 마구 버퍼에 집어넣고 Graphics객체의 DrawPath(맞나?), FillPath를 이용해서 그리게 된다. Draw 함수는 선만을 그리고 FillPath는 안의 색을 칠하는데 이 두과정이 번갈아 가면서 일어나야 독립된 사각형(마크)를 그리는데 그게 안된다. 즉 모든 사각형의 면을 칠하고 나서 다시 처음부터 모든 사각형의 선을 칠한다. 그러면? 서로 겹쳐져 있는 마크들은 선이 겹쳐진다. 가장 마지막 그림의 데이타들의 면부분이 없는 이유다.
흠 이런 저런 테스트를 해서 일단은 마크는 CachedBitmap을 사용해서 그릴려고 생각하는데 지금 막히는 문제는… CachedBitmap은 복사생성자나 operator= 가 private로 되어있다. 뭔 이윤지는 정확히 모르겠지만… 이 이유땜시 클래스로 랩핑하는데 좀 에로 사항이 꽃피고 있다.
음… 내일은 이부분하고 RTTI좀 생각해 봐야겠다.
아 데이타클래스도 만들어야 되는데. 콘란하군.
..그럴 듯 한데?