관리 메뉴

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일 1년   x나 빠르네 (역시 성능 테스트는 뭘 하냐에 의존한다. b tree 같은거 하면 다를듯)
C++ 17 12 (4.2) 3일 13년 unique_lock과 condition을 직접 사용하여 concurrent queue를 만들어서 구현함. C++경력이 제일 길지만 중간에 멀리 했다 다시 하면서 디버깅하는데 애를 먹었다.  C++의 동시성 지원은 모던 C++ (11버전 이후) 들어서, 자바의 메모리모델을 참고하여 메모리 모델도 생기고, Thread, Lock도 생기는 등 뒤늦게나마  지원하기 시작했으며 C++20 를 통해 거의 완성 될 것으로 보이지만 역시 신세대 언어들에 비해 모듈 관리 편의성 및 유틸리티류가 부족.. 크로스플랫폼으로 가면...웬만한 경험치가 아니고선 후.. 
Java 11 1.6 (2) 1일 1년 lock과 condition도 사용하였으며, 의도적으로 LinkedBlockingQueue(블로킹) 과 concurrentLinkedList(비동기)를 혼합하여 사용하였다. java는 옛날 1.5버전부터 끝내주는 동시성 라이브러리를 많이 제공해 주고 있기 때문에 이것 저것 해볼게 많다. 
Scala 2.12 9.8(1.2) 2일 1년 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일 1개월 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로도 시도를 해보는게 어떨 까 한다. 모던C++을 충실히 이해하는 당신은 비교적 무난하게 이동할 수 있다.(2022년에는 구글에서 C++개발자를 위한 새로운 언어인 카본이 등장한다. 코틀린이 자바개발자에게 그러하듯이 카본은 C++개발자에게 러스트보다 훨씬 더 상호호환성이 좋고 친숙하게 만들어졌다.) 혹시 자신은 C++ 버그 만드는 공장장이 아니라고 생각하나? 그럴 가망성이 없지만 믿어드린다. 다만 자신의 팀원들도 그럴까? 

@ 아직 아기인 Rust도 좀 더 떳으면 하는 마음에서 C++ 살짝 비판을 했지만 사실 나는 C++에 애증이 크다. (20년 개발자 인생중 13년을 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

 

Java

자바로 코딩하면 제일 먼저 드는 생각은 모든 면에서 적당하다라는 느낌? 과한거 같기도 하고 ㅎㅎ 세계에서 제일 많이 사용되는 언어로 (특히 백엔드) 오랫동안 군림 할 수 있었던 여유와 안정성이 느껴진다. 특히 더그 리 덕분인지 동시성 지원 구성요소들이 잘 정리되어 있다. 2000년대 중반 부터 자바하면 다른 신생언어가 추종하는 성능 표준이 됬을 만큼 성능도 훌륭하다. (즉 이 정도 성능이면 웬만한 곳에서는 다 무리 없이 사용 가능) 편의성 + 성능 모두 대체적으로 만족 스럽다. 단 한가지 가벼운 쓰레드가 없는건 많이 아쉽다. 이 점 및 많은 부분에서 자바와 상호호환성이 좋은 코틀린은 정말 강력한 자바의 후계자 위치를 차지할 것 같다. 나도 코틀린으로 갈아탔고.

@ 자바와 코틀린의 가벼운 쓰레드 이야기는 여기 참고-> https://www.baeldung.com/kotlin/java-kotlin-lightweight-concurrency

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++은 자유(방관)를 주는 언어이고, 자바는 (최소한만) 알아서 챙겨주는 언어라면 러스트는 채찍들고 서방님을 최고의 장군으로 만들기 위해 맹훈련시키는 평강공주같은 스타일이다. 따라서 대중화 되기는 요원할 거 같다. 라이프타임의 책임이 컴파일러 제작사보다 너무 사용자에게 전가되어 있다. 대중은 쉬운길로 간다. 다만 팀원을 신뢰하게 만들고 성능이 최우선사항이며 사람목숨이나 큰 돈이 오가는곳에 C++,Go 대신해 쓰여 질 만하다. (근데 최근 스페이스X에는 일론 머스크가  C++를 욕하고도 결국 C++를 사용) 수년간 블록체인 분야에서 일하고 있는데 이쪽 분야가 비교적 Rust를 잘 사용한다. go에 비해 쓰레드가 무겁다는 것은 아쉽다.  green thread의 단점으로 의도적으로 제거했다고 하는데..태생이 시스템 언어라 green 쓰레드는 보통 VM기반에서 돌아가기 때문. 뭐 서드파티로 나오겠지..(rust goroutine equivalent golang -> https://github.com/Xudong-Huang/may)

Elixir 
Go에는 멀티코어 사용시에 mutable 사용이 너무 자유로우며 채널을 통한 메모리 공유로 안심하다가 뒤통수 맞을 가능성도 크다.(물론 심플하니깐 문제점 찾기도 비교적 쉬울 듯?)  Scala + akka 도 무거운데다가 구현하기 나름이지만 강제하지 않기 때문에 결국 비슷해보이고... Rust는 너무 복잡하게 관리를 유도하고...이런 전차로 벤치마크 리스트에 (비교적) 순수함수형 언어도 없고 해서 아무것도 모르지만 일단  Elixir를 구현해봤다. 안전성, 심플코딩이야 특유의 장점 인건 알겠는데 ...개빠르기 까지 하네?  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편에서 다루어 볼 생각입니다. 

'소프트웨어 사색 ' 카테고리의 다른 글

Rust  (0) 2020.06.16
소프트웨어 복잡도 줄이기 (1)  (0) 2020.05.13
소프트웨어 아키텍트란  (0) 2019.12.11
'망할' 에이콘 출판사  (2) 2019.04.16
개발자 면접 방식을 바꾸자  (2) 2019.04.12
Comments