서버 성능 테스트 2차

서버를 만들며 다시 해본 테스트 결과이다.

저번 테스트와는 다르게 서버컴퓨터와 클라이언트컴퓨터를 분리해서 테스트했다. 서버는 운영체제는 CentOS 7, 인텔 i7 8코어, 16GB 메모리를 장착한 컴퓨터이다. 리눅스에서 gcc 4.8.2로 빌드되었다.

100개의 커넥션으로 100ms마다 5000개의 패킷을 전송한 결과는 다음과 같다.

K-006

송수신을 합쳐 거의 70만~90만개의 패킷을 처리했다. 서버가 받는 패킷보다 내보내는 성능이 현저히 좋지 않았으며 성능이 시간의 흐름에 따라 굉장히 떨어졌다가 굉장히 올라갔다가를 반복했다. 이 때 서버의 CPU는 8개 코어가 거의 95% 이상의 점유율을 보여줬다. 코어를 거의 다 사용하는데 이건 좀 과한게 아닌가 싶다.

그리고 테스트 경우에 따라 응답패킷이 거의 안 오는 경우도 발생했는데 이것은 시간을 두고 찾아봐야할 것 같다.

조금 더 개선하면 나아지지 않을까 생각이 든다.

 

2016년 6월 3일 추가.

Google tcmalloc을 적용했으며 테스트 환경은 위와 같다.

K-007

테스트하면서 가장 적절한 수치를 찾아보려고 했는데 대략 3000번 정도의 패킷은 무난히 처리할 수 있었다. 이 수치를 5000, 4000, 3000으로 낮춰가며 테스트했는데 5000, 4000에서는 시간이 지날수록 패킷 처리량이 감소하는 등의 문제가 생겼고 3000에서는 부담 없이 계속적으로 처리할 수 있었다. 이때 초당 처리한 메시지수는 60만 정도였다.

하나 알게된 사실은 메모리 누수를 발견했다. 시간이 지날수록 서버의 메모리 사용량이 계속적으로 증가했는데 아직 원인을 찾을 수 없다.

GCC 최적화 옵션

GCC로 컴파일하던 중 spdlog 라이브러리에서 컴파일시 -O3 옵션을 사용하는 것을 보고 궁금증이 생겨 찾아보았다.

GCC 최적화 옵션에 대해 자세하게 설명되어 있는 페이지.

http://jinynet9.tistory.com/113

* 커널 컴파일 시 최적화 옵션 -O2만 사용하는 이유
커널은 인라인 함수를 많이 사용하고 있다. -O3 최적화는 컴파일러가 판단해서 인라인을 인라인이 빠른 것은 인라인으로, 함수가 빠른 것은 함수로 바꿔버린다. 커널은 최적화된 수행 속도를 위해 의도적으로 인라인 함수를 사용하고 있어서 컴파일러에 의해서 자의적으로 함수로 바뀌는 것을 막기 위해 -O2 옵션을 사용한다.

커널 컴파일은 아니지만 최적화 옵션을 -O2 로 변경했다. 컴파일 옵션을 변경하고 -O3일 때와 파일 크기를 비교해보았는데 거의 차이가 나지 않았다. 앞으로도 -O2로 사용하면 될듯하다.

dependent-name is parsed as a non-type, but instantiation yields a type 에러 해결 방법

다음과 같은 코드를 작성했다.

template<typename T1, typename T2>
class ObjectMap
{
public:
    typedef boost::mutex                    Mutex;
    typedef boost::lock_guard<Mutex>      LockGuard;
    typedef std::map<T1, T2>              Map;

    bool InsertObject(T1& key, T2& object)
    {
        LockGuard lock(object_map_mutex_);
        auto result = object_map_.insert(Map::value_type(key, object));
        return result.second;
    }

대략 템플릿으로 std::map을 래핑해서 사용하려는 클래스인데 이 코드는 Visual Studio 2015 에서 아무 문제 없이 컴파일되며 실제로 작동상에도 아무 문제가 없는 코드이다.

하지만 이 코드를 gcc에서 컴파일 하려고 하면 다음과 같은 오류가 발생하며 컴파일 자체가 되질 않는다. 참 신기한 노릇… (컴파일러는 CentOS 7의 gcc 4.8.2 버전이다.)

/container/object_map.h:28:63: error: dependent-name ‘ObjectMap<T1, T2>::Map:: value_type’ is parsed as a non-type, but instantiation yields a type
   auto result = object_map_.insert(Map::value_type(key, object));
                                                               ^
/container/object_map.h:28:63: note: say ‘typename ObjectMap<T1, T2>::Map:: value_type’ if a type is meant

이 문제에 대해 한참을 찾아본 결과 다음의 주소에서 힌트를 얻을 수 있었다.

http://stackoverflow.com/questions/6021686/c-iterator-to-stdmap

원인은 해당하는 부분에 컴파일러가 타입을 알 수 없다는 얘기.

코드를 다음과 같이 바꾸어주었더니 gcc에서도 컴파일이 잘 된다.

auto result = object_map_.insert(typename Map::value_type(key, object));

예전에도 느꼈지만 리눅스에서 빌드하는거 참 귀찮고 어렵다는거 다시 한번 느낀다.

tbbmalloc을 적용했을 때 성능 변화

dserver의 성능의 문제가 메모리할당에 대한 이슈인가 싶어서 주말동안 tbbmalloc 을 적용했다.

tbbmalloc_proxy를 적용했고 적용하는데는 채 30분도 걸리지 않았다.

적용해서 테스트해본 결과는…..

별 차이 없다. 아마도 메모리할당에 대한 성능이슈는 아닌듯하다.

송수신시에 사용하는 락의 문제일 가능성이 커졌다.

CGCII 테스트 클라이언트를 이용한 dserver 성능 측정

CGCII의 조상현님께서 공개한 CGCII 테스트 클라이언트로 dserver의 echo 성능을 측정해보았다.

사실 게임서버에서 네트워크 라이브러리의 성능보다 비즈니스 로직에서 걸리는 부하가 훨씬 크며 에코로만 서버를 테스트하는게 얼마나 의미가 있을까…라는 생각이 들지만 어찌되었든 CGCII 홈페이지의 글들을 보고 궁금증이 피어오른건 사실이다.

지금 만든 서버는 지금 하고 있는 프로젝트를 위해 설계되었기 때문에 CGCII의 테스트에 맞추기 위해 헤더쪽 수정작업이 있어야했다. 사실 상당히 귀찮았다. 나중에 다시 또 쓸일이 있을까봐 아예 옵션 설정에서 켜고 끌 수 있게 수정했다.

빌드는 Visual Studio 2015 프로페셔널 버전으로 했으며 Release 로 빌드했으며 모든 로깅시스템은 off 되었다. 멀티스레드 기능은 최대한 사용할 수 있도록 설정했다. 테스트한 시스템 사양은 다음과 같다.

  • CPU : Intel i7-6700 3.4GHz
  • RAM : 16GB
  • OS : Windows 10 Pro

서버와 클라이언트는 같은 컴퓨터에서 실행되었다.

테스트 방법은 총 100개의 접속을 유지하고 테스트 클라이언트에서 8Byte의 패킷을 초당 1000개씩 쏘고 Echo 받는 형태이다.

성능테스트한 결과는 다음과 같다. 대략 열번 정도 반복했을 때에도 수치상 이 결과와 크게 차이나지 않았다.

dserver

결과는

  • 메시지처리량 : 초당 20~21만개 정도의 메시지를 처리할 수 있었다.
  • 데이터처리량 : 초당 1.54M ~ 1.6M 정도의 데이터를 처리했다
  • CPU 분산 이용 : 목표한대로 모든 CPU 코어가 모두 균등하게 사용되었다.

dserver

  • CPU 사용률 : 27.1%를 사용했다.
  • RAM 사용률 : 185.1MB를 사용했다.

dserver

cgcii 홈페이지에 있는 결과들과 비교해보면 한심한 수준이다. 싱글스레드로 작동하는 node.js 가 처리하는 수준과 거의 비슷한 정도이다.

그냥 생 ASIO로 만들어진 서버가 처리하는 양보다 거의 1/10 수준이니 뭐… 이정도면 아주 저성능 서버가 아닌가 싶다.

메모리풀을 전혀 사용하지 않았고, 데이터 전송시에 new / delete를 너무 많이 하는 점, 데이터 전송시에 락을 걸며 사용하는 점 등등 때문에 성능이 너무 낮게 나온게 아닌가 싶다.

tcmalloc 이라도 적용해보면 좀더 성능이 올라가지 않을까 싶다.

일단 여기까지 작업 완료.

게임서버 제작 프로젝트의 시작

새로 회사에 입사하며 기존의 소스코드를 어느정도 인수인계 받았는데 이게 생짜  C로 구현된 서버였다.

게다가 접속자당 스레드를 하나씩 생성해서 동기식 네트워킹을 쓰는 특이한 구조…

여러가지로 구조상 말도 많고 문제도 많은 이 서버를 대체하기 위한 게임서버 라이브러리를 제작한다.

 

제작하며 알게된 사실들과 정보들을 일지 형식으로 남기려고 한다.

실제로 제작에 착수한지는 꽤 되었지만 이제서야 이 글을 남긴다.