2P by neo 12일전 | favorite | 댓글 1개

벤치마크

  • 코루틴이란?

    • 코루틴은 프로그램의 실행을 일시 중지하고 재개할 수 있는 컴퓨터 프로그램 구성 요소로, 협력적 멀티태스킹을 위한 서브루틴을 일반화한 것임.
    • 협력 작업, 예외, 이벤트 루프, 반복자, 무한 리스트 및 파이프와 같은 프로그램 구성 요소 구현에 적합함.
  • Rust

    • 두 가지 프로그램을 작성함: tokioasync_std를 사용한 프로그램.
    • 둘 다 Rust에서 일반적으로 사용되는 비동기 런타임임.
  • C#

    • C#은 Rust와 유사하게 async/await를 지원함.
    • .NET 7부터 NativeAOT 컴파일을 제공하여 VM 없이도 관리 코드 실행이 가능함.
  • NodeJS

    • 비동기 작업을 위해 Promise.all을 사용함.
  • Python

    • asyncio 모듈을 사용하여 비동기 작업을 수행함.
  • Go

    • 고루틴을 사용하여 동시성을 구현하며, WaitGroup을 사용하여 작업을 대기함.
  • Java

    • JDK 21부터 가상 스레드를 제공하며, 이는 고루틴과 유사한 개념임.
    • GraalVM을 사용하여 네이티브 이미지를 생성할 수 있음.

테스트 환경

  • 하드웨어: 13세대 Intel(R) Core(TM) i7-13700K
  • 운영체제: Debian GNU/Linux 12 (bookworm)
  • Rust: 1.82.0
  • .NET: 9.0.100
  • Go: 1.23.3
  • Java: openjdk 23.0.1
  • Java (GraalVM): java 23.0.1
  • NodeJS: v23.2.0
  • Python: 3.13.0

결과

  • 최소 메모리 사용량

    • Rust, C# (NativeAOT), Go는 네이티브 바이너리로 컴파일되어 적은 메모리를 사용함.
    • Java (GraalVM 네이티브 이미지)도 좋은 성능을 보였으나, 다른 정적 컴파일 언어보다는 더 많은 메모리를 사용함.
  • 10K 작업

    • Rust는 메모리 사용량이 거의 증가하지 않음.
    • C# (NativeAOT)도 적은 메모리를 사용함.
    • Go는 예상보다 많은 메모리를 사용함.
  • 100K 작업

    • Rust와 C#이 좋은 성능을 보임.
    • C# (NativeAOT)이 Rust보다 적은 메모리를 사용함.
  • 100만 작업

    • C#이 모든 언어를 압도하며 가장 적은 메모리를 사용함.
    • Rust도 메모리 효율성이 뛰어남.
    • Go는 다른 언어에 비해 메모리 사용량이 많음.

결론

  • 많은 동시 작업은 복잡한 작업을 수행하지 않더라도 상당한 메모리를 소모할 수 있음.
  • .NET과 NativeAOT의 개선이 눈에 띄며, GraalVM으로 빌드된 Java 네이티브 이미지도 메모리 효율성이 뛰어남.
  • 고루틴은 여전히 자원 소비 면에서 비효율적임.

부록

  • Rust (tokio)에서 join_all 대신 for 루프를 사용하여 메모리 사용량을 절반으로 줄임. Rust가 이번 벤치마크에서 절대적인 선두를 차지함.
Hacker News 의견
  • 벤치마크가 Node와 Go의 비동기 처리 방식의 차이를 제대로 반영하지 못함. Node는 Promise.all을 사용하고 Go는 고루틴을 사용하여 차이가 있음. 비동기 I/O와 CPU 바운드 작업의 메모리 사용량 차이를 비교하는 것이 흥미로울 것임

  • "10초 동안 대기하는 작업"과 "10초 후에 깨우는 작업"의 차이를 설명함. Go 코드의 메모리 사용량이 다른 코드와 비교하여 차이가 큼

  • Go와 Node의 공정한 비교를 위해 타이머를 스케줄링하는 고루틴과 타이머 신호를 처리하는 고루틴을 사용하는 방법을 제안함. Node에 Bun과 Deno가 포함되지 않은 점이 이상하다고 언급함

  • 많은 동시 작업이 메모리를 많이 소비할 수 있지만, 작업당 데이터가 몇 KB 이상이면 스케줄러의 메모리 오버헤드는 무시할 수 있을 정도임

  • "동시 작업"의 정의에 따라 메모리 사용량이 달라질 수 있음. 효율적인 구현에서는 1M 동시 작업에 약 200MB가 필요함

  • Go가 메모리 사용량에서 Java에 비해 2배 이상 뒤처진다는 점을 지적하며, 벤치마크가 실제 프로그램을 대표하지 않는다고 언급함

  • 간단한 코드로 언어를 비교하는 것이 개발자에게 불공평할 수 있으며, 실제 작업을 추가하여 메모리 사용량과 스케줄링 차이를 측정할 것을 권장함

  • 벤치마크가 종종 오류로 가득 차 있으며, 이러한 벤치마크를 게시하는 사람들의 동기를 이해하지 못하겠다고 말함

  • Java 벤치마크가 잘못되었을 가능성이 있으며, ArrayList의 초기 크기를 지정하지 않아 불필요한 객체가 많이 생성됨

  • Rust의 비동기 코드가 예상보다 빠르게 완료되는 이유를 설명함. tokio::time::sleep()이 미래가 생성된 시점을 추적하기 때문임