Go민보다 Go

프론트엔드 개발자

Read

GPT, JS 타입 변환에 대한 성능 차이가 발생하는 이유

SleepingOff 2024. 1. 16. 17:00

✨ 궁금해서

JS에서 타입 변환을 할 때 별도의 타입 캐스팅을 하느 것보다 연산자를 이용하여 강제 변환을 하는 편이 더 빠르다. 결과만 알고 있던 것인데, 스터디를 하는 분께서 자바에서의 출력 방법 두 개를 비교할 때, 컴퓨터 구조와 관련하여 설명하는 것을 보았습니다. 그래서 JS에서는 왜 그런지, 컴퓨터 구조적으로 볼 수 있을 지에 대해 생각해보았습니다.

자바스크립트에서 타입은 동적으로 정해지고, 타입 캐스팅을 위해 String, Boolean, Number 등의 메서드를 쓰면 어셈블리어로 해석하는 과정에서 참조를 따라 스코프를 올라가고 새로운 결과값을 반환하는 과정이 추가됩니다. 그러니 둘이 같은 타입의 경우 바로 연산에 들어갈 수 있는 것과 차이가 크지 않을까, CPU 자체에서도 데이터의 타입에 의존하지 않기 때문에 훨씬 빠른 동작을 할 수 있는 것이 아닐까라는 생각을 했습니다.

그래서 GPT에게 물어봤습니다.

답변:

맞습니다. 정적 타입 언어에서는 컴파일 타임에 타입이 결정되어 런타임에는 타입 변환이 크게 필요하지 않습니다. 반면에 동적 타입 언어인 자바스크립트에서는 런타임에 타입이 동적으로 결정되기 때문에 타입 변환이 필요한 경우가 많습니다. 이로 인해 추가적인 오버헤드가 발생하고 성능에 영향을 미칠 수 있습니다.

어셈블리 코드는 주로 기계어 명령어로 구성되어 있고, 대부분의 기계어 명령어는 데이터의 타입에 의존하지 않습니다. 어셈블리 코드는 단순히 비트 수준에서 데이터를 다루기 때문에 정수 간의 연산이나 레지스터 간의 이동은 타입에 크게 영향을 받지 않습니다.

자바스크립트에서의 동적 타입은 코드 유연성을 제공하지만, 동시에 런타임에 타입 체크와 변환을 필요로 합니다. 이는 어셈블리 코드에서는 크게 고려하지 않아도 되는 부분이지만, 고수준 언어에서는 이러한 타입 관리가 필요하게 됩니다.

정적 타입 언어는 컴파일 시간에 타입 체크를 수행하므로 런타임에서의 타입 변환이 줄어들고, 성능에 미치는 영향이 상대적으로 작을 수 있습니다. 그러나 이는 개발자의 취향과 프로젝트의 요구사항에 따라 다를 수 있습니다.


좀 더 찾아보니 타입스크립트가 타입 체크 오버헤드 발생을 줄여준다는 것까지 이해할 수 있었습니다.

🤔 추가적으로

GPT에 물어보니 데이터의 타입과 연산 과정에 대해 어셈블리어로 표현을 해주고 주석도 이쁘게 달아줘서 기록합니다.

질문: 타입 변환하는 과정에 대한 어셈블리어를 보여줘

답변:

어셈블리 코드에서의 타입 변환은 기본적으로 레지스터나 메모리에서의 데이터를 다른 형태로 적절히 변환하는 과정입니다. 아래는 간단한 어셈블리어 코드 예시입니다. 이 코드는 레지스터를 이용하여 정수와 문자열을 변환하는 예시입니다. 주의: 아래 코드는 추상적인 예시일 뿐이며, 실제 아키텍처에 따라 다를 수 있습니다.

section .data  
    ; Data section: 문자열을 저장할 메모리 공간  
    string\_buffer db 10 DUP(0)  ; 10바이트 크기의 문자열을 저장할 버퍼  

section .text  
    global _start  

_start:  
    ; 정수를 문자열로 변환 예시  
    mov eax, 42       ; 정수 42를 eax 레지스터에 저장  

    ; 정수를 문자열로 변환하는 함수 호출  
    call int_to_str  

    ; 결과물 출력  
    mov eax, 4        ; sys_write 시스템 콜 번호  
    mov ebx, 1        ; 표준 출력 파일 디스크립터  
    mov ecx, string_buffer  ; 출력할 문자열의 메모리 주소  
    mov edx, 10       ; 출력할 바이트 수  
    int 0x80          ; 시스템 콜  

    ; 프로그램 종료  
    mov eax, 1        ; sys_exit 시스템 콜 번호  
    xor ebx, ebx      ; 종료 코드 0  
    int 0x80          ; 시스템 콜  

int_to_str:  
    ; 정수를 문자열로 변환하는 간단한 함수  
    ; 입력: eax - 변환할 정수  
    ; 출력: string_buffer에 변환된 문자열이 저장됨  

    mov ecx, 10       ; 문자열을 10진수로 변환  
    mov edi, string\_buffer + 9  ; 문자열 버퍼의 끝 부분부터 채워 나갈 것임  

convert_loop:  
    sub edi, 1        ; 버퍼 위치를 하나씩 감소시킴  
    xor edx, edx      ; edx를 0으로 초기화  
    div ecx           ; eax를 ecx로 나눈 몫이 eax에, 나머지가 edx에 저장됨  
    add dl, '0'       ; 나머지를 문자로 변환  
    mov [edi], dl     ; 결과를 문자열 버퍼에 저장  

    test eax, eax     ; eax가 0인지 확인  
    jnz convert\_loop  ; eax가 0이 아니면 반복  

    ret  

이 예시 코드에서는 정수를 문자열로 변환하는 int_to_str 함수를 포함하고 있습니다. 이 함수는 나눗셈과 ASCII 코드를 이용하여 정수를 문자열로 변환합니다. 결과는 string_buffer에 저장되며, 이를 표준 출력에 출력하고 프로그램을 종료합니다.

질문: 두 데이터의 타입이 모두 숫자일 경우에 대해 위와 같이 알려줘

답변:

두 데이터의 타입이 모두 숫자인 경우, 타입 변환은 주로 형 변환이라기보다는 산술 연산이 진행됩니다. 아래는 간단한 어셈블리 코드 예시입니다. 이 코드는 레지스터를 사용하여 두 정수를 더하는 예시입니다.

section .text
    global _start

_start:
    ; 첫 번째 숫자를 레지스터 eax에 로드
    mov eax, 1

    ; 두 번째 숫자를 레지스터 ebx에 로드
    mov ebx, 2

    ; eax와 ebx를 더하여 결과를 eax에 저장
    add eax, ebx

    ; eax에 있는 결과를 어딘가에 저장하거나 사용할 수 있음
    ; 여기서는 결과를 표준 출력에 출력하는 예시를 보여줌

    ; 결과물 출력
    mov eax, 4        ; sys_write 시스템 콜 번호
    mov ebx, 1        ; 표준 출력 파일 디스크립터
    lea ecx, [eax]     ; eax의 주소를 ecx에 로드 (간단한 예시이므로 주소를 직접 사용)
    mov edx, 1        ; 출력할 바이트 수
    int 0x80          ; 시스템 콜

    ; 프로그램 종료
    mov eax, 1        ; sys_exit 시스템 콜 번호
    xor ebx, ebx      ; 종료 코드 0
    int 0x80          ; 시스템 콜

이 코드는 레지스터 eax에 1을, ebx에 2를 저장하고, 두 값을 더하여 eax에 결과를 저장합니다. 그 후 결과를 표준 출력에 출력하는 부분이 추가되어 있습니다.

타입 변환이 별도의 명시적인 과정이 필요 없이 수행되는 이유는 대부분의 어셈블리 명령어가 특정한 데이터 타입에 의존하지 않고 비트 수준에서 동작하기 때문입니다. 어셈블리는 데이터를 단순히 비트로 다루기 때문에 정수 간의 산술 연산은 별다른 변환 과정 없이 수행됩니다.

📜 그래서...

개발 공부를 시작한 초반에는 GPT가 있어도 구글 검색을 했습니다. 원하는 답과 다른 내용이 나와도 그만큼 개발과 관련한 지식을 더 많이 볼 수 있기 때문입니다. 이제 어느 정도 프로젝트도 하고 '눈떠보니 기술면접 전날'이라는 책도 쓰면서 개발 지식에 익숙해지기 시작했고, 정보에 대한 검증을 하는 과정도 난이도가 줄어들어 요즘은 GPT를 이용하고 있습니다. GPT도 가끔 엉뚱한 대답을 하거나 구글이 더 빠를 때가 있기 때문에 완전히 신뢰하는 것은 아니지만, 잘 사용할 수 있을 것 같습니다. 그래서 결론은, JS에서의 형변환은 연산자를 쓰는 경우가 메서드를 쓰는 경우보다 빠르며, 이는 어셈블리어, 기계어로 변환하는 과정에서 추가적인 과정이 있기 때문이라는 것을 알았고, 타입 변환을 할 때, 즉 타입을 확인할 때 오버헤드의 발생을 줄이는 것이 성능에 좋은 코드라는 점을 알았습니다.

728x90