Languages/Java2013.12.31 11:56

1. Garbage Collection이 필요하다면


메모리 할당/반환을 처리하는 것이 너무 지겹고 고단하다면, Java를 배워야 할 필요가 있을지 모릅니다. 잘 잘려진 대로, Java는 메모리 할당과 반환에 대한 작업을 Garbage Collector를 통해 알아서 처리해 줍니다. 그 성능이 걱정되신다구요? Java는 만들어진 지 오래된 언어이고, JVM의 성능을 최적화하기 위해 오랫동안 애써 왔습니다. 그 이야기는, Java의 JVM이 제공하는 Garbage Collector의 성능이 이제 믿을만한 수준까지 도달했다는 의미이기도 합니다. (물론 Java에서도 메모리 누수 현상, 즉 Memory Leak은 발생할 수 있으므로 이를 피하기 위해서는 코딩할 때 주의해야 합니다. Effective Java 2nd Edition을 참고하세요.) 


물론 잘 최적화된 C/C++ 바이너리와의 성능을 비교하는 것은 어불성설입니다. 하지만 C++로 작성한다고 무조건 성능이 더 나을거라는 생각은 버리는 것이 좋습니다. 대체적으로, 높은 성능을 내는 것은 무슨 무슨 언어를 쓴다고 공짜로 따라오는 것이 아니라, 프로그래머의 노하우, 패턴, 최적화 등등이 함께 결합되어야 가능하기 때문입니다. 


http://neuroph.sourceforge.net/index.html



2. 어떤 라이브러리를 쓸까 고민하기 싫다면


Java에는 이미 굉장히 큰 규모의 라이브러리가 번들링되어 있습니다. 이 라이브러리들만 잘 사용해도 대다수의 작업은 무리없이 처리할 수 있습니다. 게다가, 관련된 오픈소스 프로젝트들도 많아서, 용도에 맞는 써드 파티 라이브러리를 선택할 때 자유도가 굉장히 높습니다. '무슨 무슨 일을 하는 라이브러리는 파이썬이나 C++ 밖에 없어요. 그러니 우리는 프로젝트를 C++로 진행해야...'와 같은 상황이 생길 여지가 별로 없다는 것이죠. 


굳이 예를 하나 들자면.... 여러분은 GPU 코어를 사용해 시스템 처리 성능을 높이는 방법론인 CUDA를 알고 계실 겁니다. 예전 같으면 이처럼 시스템에 아주 가깝게 다가가 있는 기능을 사용하는 프로그램을 작성할 때 C/C++ 말고는 선택할 수 있는 언어가 거의 없었겠지만, 이제 자바 사용자는 JCUDA(http://www.jcuda.org/)를 사용해서 CUDA 프로그래밍을 할 수 있습니다. 


3. 높은 이식성이 필요하다면


JVM마다 성능이 조금씩 달라지는 일은 있습니다만, 대체로 Java 프로그램의 이식성은 JVM에 의해 보장됩니다. Java 표준을 충실히 따르는 프로그램을 개발했다면, 거의 모든 플랫폼에서 재컴파일 없이도 프로그램을 돌릴 수 있습니다. 아, 물론 Microsoft VM을 사용하는 프로그램을 짰다면 그것은 예외. (묵념) 


4. 수평적 규모 확장성이 요구된다면


Hadoop을 아십니까? 이제 데이터 처리에 있어 수평적 규모확장성(horizontal scalability)이 필요할 때, Hadoop 기반의 플랫폼은 무슨 업계 표준인 것 처럼 받아들여지고 있는 실정이죠. 놀라운 것은, Hadoop이라는 플랫폼이 Java로 작성되어 있다는 것입니다. 그 말은, 시스템에 요구되는 높은 확장성을 달성할 때 중요한 것이 더 이상 언어가 아니라는 점이며, Java가 그러한 성능 요구사항을 달성할 수 있는 수준으로 진화했다는 사실입니다. 어쨌든, Hadoop이 필요하다면 여러분은 Java를 배우는 것이 좋습니다. 물론 다양한 언어 바인딩(binding)들이 나오고 있는 실정이지만 말이죠. 


물론 수평적 규모 확장성을 달성하는 방법이 Hadoop만 있는 것은 아닙니다. Hazelcast(http://www.hazelcast.com/)도 한번 구경해 보세요. Java 기반의 미들웨어가 어디까지 진화해 있는지 느끼실 수 있을 겁니다.


5. 좀 더 편하게 개발하고 싶다면 


Java 개발자들에게 있어서 Eclipse란 어떤 존재인가요? (물론 요즘은 C++/Python 등 다양한 언어의 개발 환경도 Eclipse로 통합되고 있는 실정이긴 합니다만.) 아마 대다수의 Java 개발자는 (저 포함) Eclipse 없는 개발은 상상도 하지 못할지 모르겠군요. 이 놀라운 IDE 덕분에, Java 개발자들의 개발 생산성은 vi로 코딩하고 Make로 빌드하던 초창기에는 상상도 할 수 없을 차원으로 높아졌습니다. 


게다가 여러분은 지금, Android 개발 까지도 진행할 수 있을 만큼 진보된 Eclipse를 사용하고 있습니다. 게다가 Marketplace 기능을 통합한 Eclipse는, 새로운 개발 지원 기능의 통합을 믿을 수 없을 만큼 신속하게 수행할 수 있도록 해주죠. Eclipse는 Java 개발자들의 생산성을 높여줄 뿐 아니라, Java 언어에 대한 진입 장벽 또한 낮추고 있습니다. 


SEE ALSO: 파이썬(Python)을 배워야 할 다섯가지 이유



저작자 표시 비영리 변경 금지
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 비밀댓글입니다

    2013.12.31 18:22 [ ADDR : EDIT/ DEL : REPLY ]
  2. 어? 왜 비밀글이 되었지??

    2013.12.31 18:23 신고 [ ADDR : EDIT/ DEL : REPLY ]
    • 항상 좋은 글 잘 보고있습니다. 이번 주제는 JAVA의 필요성에 대해 말씀해 주셨는데요 위와같은 사항으로 고민중인 C/C++ 프로그래머라면 개인적인 소견이지만 JAVA보다는 C#도 어떨까 싶습니다.
      1) 자동화된 메모리관리
      2) JAVA와 유사한 라이브러리들과 MSDN
      3) C/C++과 유사한 문법
      4) C/C++의 dll링크가 가능 (C++의 속도와 C#의 편의성 두 이득을 동시에...)
      5) 윈도우 뿐 아니라 리눅스에도 모노를 통해 점점 확장성이 생기고 있음
      6) 스마트폰 게임을 개발중이라면 유니티에서 제공하는 기본 언어(물론 자바도 가능하지만 C#쪽이 더 낫다더군요.. JAVA쪽 유니티는 경험하지 못했습니다.)
      ---
      코멘트 해주신 내용 다시 붙였습니다. 감사합니다.

      2013.12.31 18:38 신고 [ ADDR : EDIT/ DEL ]

Thoughts2013.11.08 11:37

페이스북이 대용량 데이터 분석 솔루션 Presto를 오픈 소스로 공개했습니다. 대용량 데이터를 많이 분석해야 하는 Facebook 특성상 이런 솔루션이 필요했을 것으로 보이는데요. (페이스북 내부적으로 300 페타바이트 이상의 데이터가 보관되어 있다고 합니다.) 2012년부터 이 솔루션을 개발하기 시작해서 드디어 오픈 소스로 공개가 가능한 시점까지 왔습니다. https://www.facebook.com/notes/facebook-engineering/presto-interacting-with-petabytes-of-data-at-facebook/10151786197628920



페이스북이 공개한 Presto 아키텍처는 위 그림과 같습니다. 글라이언트는 데이터 웨어하우스(Warehouse)에 대한 SQL 질의를 통해 데이터 분석을 시도하게 되는데, SQL 질의를 받으면 파서(Parser)와 플래너(Planner), 그리고 스케줄러(Scheduler)를 통해 최선의 질의 실행 형태를 결정하고 실행하게 되는데요. 이 과정에서 데이터에 가장 가까운 노드에 질의 수행이 맡겨지게 됩니다. 그 역할은 스케줄러가 맡는데, 질의 수행이 이루어지고 있는 전반적인 상황을 감시하게 됩니다. 데이터는 저장소에서 꺼내어져 워커(Worker)에 의해 병렬 분석 되고, 최종적으로 분석 결과가 클라이언트에게 되돌려지는 형태입니다. 


Hive나 MapReduce가 중간 결과를 다시 디스크에 쓰는 반면, Presto에서 모든 프로세싱은 메모리 상에서 이루어지고, 워커 간 파이프라인도 메모리로 구현됩니다. 따라서 불필요한 I/O가 발생하지 않기 때문에 질의 수행 시간이 줄어든다고 합니다. 


이 시스템은 Java로 구현되었는데, 개발하기 쉽고, 개발자 층이 두텁고, Java로 구현된 페이스북 내부 인프라와 연동하기 쉬워서 Java가 선택되었다고 하는군요. 질의 중 일부는 JVM 코드로 동적으로 컴파일되여, JVM에서 최적화가 가능하다고 합니다. Presto를 구현하면서 메모리 할당이나 가비지 콜렉션 과정에서 생기는 문제를 피하기 위해서 신경도 많이 썼다고 하는군요. (그 과정에서 얻은 교훈은 나중에 공개할 예정이라고 합니다. 사실 이 부분이 많이 기대되네요.) 



HDFS, Hive, HBase, Scribe같은 외부 시스템과의 연동을 위해서, 확장이 용이한 구조를 만드는 데도 신경을 썼다는군요. 위 그림은 그 구조를 밝힌 그림입니다. 


어쨌든 이런 구조를 통해 Presto는 Hive/MapReduce 시스템보다 열배 나은 성능을 달성했다고 합니다. (CPU 사용 효율, 그리고 질의 수행 시간의 측면에서 말이죠.) ANSI SQL을 대부분 지원한다는 군요. (다만 JOIN 테이블의 크기 제한 등, 몇 가지 제약조건은 잇다고 합니다.) 모든 처리 결과를 다시 테이블에 기록하는 기능은 현재로선 없답니다. (클라이언트에게 그냥 스트림 형태로 제공되기만 하는 듯.)


오픈 소스는 아래에 공개되어 있다고 하네요. 


http://prestodb.io/

https://github.com/facebook/presto



저작자 표시 비영리 변경 금지
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Java2013.04.15 11:16


Java NIO and reactor pattern.Java NIO and reactor pattern. http://www.moparscape.org/smf/index.php?topic=460976.0


The key component of java NIO is java.nio.channels.Selector. With this, you can easily monitor a specific set of events happening from underlying channels. With this, you can easily write codes to create a socket, bind the socket, listen the socket, and accept a new client connection. 


Following is an example code from a working system. 


Selector accept_selector = Selector.open();


ServerSocketChannel tcp_server = ServerSocketChannel.open();

tcp_server.socket().bind(new InetSocketAddress(6633));

tcp_server.configureBlocking(false);

tcp_server.register( accept_selector, SelectionKey.OP_ACCEPT );


//

// start accept loop

// 

int accept_seq = 0;

while ( !quit ) {

int r = accept_selector.select();


if ( r > 0 ) {

// accept set is ready

Set<SelectionKey> keys = accept_selector.selectedKeys();

for ( Iterator<SelectionKey> i = keys.iterator(); i.hasNext(); ) {

SelectionKey key = i.next();

i.remove();


if ( key.isAcceptable() ) {

int seq = ++accept_seq;

SocketChannel sw_channel = tcp_server.accept();

sw_channel.configureBlocking(false);

sw_channel.socket().setTcpNoDelay(true);

sw_channel.socket().setPerformancePreferences(0,2,3);


some_thread_object.addClient( sw_channel );

}

}

}

}



Above code is very simple, so I think no further explanation is needed. One thing to note is that you can also apply this kind of convention to the socket read. After creating sw_channel, you might pass the channel to a thread that monitors a set of connections. Then, the thread does 'select' on the set of connections, check if there are some readable connections. On the readable connections, you can actually perform 'read'. 


void addClient(SocketChannel client) {

synchronized ( guard ) {

try {

// ...

client.register( 

read_selector.wakeup(), 

SelectionKey.OP_READ | SelectionKey.OP_WRITE, 

null /* attachment */

);

} catch (ClosedChannelException e) {

// channel is closed. 

try {

client.close();

} catch (IOException e1) {

// does nothing.

}

}

}

}


Above code is to register a ClientChannel object to a 'read_selector' object, that the thread object has as its private member. One thing to note is, how the 'guard' object is used to prevent deadlock at client.register() and read_selector.select() call. This is an idiom, so please follow it. 


while ( !quit ) {

try {

// guard idiom to prevent deadlock at client.register() call

synchronized (guard) {}


int r = read_selector.select();

if ( r > 0 ) { // there's something to read.


Set<SelectionKey> keys = read_selector.selectedKeys();

for ( Iterator<SelectionKey> i = keys.iterator(); i.hasNext(); ) {

SelectionKey key = i.next();

i.remove();

try { 

if ( !key.isValid() ) {

// do something

key.cancel();

conn.close();

continue;

}

if ( key.isWritable() ) {

// do something

}

if (  key.isReadable() && !handleReadEvent(conn) ) {

// do something

key.cancel();

conn.close();

}

} catch ( CancelledKeyException e ) {

e.printStackTrace();

continue;

}

}

}

} catch (IOException e) {

e.printStackTrace();

// just break this watcher.

return;

}

}


Above codes are from the run loop of  'some_thread_object'. Basically, this loop monitors a set of channels that read and write is possible. Once the selector selected a set of keys (each key is mapped to one SocketChannel object) that are readable or writable, you should check teach channel is actually readable using key.isReadable(), or writable using key.isWritable(). If they return true, that means you are able to read from or write to the channel. 


May the NIO be with you!


저작자 표시 비영리 변경 금지
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Java2010.09.02 16:20
Java의 Reflection을 사용하면 속도는 뭐 그저 그럴지 모르지만 귀찮은 작업이 줄어들 때가 있습니다. 가령 어떤 객체의 모든 필드와 그 값의 쌍을 Map 객체 안에 저장하고 싶다거나 할때 특히 유용하죠. (그 역방향 작업이 필요할 때도 있겠죠.)

그런 작업이 필요할 때 어떤 코드를 작성하면 되는지 보시면...


import java.lang.reflect.Field;

import java.util.HashMap;

import java.util.Map;



class A {

private String p;

private String q;

A() {

p = "foo";

q = "bar";

}

public String getP() {

return p;

}


public String getQ() {

return q;

}


public Map<String, Object> toMap() {

Field[] fields = this.getClass().getDeclaredFields();

Map<String, Object> ret = new HashMap<String, Object>();

for ( int i = 0; i < fields.length; ++i ) {

try {

ret.put(fields[i].getName(), fields[i].get(this));

} catch (IllegalArgumentException e) {

e.printStackTrace();

return null;

} catch (IllegalAccessException e) {

e.printStackTrace();

return null;

}

}

return ret;

}

}



public class Test {


/**

* @param args

*/

public static void main(String[] args) {

A a = new A();

Map<String, Object> r = a.toMap();

for ( String s : r.keySet() ) {

Object o = r.get(s);

System.out.println("key:" + s + ", value:" + o.toString());

}

}


}


뭐 보시다시피 별로 어렵지는 않습니다. 그런데 이런 코딩이 대체 언제 필요하느냐...

XML-RPC 위에서 API를 구현한다거나 할 때 유용하죠. ㅋㅋ XML-RPC의 struct 타입을 Apache XML-RPC 라이브러리에서는 Map Object 형태로 처리하거든요.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 는 뭐 그저 그럴지 모르지만 귀찮은 작업이 줄어들 때가 있습니다. 가령 어떤 객체의 모든 필드와 그 값의 쌍을 Map 객체 안에 저장하고 싶다거나 할때 특히 유용하죠. (그 역방향 작업이 필요할 때도 있겠죠.)

    2010.09.29 14:19 신고 [ ADDR : EDIT/ DEL : REPLY ]
  2. 푸른컴돌

    Reflection을 사용하면 속도상의 손해를 많이 보나요?

    2011.10.05 13:58 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Java2010.01.29 19:29
요즘 시스템 퍼포먼스 성능 튜닝을 하고 있습니다. 제가 개발에 참여한 시스템이 Java로 구현되어 있어서, Java 성능 튜닝을 하는 중입니다.

그런데 작업을 하다가 아주 재미난 현상을 발견했습니다. 쓰레드 (아주 작습니다) 를 아주 빠른 주기로 생성해서 프로세싱을 하는데 (하다가 성능이 잘 안나올 것 같아서 쓰레드 풀을 만들어 쓰고 있습니다) 쓰레드 객체가 new되어서 실제로 run 되는데 까지 16ms가 (정확하게는 15.5ms쯤 되는 것 같습니다) 걸리는 현상이 1초당 20~40회 정도 목격된 것이죠.

이 현상이 최초로 목격된 것은 Windows 2003 서버였는데, 비스타나 Windows 7에서도 똑같았습니다.

그래서 쓰레드를 만드는 부분의 코드만 아주 작은 테스트 프로그램으로 분리한 다음에 Windows 시스템들에서 각각 돌려봤습니다. 똑같더군요. ㅋㅋ

같은 프로그램을 Mac OS X에서 돌려봤습니다. 거기서는 그런 현상이 관측되지 않았습니다. Mac OS X에서 실행한 그래프는 다음과 같습니다. Eclipse를 썼습니다. 프로그램 초반에는 쓰레드 풀과 객체 풀이 초기화되느라 약간의 삽질을 하는 것이 보입니다만, sample 수를 감안한다면 적당한 성능을 보입니다.


Windows 플랫폼에서는 가로줄이 0과 16ms 위치에 두 줄 그어집니다. 이 문제를 해결하기 위해서 웹 써치를 좀 했는데, 아무래도 실제로 성능이 그렇게 나오기 때문에 그런 그래프가 그려진다기 보다는, System.currentTimeMillis() 의 resolution이 Windows에서 떨어지기 때문인 것 같더군요.

이 문제를 workaround하기 위한 한 가지 방법은, (measurement가 중요한 환경이라면) System.currentTimeMillis()를 쓰는 대신 System.nanoTime()을 쓰는 것입니다. 그러면 Windows XP 상에서 다음과 같이 성능 측정 결과가 개선되는 것을 볼 수 있습니다.


Y 축은 nanosecond를 millisecond로 변환한 결과입니다. 측정된 성능은 확연하게 나아졌습니다. (Mac OS X보다 낫다고 하긴 뭐합니다. 이 위에 Mac OS X에 대해 그린 그래프는 System.nanoTime을 써서 그린 그래프가 아니라서 정확한 비교자료가 될 수 없거든요.)

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. Richpapa

    딴 얘기지만, 1. 퍼포먼스 테스트 툴은 무엇으로 하셨나요? 2. 떡밥보다 밑밥에 관심이 가네요. 그래프는 무엇으로 만들었지요?

    2010.01.29 20:50 신고 [ ADDR : EDIT/ DEL : REPLY ]