본문 바로가기
개발상식

[개발상식] 런타임이란

by devjh 2024. 11. 6.
반응형

런타임이란

기본적으로는 프로그램이 실행되는 동안의 환경을 의미하기도 하지만

코드를 작성 한 뒤 실행가능한 환경에 올라가는 모든 과정을 포함 합니다.

 

1. 왜 런타임을 알아야하는가

oom이 발생한 경우를 생각해보겠습니다.

oom이 발생한 원인을 확인하고 개선방안을 고민하려면

  • 로직상의 개선이 필요한건지
  • 런타임의 개선이 필요한건지
  • 실제 서버 or k8s의 노드의 스펙의 문제인지
  • 확장이 필요하다면 수직 수평중에 어떤 증설이 필요한지

런타임에 대한 이해가 없다면 판단이 보다 수월해 질 수 있습니다.

 

2. 컴파일 방식

먼저 런타임에 대한 이해를 위해 컴파일에 대해 정리합니다.

컴파일이란 우리가 작성한 코드를 기계에 가까운 언어로 변경하는 과정을 말합니다.

언어, 컴파일방식에 따라 개념이 조금씩 다릅니다(빌드란 컴파일된 결과물을 이용해 실행가능한 형태로 만드는 과정을 말하며 이는 컴파일 과정을 포함합니다.)

저는 컴파일 방식을 크게 세가지로 구분하겠습니다.

 

1) 정적컴파일 방식

  • c, c++, golang rust 등의 컴파일 방식
  • 빌드결과물은 기계어

빌드시점에 대부분의 컴파일이 끝나 기계어로 변환된 최적화된 결과물이 나오는 컴파일 방식입니다.

결과물은 운영체제에서 실행 가능한 기계어로 된 실행파일입니다.

성능이 가장 우수합니다.

 

2) 동적컴파일 방식

  • java, c# 등의 컴파일 방식
  • 빌드결과물은 바이트코드(IL)
  • 가상머신위에서 바이트코드를 한줄씩 읽는 해석기처럼 동작하지만 vm 자체에 내장된 jit컴파일러로 성능보정

빌드시점에 컴파일을 진행하지만 IL(Intermediate Language)이라는 바이트코드의 결과물이 나오는 컴파일 방식입니다.

IL은 java의 경우 .class파일이, c#의 경우 .dll 이 생성됩니다.

컴파일 후 빌드과정에서는 .class, .dll 파일을 묶어(링크) 실행가능한 파일이 나오지만(exe, jar, war)

이는 정적컴파일 결과물처럼 운영체제에서 실행가능한 결과물이 아닌 IL을 묶어놓은 형태입니다.

IL을 묶어놓은 결과물은 운영체제가 독립적으로 해석할 수 없으며 java의 경우 jvm(Java Virtual Machine), c#의 경우 clr(Common Language Runtime)이라는 가상머신을 설치하여 실행해야 합니다.

가상머신에 올라간 바이트코드는 jvm과 clr이 한줄씩 해석하여 동작합니다. 이는 기계어가 아니므로 정적 컴파일 방식보다는 성능이 떨어지는 편입니다.

이를 개선하기 위해 vm들 내에 jit(just in time) 컴파일러가 존재하며 실행시점에 동적으로 필요한 부분을 기계어로 바꾸어 성능을 개선합니다.

 

3) 인터프리터 방식

  • js, python 등에서 사용되는 방식
  • 빌드, 컴파일이 없다고 알려져있음

따로 컴파일 과정이 없으며 실행할때 해석기가 코드를 한줄한줄 읽어 cpu를 동작 시키는 구조입니다.

일반적으로 하나의 프로세스는 하나의 cpu 코어를 사용하는 방식이라 node, python으로 개발된 서버는 내부에서 동시에 많은 비동기처리나 스레드를 만들어도 cpu전체를 효과적으로 활용하지 못한다고 알려져 있으며 컴파일단계에서의 타입체크를 진행하지 않아 타입안정성이 떨어진다는 단점이 존재합니다.

 

그러나 이러한 부정적인 시각은 과거의 이야기이고 

최근에는 python파일을 컴파일하면 pyc라는 바이트코드가 되며 pvm에서 해석되며 typescript파일을 컴파일하면 js파일이 되어 타입 안정성이 많이 해소된 상황입니다.

런타임에 타입에러로 인한 버그를 막기위한 방식이 나왔고 jit 컴파일러를 사용하여 성능도 향상되어 가고 있습니다.
언어자체가 동적타입언어라 jit 컴파일러의 동작자체의 비용이 큰다는 단점이 있긴 하지만 클라우드를 사용한다면 cpu를 적게 사용하여 비용이 절감된다는 장점도 있습니다.(성능은 쪼오금 떨어지지만 하나의 pod이 적은수의 cpu코어를 사용하므로 클라우드 비용은 대폭 감소)
레이턴시가 늘어지는 경우는 언어적 문제보다는 주로 잘못된 설계, 부족한 인덱스, 타 엔드포인트의 부하로 발생하는 편이며
현시점 레이턴시 이슈로 스크립트 언어를 기피할 필요는 없습니다.

 

3. 런타임 메모리구조

일반적으로 운영체제의 메모리구조는 코드 데이터 스택 힙의 구조를 가지고 있다고 알려져있지만

실제로 내가 작성한 코드의 런타임은 해당 메모리 구조 위에서 재해석됩니다.

대부분 스택영역과 힙 영역을 가지고 있고 런타임에 환경에 따라 부가적인 메모리 영역이 존재합니다.

 

1) 스택

스택 영역은 LIFO 구조로 메서드 내부에서 여러 모듈을 호출하면 스택에 차곡차곡 쌓인 후 마지막 쌓인것부터 메모리할당을 해제하는 구조이며 가득차게 되면 stack over flow라는 에러가 생성되게 됩니다 일반적으로 서버에 request가 들어오게 되면 내부에서 thread 등이 동작하면서 stack을 사용하게 됩니다.

 

2) 힙

heap 영역은 동적으로 할당되는 구조이며 런타임마다 세대가 나눠져 있는 경우가 많습니다.(오래된 메모리는 gc가 더 자주 체크)

언어마다 조금씩 차이가 있지만 참조하는 횟수를 체크하여 메모리를 해제하거나, 루트 경로부터 모든 메모리를 체크하여 체크되지 못한 메모리는 해제하는 방법이 대표적입니다.

보통 메모리관련 문제가 생긴다면 대부분 힙영역에서 생기므로 이 영역에 관심이 많아야 합니다. 메모리에 너무 많은 데이터를 로드하거나, 로컬캐싱을 너무 많이하거나, 등 대부분 로직을 개선해야하는 케이스가 많습니다.

 

3) 부가적인 메모리 구조

vm관련 메모리, 코드자체의 영역, 캐시, 런타임을 안정성을 위한 메모리 영역 등등 런타임마다 조금씩 상이한 구조입니다. 작성한 로직과는 큰 관계가 없으므로 보통 런타임의 설정으로 조절할 수 있습니다.

반응형

댓글