1P by GN⁺ | ★ favorite | 댓글 1개
  • 고빈도 거래(HFT)처럼 지연 시간이 곧 경쟁력인 영역에서, 공개적으로 부족한 C++ 최적화 지식을 실험과 구현 중심으로 정리함
  • 결과물은 Low-Latency Programming Repository, 시장 중립 페어 트레이딩 전략 최적화, C++ Disruptor 패턴 라이브러리 세 가지로 나뉨
  • 벤치마킹은 속도, 캐시 활용, 통계적 유의성을 함께 보며, Cache WarmingConstexpr가 지연 시간 감소에서 큰 이득을 보임
  • 최적화된 페어 트레이딩 전략은 실행 속도와 수익성이 개선됐고, Disruptor 구현은 전통적 큐 방식보다 나은 성능을 보임
  • 향후 과제는 저장소 확장, 실제 거래 환경 테스트, Disruptor와 거래 알고리듬 통합 후 전체 시스템 벤치마킹임

HFT 저지연 최적화의 목표

  • 목표는 지연 시간에 민감한 코드를 최적화해 실행 속도를 높이는 데 있음
  • 초점은 고빈도 거래에 쓰이는 프로그래밍 전략과 데이터 구조에 맞춰져 있음
  • 금융 산업, 특히 공개 시장을 다루는 buy-side 기업은 기밀성과 경쟁 우위 때문에 관련 지식을 많이 공개하지 않음
  • 이 공백을 줄이기 위해 다양한 기법을 담은 맞춤형 Low-Latency Programming Repository를 만들고, 통계적 벤치마킹으로 검증함

세 가지 결과물

  • Low-Latency Programming Repository

    • 이론 모음에 그치지 않고, 통계적 벤치마킹을 포함한 실용 가이드 역할을 함
    • HFT 시스템의 지연을 줄이기 위한 프로그래밍 기법, 디자인 패턴, 모범 사례를 큐레이션함
  • 시장 중립 통계적 차익거래 페어 트레이딩 전략 최적화

    • 지연 감소 기법과 CPU 수준 최적화를 통합함
    • 실행 속도와 수익성에서 개선을 보임
  • C++ Disruptor 패턴 라이브러리

    • 전통적 큐 방식보다 성능 향상을 보임
    • HFT 시스템의 Order Management System(OMS)에 이런 데이터 구조를 적용할 수 있음을 보여줌

공개 지식이 부족한 이유

  • HFT 시스템 최적화 지식은 주로 업계 실무자에게서 나오지만, 기밀성과 경쟁 우위 때문에 최신 연구와 구현 세부사항이 공개되기 어려움
  • 지연 개선, 코드 효율, 캐시 최적화 같은 영역은 특히 공개 자료가 제한적임
  • 경제·금융 관점의 HFT 연구와 알고리듬 트레이딩 수학 모델 연구는 존재하지만, 코드 최적화나 지연 감소의 세부 기술까지 다루는 경우는 드묾
  • C++ 관련 문헌은 비교적 많아도, 초저지연 HFT 시스템 맥락으로 직접 연결되는 경우는 제한적임
  • 온라인 블로그와 게시물은 평균 지연 시간 데이터를 표면적으로 제공하는 경우가 많고, 캐시 접근이나 명령 실행 지연의 세부 동작 분석은 부족함

평가와 성능 개선

  • 평가 지표는 속도, 캐시 활용, 통계적 유의성 등을 포함함
  • Low-Latency Programming Repository의 기법 중 Cache WarmingConstexpr가 지연 시간 감소에서 가장 큰 이득을 보임
  • Disruptor 패턴 구현은 링 버퍼, 시퀀스 번호, 특수 대기 전략을 활용해 전통적 큐 방식보다 지연 시간과 속도 면에서 더 나은 성능을 냄
  • 시장 중립 페어 트레이딩 전략은 CPU 수준 최적화와 지연 감소 기법을 통해 실행 속도와 수익성이 개선됨

공개 저장소와 향후 작업

  • 저장소, 거래 전략, Disruptor 라이브러리는 https://github.com/0burak/imperial hft에 있음
  • 향후 작업은 저장소 확장을 포함함
  • 최적화된 거래 알고리듬을 실제 거래 환경에서 테스트하는 과제가 남아 있음
  • Disruptor 패턴을 거래 알고리듬과 통합해 전체 시스템 수준의 벤치마킹을 수행하는 방향도 포함됨

댓글과 토론

Hacker News 의견들
  • 이 글은 주제에 대한 꽤 기초적인 입문으로 보임
    학부생을 가르쳐 본 경험상, 학생들도 이런 내용은 대체로 이미 알고 있음. 컴퓨터 구조 수업에서 분기 예측, 캐시 일관성, 명령어 캐시처럼 성능의 기본 요소를 배움
    고전적인 성능 저하 요인인 거짓 공유(false sharing) 를 전혀 다루지 않는 점은 의외였고, 단일 스레드 지연 시간에 주로 초점을 둔 것 같음. fat LTO, PGO, [[likely]], [[unlikely]] 같은 “공짜” 최적화 힌트도 빠져 있어 놀라웠음
    더 깊은 성능 문제는 특정 입출력 API, 동기화 원시 연산, 프로세스 간 통신, 난해한 컴파일러 내장 기능의 사용 방식까지 들어가야 함
    저지연 프로그래머에게 가장 부족하고 가르치기 어려운 건 일종의 편집증임. 불필요한 할당, 복사, 성능 저하 요인에 대한 진짜 공포와 분노가 필요함. 핫 루프 한가운데서 객체 캐시가 빗나가 할당자로 가는 호출을 찾으려고 callgrind로 벤치마크를 강박적으로 돌리는 감각임
    개인적으로는 저지연 서버를 만들다가 벡터 입출력 작업을 구성하는 것보다, 작은 객체들을 연속 버퍼에 복사해서 단일 write를 하는 편이 전체적으로 더 빠르다는 걸 깨달은 순간이 중요했음. 공짜 복사는 없고, fat pointer도 예외가 아님

    • 그럴 수도 있지만, 저지연 C++ 은 독립된 분야임에도 정보가 거의 사막에 가까움
      현재 얻을 수 있는 최고의 자료도 C++ 콘퍼런스 강연 몇 개 정도인데, 아쉬움이 많았음
      뽐내고 싶은 유혹은 제쳐두고 보면, 이 문서는 이 분야에 대한 뛰어난 기여이며 아마 첫 권위 있는 참고 자료일 수 있음. 다른 강의에서 비슷한 정보를 조합할 수 있다는 식의 막연한 말은 기여가 아니고, 누구에게도 도움이 안 됨
    • 요즘은 그런 일을 안 해서 다행이지만, 진짜 편집증은 하이젠베르크식 불신에 있음. 측정 중일 때와 측정하지 않을 때 프로그램이 다르게 동작하는 것 아닌가 하는 의심을 지울 수 없음
    • 대체로 추천할 만한 문헌이 있는지 궁금함
    • 나라면 이렇게 접근할 것 같음. 이 분야에 더 가까운 사람들의 피드백이 궁금함
      먼저 원시 속도를 위해 프런트엔드 FPGA로 부하를 단순한 자산별 데이터 스트림으로 나눔. 다만 반복 개발, 인력, 공급망 등의 마찰이 너무 크므로 여기서 실제 실행까지 하려는 유혹은 피함. 입력은 FIX 스트림 같은 것이고, 출력은 저지연 버스를 따라 자산별 이진 이벤트 스트림으로 나뉘어, 저가 MCU로 구성한 확장형 클러스터의 자산별 세그먼트로 들어감
      둘째, 자산별 MCU 기반 실행 플랫폼에서는 범용 운영체제 가정을 제거해, 실제로 구할 수 있는 하드웨어 위에서 사람들이 작성할 수 있는 저수준 코드로 더 빠른 전환을 가능하게 함. 셋째, 수익? 이런 구조에서는 범용 운영체제 기반의 감독자가 전체 상태를 모니터링하면서 필요할 때 개별 요소를 재프로그래밍해 전략을 멈추거나 바꿔야 함
      실제 지연 시간이 얼마나 낮은지가 관건임. 어느 지점부터는 엔지니어링보다 하드웨어를 코어에 더 가깝게 두는 비용을 내는 편이 낫지 않나 싶음. 이는 해당 거래소나 풀에서 제공하는 규칙, 데이터센터, 링크 인프라에 크게 좌우될 것임
      수익성 있는 운영 중 상당수는 어떤 풀에 연결하는지 공개하지 않고, 규제나 약관은 무시한 채 선행매매를 사업으로 삼을 가능성도 있어 보임. 그런 경우 두 실행 지점 사이의 상대적인 네트워크 지리 지연 시간이, 한 지점까지의 절대 지연 시간보다 더 강력함
    • PGO를 한다면 힌트 속성은 오히려 역효과 아닌가 싶음
      실제로 컴파일러 쪽 사람들이 흔히 말하는 상식은, PGO가 없어도 대부분의 경우 이런 힌트가 역효과라는 것임. 현대 컴파일러는 이런 힌트보다 자체 분석 패스를 더 신뢰하고 보통 무시함
      참고로 실제 코드에서 이런 힌트를 본 건 컴파일러가 쉽게 넣을 수 있는 곳뿐이었음. 예를 들면 malloc 호출 뒤의 널 검사 같은 경우임
  • 강조하고 싶은 부분은 이거임
    “이 테스트의 출력은 테스트 통계량(t-statistic)과 관련 p-value다. 점수라고도 하는 t-statistic은 잔차에 대한 단위근 검정 결과다. 더 음수인 t-statistic은 잔차가 정상적일 가능성이 더 크다는 것을 시사한다. p-value는 테스트의 귀무가설, 즉 공적분이 없다는 가설이 참일 확률의 척도를 제공한다. 테스트 결과는 약 0.0149의 p-value와 -3.7684의 t-statistic을 냈다.”
    이 부분은 LLM으로 쓴 것 같음
    예제도 정말 이상함. 5년 동안 하루 한 번의 종가 상관관계를 본 다음, 65마이크로초 지연 시간으로 스프레드를 계산하는 코드를 작성함. 실제로 할 만한 일로는 말이 안 됨. 내부 루프에서 스프레드 통계를 계산하지도 않을 것이고, 65마이크로초는 내부 루프치고 너무 느림
    요지는 최적화 기법을 연습하는 것일 수 있지만, 최적화 대상으로는 꽤 대표성이 떨어짐

  • C++로 LMAX Disruptor 패턴을 쓰는 주식 거래소 구현을 만들었음
    https://github.com/sneilan/stock-exchange
    LMAX Disruptor의 기본 구현도 C++ 파일 몇 개로 만들어 둠
    https://github.com/sneilan/lmax-disruptor-tutorial
    다만 이걸 Rust로 다시 만들려고 보고 있음. 자체 웹소켓 프로토콜, 인증 시스템, SSL 등을 구현한 지점까지 갔는데, 메모리 관리와 의존성은 Rust가 훨씬 쉽다는 걸 깨달았음. 특히 1인 소프트웨어 프로젝트라면 더 그렇다

    • 이런 자료구조를 C++로 제대로 만들기는 쉽지 않음. 큐 구현에 몇 가지 문제가 있음
      메모리 접근은 컴파일러와 CPU 양쪽에서 재정렬될 수 있으므로, 원래 LMAX Disruptor 논문에서 설명한 장벽을 얻으려면 생산자와 소비자 위치에 std::atomic을 써야 함
      get 메서드에서는 소비자 위치를 올린 뒤, 즉 생산자에게 슬롯을 해제한 뒤 큐 내부 요소의 포인터를 반환하고 있음. 그래서 사용자가 접근하는 동안 덮어써질 수 있음
      그리고 생산자 위치와 소비자 위치가 같은 캐시 라인에 들어갈 가능성이 높아 거짓 공유가 발생함
    • 이런 코드 대신
      T *item = &this->shared_mem_region->entities[this->shared_mem_region->consumer_position];
      this->shared_mem_region->consumer_position++;
      this->shared_mem_region->consumer_position %= this->slots;
      이렇게 할 수 있음
      uint64_t mask = slot_count - 1; // 이진수로 모두 1
      item = &slots[ pos & mask ];
      pos ++;
      즉 나눗셈/모듈로를 비트 AND로 바꿔 계산을 조금 줄일 수 있음. 단, 링 버퍼 크기는 2의 거듭제곱이어야 함
      더 나아가 uint64_t 같은 전체 범위의 시퀀스 번호를 쓸 수 있음. 래핑은 자동으로 처리됨. 두 시퀀스 번호를 빼도 래핑을 감안해 문제없이 동작함. 버퍼가 가득 찼는지 비었는지 구분하려고 슬롯 하나를 비워둬야 하는 바보 같은 문제도 없어짐
      물론 “살아 있는” 시퀀스 번호의 창이 링 버퍼 창 크기를 절대 넘지 않도록 조심해야 함
    • 주식 거래소 코드를 잠깐 봤음
      메모리 관리는 std::shared_ptr로 바꾸는 걸 고려해볼 만함. 속도를 늦추지 않으면서 그 걱정을 완전히 없애줌
      소켓은 직접 작성한 코드보다 성능이 좋고 골치 아픈 예외 사항도 줄여주는 자유 오픈소스 라이브러리가 있음. 예를 들어 FD_ISSET을 순회하는 방식은 epoll이나 kqueue보다 느림
      의존성 관리는 C++이 다른 언어보다 확실히 더 거칠다. 관리보다 찾기가 더 어렵기도 함. 쓸 만한 라이브러리 코드가 여기저기 있고, 일부는 인터넷의 잊힌 구석에 숨어 있음. 찾아내는 것 자체가 하나의 기술이고, 잘하면 큰 보상을 줌
    • LMAX Disruptor는 스레드를 코어에 고정하고 대부분 또는 전부가 경합하지 않을 때 훌륭한 자료구조임. 이 패턴이 아니라면 꼬리 지연에서 끔찍한 병리가 생김. 스레드가 나쁜 타이밍에 스케줄에서 밀려나면 큰 타격이 됨
      생각 중인 시스템에서는 SPSC 링 버퍼를 이기기 어려울 것 같고, 필요하다면 오래된 방식의 락으로 작업 훔치기도 구현할 수 있을 것임
    • 재미있는 사실: 원래 LMAX는 Java용으로 설계되고 Java로 작성됐음
      https://martinfowler.com/articles/lmax.html
  • https://github.com/CppCon/CppCon2017/blob/master/Presentatio...가 떠오름

    • 훌륭한 슬라이드임
      가짜 서버가 주문 데이터를 재생하고, 두 번째 서버가 실행 시간을 계산하고, 테스트 대상 서버와 하드웨어 스위치로 패킷 시간을 측정하는 슬라이드는 정말 기분 좋을 정도로 하드코어함
      금융 쪽에서 일하고 싶은 마음은 없지만, 벤치마킹만을 위해 랙 단위 하드웨어를 사는 게 경제적으로 가능한 수준의 성능 임계 시스템을 다루는 건 재미있을 것 같음
  • LMAX Disruptor와 비슷한 점이 많은 C++ 로깅 라이브러리를 만들었고, HFT 커뮤니티에서도 어느 정도 쓰이는 것 같음
    원래 목적은 운영 환경에서 사후 디버깅을 위해 매우 상세한 로그를 성능 저하 없이 남길 수 있게 하는 것이었음. 문제 해결에 중요한 정보를 로그에 넣으면 성능에 영향이 갈까 봐 거부하던 동료들이 있었는데, 이 라이브러리로 그 논쟁이 끝남
    [1] https://github.com/mattiasflodin/reckless

  • 컴파일 시간 디스패치의 또 다른 장점은, 컴파일러가 어떤 함수가 호출되는지 정적으로 판단할 수 있을 때 호출된 함수의 코드를 호출 지점에 직접 인라인할 수 있다는 점임
    그러면 함수 호출 오버헤드를 모두 없애고, 죽은 코드 제거, 상수 전파 같은 추가 최적화도 가능해질 수 있음

    • 알기로는 속도 향상의 원인이 함수 호출 오버헤드인 경우는 거의 없음. 끝부분에서 말했듯이 핵심은 컴파일러 최적화가 동적 분기 너머를 볼 수 있느냐에 있음
      좋은 JIT는 다형적 인라인을 지원함. C++에 대한 내 경험은 좀 오래됐지만, 이 문제의 해법은 PGO였음. 다만 널리 쓰이지 않음. 대신 성능 민감 코드에서는 동적 디스패치 자체를 피하는 경향이 있음
      더 일반적인 교훈은, 어떤 언어든 코드의 뜨거운 구간에서는 컴파일러나 JIT가 이를 꿰뚫어 본다는 강한 확신이 없는 한 불필요한 동적 분기를 피하라는 것임
    • 실제 성능은 컴파일러 최적화뿐 아니라 기계의 런타임 동작에도 달려 있음. 이 주제에 관해서는 이 강연이 매우 흥미로웠음
      https://youtu.be/i5MAXAxp_Tw
    • 반대로 명령어 캐시가 한계라면 지연 시간 측면에서는 순손해일 수 있음. 물론 접근 패턴 등에 따라 달라짐
  • 고빈도 거래가 존재해야 할 좋은 이유가 있나? 사람들은 Bitcoin이 에너지를 낭비한다고 자주 비판하지만, 이것도 사회적으로는 명백히 순손실처럼 보이는데 이상하게 그냥 넘어가는 것 같음

    • 매수/매도 스프레드는 예전보다 훨씬 좁아졌음. HFT 업계 전체의 이익을 보면 그렇게 크지 않고, 수십억 달러 수준이며 거래 금액은 수조 달러임
      이 산업이 엄청나게 친사회적이라고 말하긴 어렵지만, 스프레드를 좁히면 중개자에게 가는 돈이 줄어드는 건 맞음
    • 명시적으로 금지되어 있지 않기 때문 아닌가 싶음
      HFT는 꽤 집중된 영역이긴 해도 규모 자체는 작은 편임. 에너지 낭비 측면에서는 Bitcoin보다 몇 자릿수 작음
      HFT의 유일한 긍정적 효과는 유동성과 더 좁은 스프레드인데, 사람들이 HFT를 어떻게 정의하느냐에도 달려 있음. 예를 들어 Robinhood와 무료 거래는 아마 이것 없이는 존재하지 않았을 것임
      이들은 이전에 브로커와 은행으로 가던 몫을 가져가고 있음. HFT는 “개미”를 등쳐먹는 사업이 아님
      내 관점에서는 사회에 대한 부정적 영향이 거의 없거나 없음. 장기적으로 주식시장에 투자하는 사람이라면 HFT를 신경 쓸 이유가 거의 없음
    • Warren Buffett은 주식시장이 분기마다 한 번처럼 더 드물게 열려야 한다고 제안했음. 그러면 투기가 아니라 장기 투자를 장려할 수 있음
      어쨌든 고빈도 거래를 필요로 하는 자연적 사건은 없음. 기초 가치가 매우 빠르게 변하는 일은 드물고, 변한다 해도 변동성이 아니라 확정적인 전환에 가까움
    • Bitcoin이 아닌 거래는 여러 데이터베이스에 항목 몇 개를 쓰는 것뿐임. Bitcoin 채굴은 강한 수치 계산 작업임
      HFT는 불일치, 예를 들어 세 통화쌍이 서로 어긋나는 상황이나 “명백한” 오가격을 해소해서 금융시장을 아주 조금 더 정확하게 만듦
    • 어디까지 알아봤고, 주식을 사고팔아 본 적이 있는지 궁금함
      뭔가를 거래하려 할 때는 반대편에 누군가가 있음. 대개는 내가 원하는 가격에서 HFT 참여자와 거래하게 될 가능성이 큼. 더 나은 가격을 받는다면 그 돈은 내가 지키는 돈임
      “그냥 넘어간다”는 말에도 동의하기 어려움. HFT는 여기서도 꽤 자주 비판받음
  • 전문 개발자라면 전체를 볼 가치가 있음
    https://github.com/CppCon/CppCon2017/tree/master/Presentatio...
    그리고 그 상위 디렉터리도

  • 궁금한 점이 있음. 이 분야에서는 왜 로직에 C가 아니라 C++ 을 쓰거나 써왔을까? 이 영역에서 C++이 C보다 갖는 장점은 무엇일까? C/어셈블리는 능숙하지만 HFT 관행은 전혀 모르니 쉽게 설명해주면 좋겠음

    • C++은 C보다 표현력이 높고 훨씬 많은 추상화를 허용함. 오랫동안 C++은 C 수준 성능과 풍부한 추상화를 함께 제공하는 유일한 주류 언어였고, 그래서 HFT, 게임 개발, 그래픽처럼 복잡한 도메인 모델링이 필요한 분야에서 인기를 얻었음
      물론 이런 표현력이 언어의 엄청난 복잡성을 감수할 가치가 있는지는 논쟁할 수 있지만, 실제로는 사람들이 경험적으로 C++을 선택해 왔음
  • 이 글의 구조와 어조에서는 LLM 냄새가 강하게 남