관리 메뉴

HAMA 블로그

Go vs Rust vs C++ vs Java 등 벤치마크 이야기 본문

소프트웨어 사색

Go vs Rust vs C++ vs Java 등 벤치마크 이야기

[하마] 이승현 (wowlsh93@gmail.com) 2020. 3. 11. 21:23

한국에선 Go가 1등, 세계적으론 Rust가 1등 - 사랑하고 배우고 싶은 언어 

1. 시작하면서

언어에 대한 벤치마크에 있어서 보편적, 절대적이란것은 없으며, 팀의 숙련도, 제품의 특징등에 따라서 성능/개발속도는 천차만별 일 것이다. 이 포스트는 그 동안 블록체인 연구만 하느라 잊어버린 코더라는 나의 정체성을 일 깨우고, 각 언어에 대해서 일단 빠른 시간내에 손가는 대로 만들면 어떻게 되는지 재미로 만든 것이며, 옵티마이징에 대한 부분은 전혀 신경 쓰지 않았기 때문에 객관적 지표로 삼을 수는 없을 것이다. 특히 CPU점유율 같은건 신경도 안썼다. 락을 안걸고 CPU 펌핑시키면 성능은 엄청 올라 간다. (참고로 코드 품질은 안드로메다) 이 벤치마크는 주로 생산자-소비자 패턴에 대한 테스트이며, 쓰레드끼리 데이터를 주고 받는 과정을 시뮬레이션 하였다. 현재 Future등을 통한 비동기 행위는 없으며 sequential(blocking)하게 통신이 이루어 진다. 

2. 벤치마크를 위한 심플 프로젝트

하이퍼레저 패브릭이라는 허가형 블록체인 시스템의 미니멀 버전으로써 코드의 주요 흐름은 2가지가 있는데 

1. 장부에 쓰기
   1-1. endorser들에게 보증 받기  <--- 벤치마크는 이 부분에서만 
   1-2. orderer/committer에게 값 보내서 영구저장 시키기
2. 장부에서 읽기

아래에 모듈간의 흐름을 그려보았다.

쓰기 순서

브라우저에서 신용장을 만들고 싶어서 해당 데이터를 백엔드로 보내고, 벡엔드에서는 하이퍼레저 패브릭 시스템과 통신할 수 있는 SDK를 호출 한다. 이 부분이 코드에서 미들웨어이다.

1. 미들웨어는 Endorsing Peer들에게 트랜잭션(Proposal request)을 요청함
2. Endorsing Peer 들은 해당 트랜잭션에 대한 체인코드를 호출하여 실행하고 결과값(RWSet)을 미들웨어로 돌려줌
3. 미들웨어는 RWSet을 받아서 보증에 대한 확인후 이상없으면 Orderer에 RWSet을 보낸다.
4. Orderer들은 받은 트랜잭션을  Kafka의 채널에 Push하고 , Pull 하여 정렬.
5. Orderer는 정렬된 트랜잭션 모음을 받아서 블록으로 가공.
6. 가공된 블록을 Commit Peer로 보내고
7. Commit Peer는 블록을 검증하고 Ledger (Blockchain + State Storage)에 저장.

읽기 순서
브라우저에서 신용장을 읽고 싶어서 백엔드로 요청. 벡엔드에서는 하이퍼레저 패브릭 시스템과 통신할 수 있는 SDK를 호출.

1. 미들웨어는 Commiting Peer에게 트랜잭션을 요청함
2. Commit Peer는 Ledger (State Storage)에서 정보를 가져온다.
3. 미들웨어(클라이언트)에 반납.

3. 결과

언어 성능 구현
기간
사용기간  요약   
Go 1.14

1 (1) 

1일 2년 외부라이브러리 사용없이 언어자체로 지원하는 고루틴과 채널을 이용하여 구현함. Actor패턴보다 CSP가 더 재밌고 편하다는 것을 다시 한번 느꼈으며 디버깅을 할 필요가 없이 가장 빠르게 완성함. 편의성/성능 최강 
C++ 17 16 (4.2) 3일 13년 unique_lock과 condition을 직접 사용하여 concurrent queue를 만들어서 구현함. C++경력이 제일 길지만 잠시만 멀리 했다 다시 하면서 디버깅하는데 애를 먹었다.  C++의 동시성 지원은 모던 C++ (11버전 이후) 들어서, 자바의 메모리모델을 참고하여 메모리 모델도 생기고, Thread, Lock도 생기는 등 혁신을 시작했으며 C++20 를 통해 거의 완성 될 것이다. 하지만 역시 신세대 언어들에 비해 모듈 관리 편의성 및 유틸리티류가 부족.. 크로스플랫폼으로 가면...후 
Java 11 1.6 (2) 1.5일 6개월 lock과 condition도 사용하였으며, 의도적으로 LinkedBlockingQueue(블로킹) 과 concurrentLinkedList(비동기)를 혼합하여 사용하였다. java는 옛날 1.5버전부터 끝내주는 동시성 라이브러리를 많이 제공해 주고 있기 때문에 이것 저것 해볼게 많다. 
Scala 2.12 9.8(1.2) 2.5일 3개월 akka의 actor패턴을 사용하여 구현하였다. go루틴/채널이 기본적인 것들만 지원하는것에 비해 정말 많은 것들을 지원해주고 있으며, 그 만큼 복잡하다. 양면의 칼!! 오랜만에 사용하다보니 삽질을 꽤나 했다. 
Rust  1.31 3.2(4.2) 3일 2주일 C++처럼 직접 동시성 큐를 만들려다가 go처럼 채널을 이용해서 만들어 보았다. 채널에는 std::sync::mpsc를 확장한 cossbeam::channel을 이용하였다. 참로로 이 채널은 Go와 다르게 recv시 블로킹을 하지 않는다. C++과 다르게 개발 편의 라이브러리가 대부분 존재 하고 Cargo덕분에 모듈관리편의성이 엄청 편해졌다. 물론 코딩하는 동안에 소유권 문제, 라이프타임 문제등에 대한 에러가 당황스러웠으며 분석하고 수정하는데 애먹었다. 
Node 12.16 N/A(4.1) 1일 6개월 Since the release of Node.js v10.5.0, there’s a new worker_threads module available, and it has been stable since Node.js v12 LTS. There is a Message Channel that is a simple communication channel. It has two ends, which are called ‘ports’. In JavaScript/NodeJS terminology, two ends of a Message Channel are just called ‘port1' and ‘port2'.
Python 3.7 1000+(13) 1일 2년  Performance of this kind of pattern is very slow as i expected(GIL).  I think Python has to be only used like as asyncIO
Elixir 1.10.2  1.8(13) 1일 2일 Elixir is a dynamic, functional language Based on Green Thread, Actor model.
Actor communication is very fast but tail recursive optimization is not good.

* 성능은 낮을 수록 좋음 (100만 트랜잭션 처리 시간 기준)
* 사용기간이란 해당 언어를 사용(스터디, 쓰기,읽기) 해본 필자의 경력기간 
* 성능 괄호안은 Integer to String 변환 (32,000,000번 반복)
* rust, elixir, python, node is partially completed

4. 언어 별 썰을 풀어 보자면 

(Just my emotional criticize in some part but may be changed in future) 

C++
러스트와 다르게 메모리를 스마트하게 다루어도 되고,안 해도 되니 문제가 생길 수 밖에 없다. 개발자에게 자유를 준다. 망할 자유를... 즉 윈도우즈 버그의 70%이상이 메모리,동시성 문제. C++은 이제 이전에 레거시로 존재하는 것만 유지보수하고, Rust 세상을 맞을 준비를 하는게 어떨까 싶다. (D lang도 좋다.) C++전문가인 당신은 손쉽게 이동할 수 있다.. 혹시 자신은 C++ 버그 만드는 공장장이 아니라구요? 그럴 가망성이 없지만 믿어드린다. 다만 자신의 팀원들도 그럴까?  @ C++ 비판을 했지만 사실 나는 C++ 에 애증이 가장 크다. 따라서 이런 의견도 링크 해 드린다 : Rust 언어 비판과 C++이 사라지지 않을 이유 

 

Criticizing the Rust Language, and Why C/C++ Will Never Die

We liked the article "Criticizing the Rust Language, and Why C/C++ Will Never Die" very much. We offer the author that we will do the translation on our own, and publish it in our blog. He agreed, and we represent this article in Russian and English with g

www.viva64.com

 * 참고로 C 는 논의 대상이 아니다. C는 C++과 전혀 다르며 C 하는 사람들은 그냥 C 하면됨. 

Java

자바로 코딩하면 제일 먼저 드는 생각은 모든 면에서 적당하다라는 느낌? 세계에서 제일 많이 사용되는 언어로 오랫동안 군림 할 수 있었던 여유와 안정성이 느껴진다. 특히 더그 리 덕분인지 동시성 지원 구성요소들이 잘 정리되어 있다. 2000년대 중반 부터 자바하면 성능 표준이 됬을 만큼 성능도 훌륭하다. (즉 이 정도 성능이면 웬만한 곳에서는 다 무리 없이 사용 가능) 편의성 + 성능 모두 만족 스럽다. 

String.valueOf(i)
* 자바와 스칼라에서 숫자 -> String 변환시 속도 차이가 약 2배나 차이나는데, 왜 그런지 누가 분석 해서 알려 주시면 좋겠다.

Go
절대로 Go로 하면 안되는게 아니고서는 (그런게 있을 확률도 희박, 가비지컬렉터 문제는 최신버전에서는 정말 더 좋아졌다.웬만하면 월드스탑 핑계는 대지말자) 즉 대부분의 프로젝트에서는 Go를 사용하자고 오버해서 말하고 싶다. 자바와 마찬가지로 편의성 + 성능이 좋으며 추가로 심플함 까지 추가되었다. 무엇을 하냐에 따라 달라지겠지만 최고의 전문가들로만 이루어진 팀이 아니라면 러스트,C++이 Go보다 성능이 더 좋을지도 의문사항이다. 

Rust
자신이 C++개발자인데, C++2X 버전들어서 RAII가 강제되고, Uniqued_ptr, move가 디폴트가 되었으며, Shared_ptr가 좀 더 세분화되어 강제되었다고 생각 해보자. 게다가 스칼라 같은 패턴 매칭 및 적절한 함수형 스타일 첨가와 Go처럼 클래스 없이 struct + traits의 덕타이핑 스타일로 변화 되었다고 하자. 그것이 Rust이다. 순수함수형 스타일이 아닌 언어로 멀티코어 프로그래밍을 할 때 얼마나 주의를 기울여야 안전한 솔루션이 나올 수 있는지에 대한 철저한 교과서이다. 즉 C++ 및 다른 언어로 개발 할 경우, 이 만큼의 주의를 기울이지 않을 가망성이 크기 때문에 버그가 끊임없이 생겨 난다는 얘기.
C++은 자유(방관)를 주는 언어이고, 자바는 알아서 챙겨주는 언어라면 러스트는 채찍들고 서방님을 최고의 장군으로 만들기 위해 맹훈련시키는 평강공주같은 스타일이다.
go에 비해 쓰레드가 무겁다는 것이 아쉽다. green 쓰레드 없나 본데 -.-a

Elixir 
Go에는 멀티코어 사용시에 mutable 사용이 너무 자유로우며 채널을 통한 메모리 공유로 안심하다가 뒤통수 맞을 가능성도 크다.(물론 심플하니깐 문제점 찾기도 비교적 쉬울 듯)  Scala + akka 도 무거운데다가 구현하기 나름이지만 강제하지 않기 때문에 결국 비슷해보이고... Rust는 너무 복잡하게 관리를 유도하고...이런 전차로 벤치마크 리스트에 (비교적) 순수함수형 언어도 없고 해서 다음편엔 Elixir 에 대해서 공부해보고 추가 구현 해 보려 한다.(did it)  Elixir is a dynamic, functional language Based on Green Thread, Actor model



5. 구현 코드

Go: https://github.com/wowlsh93/hyperledger-fabric-400

 

wowlsh93/hyperledger-fabric-400

hyperledger fabric in less than 400 lines of Go. Contribute to wowlsh93/hyperledger-fabric-400 development by creating an account on GitHub.

github.com

Elixir : https://github.com/wowlsh93/hyperledger-fabric-400-elixir
Scala:  https://github.com/wowlsh93/hyperledger-fabric-400-scala
Java:  https://github.com/wowlsh93/hyperledger-fabric-400-java
Rust :  https://github.com/wowlsh93/hyperledger-fabric-400-Rust 
C++:  https://github.com/wowlsh93/hyperledger-fabric-400-CPP  
Python: https://github.com/wowlsh93/hyperledger-fabric-400-python

6. 마무리

테크니컬 포스트라기 보다는 주관적 수필 같은 느낌의 코딩 작업과 글이 었다. 코드는 계속 수정 보완 될 것이며  실제 프로덕트에서는 Lock free 및 object copy관점에서 많은 최적화가 이루어 질 수 있을 것인데 언어별 문제점 및 동시성 최적화 후의 변화에 대해서는 2편에서 다루어 볼 생각이다. 1. 가능하면 lock을 잡지 않는다. 2. lock 지연  3. lock을 세밀하게 나눈다 등등을 적용해서~  + Future Pattern

0 Comments
댓글쓰기 폼