관리언어는 정말 느린가?

프로그래밍 업계에서 성능이 좋은 언어라고 말하면 go vs rust 가 나옵니다.

절대 python, javascript가 빠르다고 하는 사람은 없지요.

그런데 Go는 알려진대로 Garbage Collector가 있는 관리 언어입니다.

Rust는 알려진대로 Garbage Collector가 없는 비관리 언어입니다. 비슷한 비관리 언어로 C, C++이 있지요.

관리 언어는 가비지 컬렉터로 인해 성능이 잘 나오지 않기 때문에 비관리 언어가 성능측면에서 좋다라는 말을 많이들 합니다.

그런데 그럼 Go는 관리언어인데 왜 인식이 좋은걸까요?

Go로 개발된 기가막힌 솔루션에는 제가 알기로는 Docker와 Kubernetes가 있습니다.


가비지 컬렉터가 있어서 느리다. 라는 것은 아무래도 가비지 컬렉션이 발생할 때의

  1. STW (Stop-The-World)
  2. Memory Compaction

두 가지 때문일꺼라고 생각합니다.

가비지컬렉션이 동작할 땐 프로세스 안의 모든 스레드가 일시정지하는 순간이 오고 수집할 객체가 많을 수록 이 시간이 크다고 합니다.

그리고 가비지 컬렉션이 발생한 후에는 Heap에 메모리 할당을 빠르게 하기 위해 수집된 객체들이 빵꾸낸 자리를 메꿔서 매모리들을 재정렬시킨 다음 순차적으로 메모리 할당을 하기 시작한다고 하죠.

물론! 이 과정에서 당연히 객체의 Heap 메모리 주소는 바뀌게 될 것입니다.

관리언어가 메모리를 할당하는 속도가 빠른 이유는 이 메모리 압축이 발생하여 객체를 할당할 시작주소를 이미 알고 있기 때문입니다.

저는 비관리 언어는 잘 모르지만 메모리를 할당하는 시간은 관리언어에 비해서 느리다고 알고 있습니다. GC가 없이 수동해제하기 때문에 개발자가 원할때 바로 제거할 수 있어서 그런 면에선 좋지만요.

이런 것들을 피하기 위해 가비지 컬렉션이 빈번하게 발생하는 0,1 세대가 아니라 2세대에 큰 객체를 할당하여 Full GC만 이용하도록 하면 서비스의 휴지기에 수집을 하여 사람들이 불편을 느끼지 않을 수 있다고 합니다. (여기서 0,1,2 세대는 .NET 가비지 컬렉션 기준이고, 다른 언어는 용어나, Generation 수가 다를 수 있습니다.)

또한 최근 .NET 5부터 등장한 POH같은 것으로 가비지 컬렉션의 영향을 받지 않는 Heap에 메모리를 할당하는 것입니다.

관리언어가 Heap에 대해 개발자가 직접 해제를 할 수 없어서 느리다고 알려져 있는데, 다른언어는 모르겠으나 적어도 .NET에서는 가비지 컬렉션이 메모리를 할당하고 제거하는 Heap은 GC Heap이라고 불리지요. (사실 가비지 컬렉터가 메모리를 제거만하지 할당을 한다는 개념은 모르는 사람도 있긴 했습니다.)

즉, GC Heap이 아닌 Heap에 할당하여 개발자가 직접관리하면 되는 부분이라고 생각합니다. 비슷한 맥락으로 가비지 컬렉터가 접근하지 못하는 Heap은 static 객체들이 할당되는 High Frequency Heap도 있고, Literal String이 저장되는 Intern Pool도 존재합니다.

그 밖에도 가비지 컬렉터가 접근하지 못하는 여러 Heap이 많은 것으로 알고 있습니다.

같은 하드웨어 위에서 동작하니만큼 똑같은 CPU의 IO를 탈 것이고 아키텍쳐도 같을 텐데 go만 성능적으로 우수하다는 인식이 생긴 이유는 무엇일까요?

9 Likes

제 경험상. 객체 할당을 최소화 하면서 최적화만 잘 해 두면 C++보다 빠른 코드도 가능합니다.
비슷한 압축 알고리즘을 최적화하여 C#으로 이식하였는데 벤치마크 결과 더 빨랐던 기억이 있습니다.

관리 언어가 느리다는 인식은

  1. 이전 닷넷 프레임워크에서는 메모리 관리 지원이 부실했기에
  • Span<T> 등이 생기며 불필요한 메모리 복사가 매우 크게 줄어듬
  • ArraySegment<T>는 이전에도 존재했으니 지원이 매우 부실했음
  1. 변수 할당의 편의성과 그에 비해 최적화되지 않은 코드
  • 보통 유니티 같은 경우가 아니면 GC를 건드리지 않는 것이 대부분
  • 변수 할당을 수시로 하고 언제 해제되는지는 고려하지 않은 경우가 다수
  • 심지어 Dispose 조차 제대로 하지 않는 경우도 많음
  1. 자바
  • 자바는 그냥 느립니다.
8 Likes

의견 감사합니다.

개인적으로 이번에 Garnet의 방향과 상용화가 어디까지 진행될지가 가장 궁금한데,

Redis는 C로 개발되었고 Garent은 .NET으로 개발되었는데

컨셉이 겹치다보니 Redis의 레퍼런스가 많은 것을 떠나 성능적으로 대체가 가능하다면

.NET도 빠르다는 인식을 가질 수 있을 것 같아서 궁금하네요.

Orleans도 그렇고…

3 Likes

go는 스테틱 링킹으로 exe 파일 하나로 배포와 소켓을 스텐다드 라이브러리로 제공한게 c와 큰 차이점이라고 보네요.

c 개발할때 winsock2과 bsd소켓에 크게 고통받아서 그런가 go가 그렇게 마음이 편했었죠.

3 Likes

.NET으로 해석한다면 .NET Core부터 정식 지원되는 단일 어셈블리 빌드&배포 기능과 비슷한 것으로 이해가 되는군요.

.NET Framework때는 Fody나 IL Merge 같은 거 써야했으니…

그나저나 가비지 컬렉션이 가장 큰 차이점이 아닌 것은 새로운 관점이네요 ㅎㅎ 의견 감사합니다.

1 Like

주식 유튜버 슈카채널에 나온 유명한 얘기가 있죠.

A: “그럼 이 하락장에서 어떻게 대응하죠?”

B: “버텨야 합니다. 어쩔 수 없어요. 버텨야 합니다.”

A: “아니, 그런 말 말고, 어떻게 해야 대응할 수 있냐구요”

B: “과연 너가 그때 대응할 수 있었을까요? 가??”

과연 직접 메모리를 관리한다면,

당신이 메모리 관리를 GC보다 잘할 자신이 있습니까?

라는 질문으로 정리할 수 있을 것 같습니다.

4 Likes

메모리 뿐만 아니라 컴퓨터가 어떻게 동작하는지를 아는건 중요하다고 생각하지만

몰라도 실무에서 큰 사고가 나거나 하지는 않아서…

추상화를 통해 보다 당장 눈앞의 비즈니스 논리는 해결하는 것이 회사의 이익을 가져오는 거 같아 안타깝죠.

내마음은 이렇지만 누군가에게 강요할 수 없는

1 Like

관리언어가 GC로 메모리 관리한다는 의미가 아니지 않나요?
Go언어는 네이티브+GC라 비교 대상에서 좀 거리가 있는 것 같고 고성능 언어는 아니니까요
결론은 GC가 성능을 저하시킬 수 있다고 봅니다.

2 Likes

이것 때문에 러스타시안들이 Rust를 선호하는 이유이기도 합니다.

GC도 안쓰면서 메모리까지 안정적이라서요.

1 Like

호오…관리언어는 GC로 메모리 관리한다는 의미로만 생각했는데 다른 의미가 있군요…?

혹시 어떤 의미일까요??

Go는 GC가 있긴 하나 IL같은 중간언어 없이 바로 바이너리가 나오는 개념으로 알고 있는데 Go가 고성능 언어는 아니라는 말은 그라목손님의 주장에서 처음 접하는 것입니다.

혹시 어떤 근거가 있으실까요??

무척 흥미로운 관점입니다.

아…동영상을 재생할 수 없다고 나오는군요 ㄷㄷ

아 좀 찾아보니 간단한 사실을 까먹었었군요… 관리언어는 VM의 유무로 판단할 수 있겠네요 ㅋㅋ

[CS 기초] Managed vs Unmanaged 언어 — AlgorFati의 개발 기록 (tistory.com)

직접 재생이 아니라
유투브에 들어가서 재생하면 됩니다.

1 Like

관련 글이 상단에 계속 떠서… 저도 의견을 달아봅니다.

먼저 "관리언어는 정말 느린가?"로 짧게 정의하기에는 다양한 변수가 있어서 뭐라 할 수는 없지만 최적화된 비관리언어에 비해 빠른가 하면 그렇지는 못하다고 생각합니다.

같은 맥락에서 Go vs Rust의 경우 최적화된 Rust의 코드 결과가 월등히 뛰어날 것으로 생각하고요,

그렇다면 C# vs Go에서 Go가 우위에 있는 (특히 도커 등의 환경에서) 이유는 무엇인가? Go는 네이티브+GC 이기 때문이겠지요.

C#의 경우 NativeAOT가 이제는 상당히 숙성했기 때문에 Go와 비교될 수준으로 Native 컴파일 환경이 개선되었다고 생각합니다.

다른 관점으로는

Go의 장점이 관리언어의 장점 보다는 언어 자체의 단순함에서 Go 언어의 인기를 찾아야 하지 않을까 생각을 해보는데요, 실제로 유명한 서비스들이 Go로 만들어진 경우가 많습니다. (예를 들어 Caddy 등)

3 Likes

.NET의 경우 NativeAOT 환경을 계속해서 개선하고 있기 때문에 콘솔 프로그램의 경우 이미 훌륭한 네이티브 코드를 생성할 수 있습니다. 여러 커뮤니티 활동으로 GC를 제거할 수도 있고요. 동작 속도도 상당히 빠릅니다.

메모리 관리형 언어의 경우 가비지 콜렉션을 최소화 하는 것이 최적화의 방향이기 때문에 힙 할당에 신경을 써줘야 합니다. 이는 Go 언어도 마찬가지에요. .NET의 경우 ref structSpan<T>를 이용해서 힙 메모리할당을 최소화 하는 다양한 방식을 이미 적극적으로 적용하고 있습니다. 아마도… .NET 8 또는 .NET 9의 최적화된 코드는 Go에 못지 않게 빠를 것 같아요.


2 Likes

의견 감사합니다. Go가 키워드가 적은 것이 인기가 좋은 이유 중 하나라고 듣긴 했었는데 퍼포먼스보다는 이쪽으로 접근을 해야겠군요…감사합니다

NativeAOT는 제가 아직 지식이 거의 없어서… 아는거라곤 기존에 코딩하던 방식대로 C#을 쓰면 안되고 좀 여러가지 익숙하지 않은 방법들로 해야한다고만? 알고 있습니다.

NativeAOT를 쓰면 그래도 Go와 비빌 수는 있게 될 수 있다는 의견 감사드립니다.

네… NativeAOT 컴파일된 실행코드의 사이즈도 인상적입니다. 프로젝트가 콘솔 애플리케이션이라면 적극 검토해보셔도 좋구요. ASP.NET Core (.NET 9 미리보기 기준) 에서 OpenAPI, SignalR도 이제 NativeAOT를 지원합니다.

2 Likes

관리 코드는 런타임에 의해 컴파일되고, 실행됩니다. 보통 중간 언어라고 부르죠.
네이티브 코드(비 관리 코드)는 OS에 의해 바로 실행됩니다. 보통 바이너리 코드라 부르죠.

관리 언어를 컴파일하면, 런타임을 위한 관리 코드(중간 언어)가, 비 관리 언어를 컴파일하면 OS를 위한 바이너리 코드가 생성됩니다.

Go 코드를 컴파일하면 바이너리 코드가 생성되기 때문에, 관리 언어가 아닙니다.

그런데, Go 는 특이하게 “런타임” 기능을 제공하는데, 이는 라이브러리로 구현되어 있습니다.
닷넷은 런타임이 주캐인데 반해, Go에서는 부캐라고 할 수 있죠.

2 Likes

말씀하신 런타임이 GC를 포함한 여러 기능들인가요?
라이브러리로 되어있었군요…
감사합니다.

1 Like

GC는 런타임이 제공하는 서비스 들 중에 하나 입니다.

그 런타임이 주캐이든 부캐이든 말이죠.

런타임이 제공하는 서비스 중에 또 다른 한 축은 보안입니다.
이 보안 때문에, C/C++ 을 더 이상 쓰지 말라고 하는 것이고, Rust 가 각광 받고 있는 것이죠.

1 Like