EOS에서의 Web Assembly - 초당 50,000회 전송

우리는 EOS용 컨트랙트 작성용으로 작으면서도 견고한 스크립트 언어를 사용하길 원했습니다. 첫 선택은 Wren(프로그래밍 언어)이었습니다.
몇 주 전, 저는 빈 컨트랙트를 이용해서 Wren의 성능을 테스트해봤습니다, 대략 초당 1000 트랜잭션 정도의 퍼포먼스를 보여주였으나, 우리의 목표를 달성하기에는 너무 느린 퍼포먼스였습니다.

그래서 지난 몇 주 동안, EOS 개발팀은 Wren을 버리고, 새롭게 Web Assembly를 차용했습니다.
그리고 오늘, 현재까지의 진행 상황과 성과에 대해서 알려드리고자 합니다.

Web Assembly에 대해서

Web Assembly는 Microsoft, Google, Apple의 지원을 받아 최근에 개발된 웹 표준 기술입니다.
Web Assembly의 목표는 신뢰할 수 없는 고성능 코드(네이티브 수준의 코드)를 브라우저에서 실행할 수 있게 만드는 것입니다.
Web Assembly는 비디오 및 이미지 편집, 게임 등과 같은 고성능 앱을 브라우저에서 바로 실행할 수 있게 만들어 웹의 판도를 바꿀 것입니다.

WebAssembly는 통합된 컴파일 타겟을 제공합니다. 그래서 어떤 프로그래밍 언어든 WebAssembly로 컴파일될 수 있습니다. 현재는 C, C++, Rust용 컴파일러가 있고, Solidity(이더리움 스마트 컨트랙트 개발 언어)를 WebAssembly로 컴파일하는 프로젝트도 진행되고 있습니다.

EOS에 통합하기

몇 주 전 우리는 Wren으로 작성한 가상의 스마트 컨트랙트 화폐 예제를 소개했습니다.
오늘은 'C'로 작성해서 WebAssembly(WASM)로 컴파일되는 구현체를 하나 보여드리겠습니다. 이것은 위 예제와 달리 실제로 동작합니다. 우리는 EOS 트랜잭션을 이용해서 계정(@simplecoin)을 만들고 블록 체인을 테스트하기 위해서 WASM 코드를 업로드했습니다.

현재 개발 단계에서는 모든 요소가 변경될 수 있으며, 몇몇 사항들은 이번 여름에 공식 테스트 네트워크를 오픈하기 전까지 지속적으로 다듬어질 것입니다.
그 예로, 메시지 정의와 역직렬화(Deserialization) 부분의 Boilerplate는 위 Wren으로 작성한 컨트랙트 예제와 비슷한 수준의 입력을 받아 이에 해당하는 코드를 자동으로 생성하는 코드 생성기에 의해서 대체될 수 있습니다.

아래는 'C'로 작성한 컨트랙트 코드입니다:

typedef struct  {
  AccountName from;
  AccountName to;
  uint64_t    amount;
  String*     memo;
} Transfer;
 
void Transfer_unpack( DataStream* ds, Transfer* transfer )
{
   AccountName_unpack( ds, &transfer->from );
   AccountName_unpack( ds, &transfer->to   );
   uint64_unpack( ds, &transfer->amount );
   String_unpack( ds, &transfer->memo );
}
 
typedef struct {
  uint64_t    balance;
} Balance;
 
/** 코드가 처음 업로드되면 호출되는 생성자 */
void onInit() {
  static Balance initial;
  static AccountName simplecoin;
  AccountName_initCString( &simplecoin, "simplecoin", 10 );
  initial.balance = 1000*1000;
  
  store( &simplecoin, sizeof(AccountName), &initial, sizeof(Balance));
}
 
/** 전송 메시지가 @simplecoin에게 전달되었을 때 호출되는 메시지 핸들러 */ 
void onApply_Transfer_simplecoin() {
   static char buffer[100];
   int read   = readMessage( buffer, 100  ); /** load message content */
   static Transfer message;
   static DataStream ds;
   DataStream_init( &ds, buffer, read );
   Transfer_unpack( &ds, &message );  /* unpack it */
 
   static Balance from_balance;
   static Balance to_balance;
   to_balance.balance = 0;
   
   read = load( &message.from, sizeof(message.from), 
                &from_balance.balance, sizeof(from_balance.balance) );
   
   assert( read == sizeof(Balance), "no existing balance" );
   assert( from_balance.balance >= message.amount, "insufficient funds" );
   
   load( &message.to, sizeof(message.to), 
         &to_balance.balance, sizeof(to_balance.balance) );
   
   to_balance.balance   += message.amount;
   from_balance.balance -= message.amount;
 
   if( from_balance.balance )
      store( &message.from, sizeof(AccountName), 
             &from_balance.balance, sizeof(from_balance.balance) );
   else
      remove( &message.from, sizeof(AccountName) );
 
   store( &message.to, sizeof(message.to), 
          &to_balance.balance, sizeof(to_balance.balance) );
}

위 코드를 WasmFiddle를 이용해서 WebAssembly (WASM)로 컴파일했습니다. 이 코드는 블록체인으로부터 정보를 읽고 쓰기 위해서 readMessage, load, store 등의 몇 가지 간단한 API를 사용합니다.

위 코드(컨트랙트)는 1백만 코인을 만들어서, 이것을 @simplecoin에게 할당하고, @simplecoin이 이 코인을 다른 계정으로 전송할 수 있게 만듭니다. 그리고 코인을 받은 계정들 또한 코인을 다른 계정으로 전송할 수 있게 됩니다.

성능 벤치마크

저는 이 컨트랙트를 로드하고 트랜잭션을 실행하는 단위 테스트를 만들었습니다. 이 단위 테스트@simplecoin으로부터 @init1으로 코인을 전송하는 개별 트랜잭션을 1000회 실행합니다.

     auto start = fc::time_point::now();
     for( uint32_t i = 0; i < 1000; ++i )
     {
        eos::chain::SignedTransaction trx;
        trx.emplaceMessage("simplecoin", "simplecoin", 
                           vector<AccountName>{"init1"}, "Transfer",
                           types::Transfer{"simplecoin", "init1", 1+i, "memo"} );
        trx.expiration = db.head_block_time() + 100;
        trx.set_reference_block(db.head_block_id());
        db.push_transaction(trx);
     }
     auto end = fc::time_point::now();
     idump((  1000*1000000.0 / (end-start).count() ) );

여러 번의 시행 끝에 최종적으로 1초 동안 평균 50,000번의 전송을 실행할 수 있다는 결과를 얻었습니다. 이는 초기 테스트 결과이기 때문에, 성능에 영향을 주는 여러 요소가 있을 수 있어서 블록체인에서의 최종 성능에 대한 결론을 내기에는 너무 이릅니다. 하지만, 초당 50,000 번의 연속적인 명령을 실행하는 것은 우리가 원하는 목표(페이스북, 비자 등의 수준)에 근접합니다.

이 벤치마크는 2014 iMac (4Ghz Intel Core i7 CPU)에서 측정되었습니다.

Wren과의 차이

Wren의 경우 실행할 때마다 코드를 컴파일해야 하기 때문에 느렸습니다. 그러나 그것을 극복하기 위해서 컴파일된 결과를 쉽게 캐싱할만한 방법도 딱히 없었습니다. 또한 Wren은 초기의 컴파일 비용 때문에 오래 실행시키는 프로그램에 유리합니다. 그리고 이것은 굉장히 많은 프로그램을 짧고 빠르게 실행시켜야 하는 스마트 컨트랙트 플랫폼에는 맞지 않습니다.

우리가 EOS에 사용하고 있는 WebAssembly 라이브러리는 LLVM 컴파일러 라이브러리를 이용해서 WASM 코드를 네이티브 x86 명령어로 컴파일합니다. 이것은 WASM을 네이티브의 80% 속도로 실행할 수 있게 만듭니다. (어떤 인터프리터 언어보다 빠릅니다.) 그리고 JIT(Just-in-Time) 최적화가 실행되면 더 빨라질 수 있습니다.

앞으로의 계획

지금 우리는 WebAssembly 기반 컨트랙트의 컨셉을 증명했고, 이것의 싱글 스레드 퍼포먼스는 이미 업계 최고 수준임을 확인했습니다.
우리는 더 많은 API를 WebAssembly에서 사용할 수 있게 만들 것이고, 개발자들이 컨트랙트를 'C'로 작성하지 않고, 더 쉽게 작성할 수 있도록 고수준 언어와 도구에 대한 지원을 추가할 방법을 찾아볼 것입니다.

우리의 초기 테스트 네트워크는 코드와 API의 안정성에 집중할 것이며, 컨트랙트들을 싱글 스레드로 실행될 것입니다. 하지만, EOS.IO 소프트웨어 아키텍처는 블록체인의 하드포크 없이 멀티-스레드 실행 환경으로 변경할 수 있게 설계되었습니다. 또 비록 EOS의 싱글 스레드 구현일지라도, 우리의 초기 벤치마크가 보여주듯이 이미 오늘날의 어플리케이션들을 충분히 실행할 수 있으며, 이는 업계 최고 수준의 성능입니다.

계속 지켜봐주세요

https://eos.io 메일링리스트에 가입해서, 개발에 대한 최신 정보와 다가오는 EOS 토큰 세일에 대한 정보를 받아보세요.


원문: @dantheman/web-assembly-on-eos-50-000-transfers-per-second
@heejin 님께서 번역해주신 글입니다. 번역자를 위한 투표는 이 링크에서 하실 수 있습니다.

H2
H3
H4
3 columns
2 columns
1 column
12 Comments