ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [CSAPP] CH1. Introduction to Computer Systems - 컴퓨터구조에 대한 이해
    컴퓨터구조 2023. 6. 10. 03:33
    반응형
    5 great realities of Computer Systems


    #1. Int는 정수(Integers)가 아니고, Float은 실수(Reals)가 아니다.

     

    이게 무슨 말이냐, 예를 들어서 이해를 해보자.

     

    x^2>0

     

    이 식이 항상 성립하는가?

    x가 Float이라면, 그렇다.

     

    x가 int 자료형이라면 만약 x=40000일 때, 40000 * 40000= 1600000000이 나와서 저 식은 참이 될 것이다.

     

    하지만, x가 5만이라면? x^2은 int 의 숫자범위를 벗어나 쓰레기 값이 나올 것이다.(주로 음수 값)

    이것이 반례이다.

     

    또하나 식을 살펴보면,

    (x + y) + z = x + (y + z) 

     

    이 식 또한 항상 성립할까?

    우리가 잘 아는 교환법칙이다. 당연히 실수(Real Number)에서도 성립한다.

    마찬가지로, 이 식의 반례를 찾아보자.

     

    만약, x,y,z가 Unsigned & Signed Int 라면 항상 성립한다.

     

    다음으로 float일 때 다음 식들을 살펴보자.

    (1e20 + -1e20) + 3.14 의 결과 값은 3.14가 된다.

    1e20 + (-1e20 + 3.14) 의 결과는 어떻게 될까? 결과는 3.14가 아니라 0이 나오게 될 것이다. 명백한 오류인데, 이 부분이 처음에 이해하기 어려웠다.

     

    이걸 이해하려면 부동소수점 정밀도 개념에 대해서 알아야 한다.

    뒤에서 부동소수점에 대해 배워야 완벽하게 이해할 수 있을 것이다.

    여기서는 그냥 이런게 있다 정도로만 이해해보도록 하자!


    컴퓨터는 부동소수점을 표현할 때, 제한된 비트 내에서 지수부분, 소수점 부분 영역을 나눠서 표현한다. 그런데, 말그대로 소수점 부분이 제한된 메모리 영역을 가지기 때문에, 숫자의 크기가 매우 크거나 작을 때, 작은 숫자에 대한 정밀도 손실이 발생할 수 있다.

    실제로 계산을 수행해보면, "-1e20 + 3.14"는 "-1e20"과 "3.14"의 크기 차이로 인해 "-1e20"의 값에 대한 오차가 무시된다. 따라서, 괄호 안의 연산 결과는 약간의 오차가 있는 "3.14"가 된다. 이후 "1e20"과 더해지면, "1e20"과 "3.14"가 더해져 약간의 오차가 있는 "1e20 + 3.14"의 결과가 나오게 된다.

     

     

    #2. Assembly를 꼭 공부해야한다.

     

    어셈블리어는 저수준 프로그래밍 언어로, 컴퓨터 아키텍처의 기계어에 가까운 형태로 작성된다. 

     

    우리가 어셈블리어로 코드 짤 일은 아마 없을 것이다. 애초에 컴파일러들이 다 변환을 해주기 때문이다.

    그런데 왜 배워야 하느냐!

     

    어셈블리어를 이해하면 프로그램이 버그를 포함했을 때 어떻게 동작하는지 이해할 수 있다.

     

    우리가 사용하는 c언어, 파이썬 같은 고수준 언어 모델은 어셈블리어로 작성된 기능을 추상화하여 제공한다. 근데  어셈블리어를 공부하면 고수준 언어가 기계어로 어떻게 변환되는지 이해할 수 있다. 이를 통해서 컴파일러가 수행하는 최적화를 이해하고, 프로그램의 성능을 향상시키기 위해 우리가 직접 최적화를 수행할 수 있는 것이다.

     

    어셈블리어를 알면 프로그램의 효율성에 대한 원천을 이해할 수 있다. 어셈블리어는 메모리 액세스, 연산, 분기 등 기계 수준의 동작을 직접 표현할 수 있다. 따라서 프로그램이 어떻게 동작하고 효율적으로 실행될 수 있는지 이해할 수 있다.

     

    어셈블리어를 알면 시스템 소프트웨어 개발에 유용하다. 운영 체제는 프로세스 상태를 관리하고 하드웨어와 상호 작용해야 한다. 어셈블리어를 이해하면 운영 체제 개발에 필요한 저수준 기능을 구현할 수 있다.

     

    악성 소프트웨어 분석과 대응에도 어셈블리어가 필요하다. 많은 악성 소프트웨어가 어셈블리어로 작성되어 있으며, 이를 이해하고 분석하여 보안 조치를 취할 수 있다.

     

    #3. 메모리는 정말정말 중요하다.

     

    메모리가 무한하지 않으며, 운영 체제가 메모리를 할당하고 관리해야 한다는 것을 의미한다. 운영 체제는 프로그램이 필요한 메모리를 할당하고, 필요하지 않은 메모리를 회수하여 효율적인 메모리 관리를 수행한다.

     

    메모리 참조 버그는 특히나 치명적이다. 프로그램에서 메모리 참조 오류가 발생하면 예상치 못한 결과가 발생할 수 있다. 심각한 버그로 인해 프로그램이 비정상적으로 동작하거나 충돌할 수 있다.

     

    메모리 성능은 획일화되어 있지 않다. 캐시와 가상 메모리 시스템은 메모리 액세스의 성능에 크게 영향을 줄 수 있다.

    뒤에서 배우겠지만, 캐시 메모리(cache)는 CPU 근처에 위치한 속도가 아주 빠른 메모리로, 중간에서 메모리 액세스 속도를 향상시키는 역할을 한다. (우리가 한번씩 개인정보 보호를 위해 캐시 메모리를 삭제할 때 그 캐시 메모리이다!)

     

    가상 메모리는 물리적인 메모리 용량을 초과하는 프로그램 실행을 가능하게 하고, 하드 디스크 등의 보조 기억 장치와 조합하여 사용되는데, 어떻게 사용하느냐에 따라서  프로그램의 성능에 큰 영향을 줄 수 있다. 그래서 메모리 시스템의 특성에 맞게 프로그램을 최적화하면 캐시와 가상 메모리 효과를 최대한 활용하여 성능을 향상시키기 때문에, 프로그램의 실행 속도를 크게 향상시킬 수 있다.

     

     

    #4. 성능 향상을 위해 점근적 복잡도 말고도 정말 다양한 요소들을 고려할 수 있다.

     

    성능에는 상수 요소도 중요하다. 알고리즘의 점근적 복잡도는 입력 크기에 대한 성능 추이를 나타낸다. 그러나 알고리즘의 성능은 상수 요소에도 영향을 받는다다. 두 개의 알고리즘이 같은 점근적 복잡도를 가지더라도, 상수 요소가 큰 알고리즘이 더 나쁜 성능을 가질 수 있습니다.

    정확한 연산 횟수조차도 성능을 정확히 예측하지 못한다. 연산 횟수는 프로그램의 명령어 수를 나타내지만, 실제 실행 속도에는 다른 요소들이 영향을 줄 수 있다. 예를 들어, 메모리 액세스 패턴, 캐시의 활용, 하드웨어 특성 등이 프로그램의 성능에 큰 영향을 줄 수 있다.

    성능을 최적화하려면 알고리즘, 데이터 표현, 프로시저, 루프 등 다양한 수준에서 최적화해야 한다. 최적화는 단순히 알고리즘을 개선하는 것뿐만 아니라, 데이터 구조나 함수의 호출 방식 등을 변경하여 성능을 향상시킬 수도 있다.

    다섯째, 앞에서 말한대로, 성능을 최적화하려면 시스템을 이해해야 합니다. 프로그램이 어떻게 컴파일되고 실행되는지, 시스템의 구조와 동작 원리를 이해해야 해당 시스템에서 최적의 성능을 얻을 수 있다.

    여섯째, 프로그램의 성능을 측정하고 병목 현상(bottlenecks)을 식별하는 방법을 알아야 한다. 성능 프로파일링 도구를 사용하여 프로그램의 실행 시간 분석이나 메모리 사용량 등을 측정하여 병목 현상을 찾을 수 있다.

    마지막으로, 코드의 성능을 향상시키는 동시에 모듈성과 범용성을 유지해야 gks다. 성능을 향상시키기 위해 코드를 지나치게 최적화하면 가독성이나 유지보수성이 떨어질 수 있다. 따라서 성능 개선과 코드의 모듈성과 범용성을 균형있게 고려해야 한다. -> 무조건 짧은 코드가 더 좋은 코드라고 할 수 없는 이유!

     

    메모리에 배열 요소가 담기는 순서, 따라서 접근하는 순서가 다르기 때문에 위의 사진과 같이 i, j 순서만 바꿨을 뿐인데 속도 차이가 정말 많이 나는 것을 알 수 있다.

     

    #5. 컴퓨터는 프로그램 실행만 하는 도구가 아니다.

    컴퓨터는 프로그램 신뢰성과 성능의 정말 중요한 요소인 입출력 시스템도 가지고 있고,
    네트워크를 구성해 서로 communicate하기 때문에, 그에 따라 보안, 호환성 같은 network-level 문제들도 일어나기도 한다.


    Computer Systems Overview

    프로그램 실행에 관련된 구성요소

     

     

     

    응용 소프트웨어: 사용자가 사용하는 소프트웨어 애플리케이션

    우리가 일반적으로 배우는 c언어, 파이썬, 자바와 같은 고수준 언어로 작성된다. 사용자의 요구를 충족시키기 위한 특정 기능을 수행한다.

    시스템 소프트웨어: 응용 소프트웨어의 실행을 지원하기 위한 소프트웨어 계층

    - 컴파일러: 고수준 언어 코드를 기계어로 변환하는 역할을 수행합니다. 이는 응용 소프트웨어를 실행 가능한 형태로 변환합니다.

    - 운영 체제: 시스템 소프트웨어의 기본이 되는 핵심 요소로, 다음과 같은 다양한 기능이 있음.
        1. 입력/출력 처리: 사용자와 시스템 간의 데이터 흐름을 관리
        2. 메모리 및 저장장치 관리: 메모리와 저장장치의 할당과 해제를 관리
        3. 작업 스케줄링과 자원 공유: 여러 작업을 효율적으로 실행하고 시스템 자원을 공유

     

    하드웨어: 컴퓨터 시스템의 물리적인 부분으로, 다양한 하드웨어 구성 요소로 구성
     - 프로세서: 명령어를 실행하고 연산을 처리하는 중앙 처리 장치
     - 메모리: 데이터와 프로그램 명령어를 저장하는 공간
     - I/O 컨트롤러: 입력과 출력 장치를 제어하고 데이터를 전송

     

     

     

    고수준 언어 (High-level language):
    추상화 수준의 언어. 프로그래머가 문제를 해결하고 애플리케이션을 개발하는 데 도움을 주는 기능과 구문을 제공. 사람이 이해하기 쉽고 생산성이 높으며, 이식성이 좋다. ex) C, Java, Python

    어셈블리어 (Assembly language):
    기계어 명령어를 텍스트로 표현한 것. 어셈블리어는 기계어와 1:1 대응, 기계어와 직접적으로 연결된 언어입니다. 어셈블리어는 기계어 명령어의 의미를 이해하기 쉽도록 니모닉(mnemonic)이라는 기호를 사용. ex), x86 아키텍처에서 "MOV"는 데이터를 이동하는 기계어 명령어를 의미.

    기계어 (Machine language):
    기계어는 컴퓨터 하드웨어에서 직접 실행되는 이진 숫자(0과 1로 구성된 비트)로 표현된 명령어와 데이터. 기계어는 컴퓨터의 아키텍처에 따라 다르며, 특정 프로세서 또는 컴퓨터 시스템에서 실행되는 명령어 집합을 포함. 기계어는 컴퓨터가 이해하고 실행할 수 있는 가장 낮은 수준의 코드이다.

     


    다음 글에서는 , Data Representation(CH 2.1 ~ CH 2)에 대해서 알아보겠다.

     

    읽어주셔서 감사합니다. 🙏🏻 혹여 잘못된 내용이나, 이해가 가지 않는 부분은 댓글로 남겨주세요.

    반응형
Designed by Tistory.