본문 바로가기

프로그래밍/프로그래밍 언어론

프로그래밍 언어론 독학 3일차

책 제목 : Programming Language Pragmatics (Michael L.Scott 저)

 

 

3일차 목표 - 5.1까지 학습 완료하기

 

 

3.6. 범위 내에서의 바인딩

 

 

*별칭

    - 두 개 이상의 이름이 같은 범위에서 동일한 객체를 가리키는 것.

 

 

*오버로딩

    - 같은 이름이 해당 범위에서 하나 이상의 객체를 참조하는 것.

 

 

3.6.1. 별칭

 

 

- 별칭은 포트란의 커먼 블록과 동치 문장, 파스칼이나 C#에서의 가변 레코드와 union에서 쉽게 찾을 수 있다. 또한 프린터 기반의 자료구조를 사용하는 프로그램에서도 나타난다.

- 별칭이 발생하는 다른 경우가 있다. 어떠한 서브루틴에 변수를 참조로 전달하는데, 해당 서브루틴이 그 변수를 직접 접근할 수 도 있을 때이다.

- Ruclid와 Turing은 명시적/묵시적 서브루틴 임포트 리스트를 통해 별칭을 사용했을 때 프로그래머가 의도하는 동작방식이 나올 수 있도록 하였다. 이 때 컴파일러는 임포트 리스트가 주어지면 서브루틴 호출이 별칭을 만드는지 여부를 확인할 수 있으며 언어차원에서 별칭을 금지할 수 있다.

- 별칭은 일반적으로 프로그램을 이해하는 데 어려움을 증가시킨다. 또한 컴파일러가 코드 개선을 하기 훨씬 어렵게 만든다.

 

 

3.6.2. 오버로딩(Overloading)

 

 

- 대부분 프로그래밍 언어는 제한적 형태로 오버로딩을 지원한다.

 

 

*열거형 상수 오버로딩

    - 조금 더 복잡한 형태의 오버로딩으로 Ada의 열거형(enumeration)상수에서 나타난다.

    - C, C++과 표준 파스칼은 열거형 상수를 중복 정의할 수 없다. 즉 주어진 범위 내 열거형 상수는 전부 서로 달라야        한다는 것이다.

 

 

*Ada와 C++에서의 오버로딩

    - 서브루틴 이름을 서로 중복하게 만드는 기능을 지원하고 있다.

    - C++의 이러한 방식은 자바와 C#에도 그대로 이어졌다. 주어진 이름이 동일 범위에서 임의 개수의 서브루틴을            그대로 나타낼 수 있다. 단, 서브루틴의 아규먼트 수와 타입이 달라야 한다는 조건이 있다.

 

*빌트인 연산자 재정의

    - Ada, C++, C#, 포트란 90은 빌트인 수학 연산자를 사용자 정의 함수로 오버로딩하는 것을 허용한다.

 

 

3.6.3. 다형성과 관련 개념(Polymorphism)

 

 

- 서브루틴의 이름을 다루는데 오버로딩과 비슷한 개념으로 자동 형전환(coercion)과 다형성(Polymorphism)이 있다. 셋 모두 하나의 이름을 가진 루틴이 여러 가지 타입의 아규먼트를 받을 수 있다는 공통점이 있다.

 

 

*오버로딩

    - 두개의 최소값을 계산할 때 정수와 실수 모두 지원하게 만들려고 할 경우 C++에서는 중복정의된 함수로 원하는        결과를 얻을 수 있다.

    - C++컴파일러가 아규먼트 타입에 따라 다른 버전의 함수 중에 하나를 고르도록 하는 것이다.

 

 

*자동형전환

    - 자동형전환은 컴파일러가 주어진 타입의 값을 다른 타입의 값으로 자동 변환하는 절차이다. 대부분의 스크립트        언어는 훨씬 더 많은 형전환을 제공하며, C++은 프로그래머가 추가적인 형전환을 만들 수 있게 허용한다.

    - C컴파일러가 하나의 서브루틴에 맞게 아규먼트를 수정하는 것을 허용하는 것이다.

 

 

*다형성

    - 여러 형태를 가진이라는 뜻으로 자료구조나 서브루틴에 적용하면 코드가 여러 타입의 값에 대해 동작할 수 있게        한다는 것이다.

    - 하나의 서브루틴이 변하지 않은 채 여러 타입의 아규먼트를 그대로 받아들일 수 있게 허용하는 것이다. 이 개념       이 적용되면 코드가 여러 타입의 값에 동작할 수 있게 된다.

    - 다형성을 적용하려면 타입은 공통되는 특성을 가지고 있어야 하며 코드는 그 특성에 기반하여 작성해야 한다.

    - 매개변수 다형성(parametric polymorphism)은 코드가 타입을 명시적/묵시적인 방식으로 매개변수로써 받       게 된다.

    - 서브타입 다형성에서는 코드가 특정 타입에 맞게 작성되지만, 프로그래머는 이를 확장하거나 세분화한 다른 타       입을 정의 할 수 있고, 해당 다형적 코드는 이들 서브타입에 대해도 잘 동작한다.

    - 명시적 매개변수 다형성은 또한 제너릭으로도 알려져 있다. 또한 C++에서는 템플릿으로도 알려져 있다.

    - 제너릭 함수를 사용하는 부분에서 해당하는 타입에 대한 코드가 생성되는 것을 묵시적 인스턴스화라고 한다.

 

 

*자동형전환과 중복정의

    - 의미적 차이 뿐 아니라 비용적으로도 둘은 차이가 크다. min함수의 정수 아규먼트 타입을 호출하는 것은 정수         아규먼트에 대해 실수 타입 버전을 호출하는 것보다 훨씬 효율적이다. 이 과정은 비교에서도 정수 연산을 쓰며 네       번의 자동형변환 연산을 피할 수 있다.

    - 언어에서 자동형전환에 반대하는 이유 중 하나가 이런 숨겨진 비용의 문제이다.

 

 

*코드의 범용성(genericity)

    - 오버로딩은 min연산을 사용하는 모든 타입에 대해 프로그래머가 여러 벌의 코드 사본을 직접 작성하게 한다.

    - 제너릭은 필요한 타입에 대한 코드 사본을 자동으로 생성한다.

    - 오버로딩은 생성된 결과 코드에 대해 호출하는 구문이 유사하기 때문에 애드혹(adhoc)다형성이라고 부르기도        한다.

    - 의미적 관점에서 오버로딩된 서브루틴은 여러 개의 코드에 한 이름을 쓰는 것이고 다형적 서브루틴은 하나의 이      름으로 하나의 대상 코드를 나타내는 것이다.

 

 

5. Target Machine Architecture

 

 

- 컴파일러는 번역기이다. 즉 한 언어로 작성된 프로그램을 다른 언어로 된 프로그램으로 번역하는 것이다.

- 프로그래밍 언어와 마찬가지로 기계어도 많이 있다. 각 기계어는 하나의 프로세서 아키택쳐어 대응한다.

- 올바른 코드를 생성하기 위해서 컴파일러 작성자는 단순히 타겟 아키텍쳐를 이해하면 되지만, 빠른 코드를 생성하기 위해는 구현까지도 이해해야 한다. 왜냐면 언어 구성요소의 여러 가지 번역 방법들이 속도의 측면에서 어떻게 다를지 결정하는 것이 구현이기 때문이다.

- RISC(reduced instruction set computer) 기계는 초당 실행 가능한 명령문 수를 늘리기 위해 명령문 집합의 다양성을 희생할 것이다.

- CISC(complex instruction set computer) 설계는 가장 흔한 데스크탑 프로세서에 쓰이고, RISC는 좀더 새로운 설계에서 많이 쓰인다.

 

 

*아키텍쳐

    - 아키택쳐는 이론적으로 하드웨어와 소프트웨어 간의 인터페이스이다.

 

 

*소프트웨어

    - 컴파일러에 의해 생성된 언어나 프로그래머가 기계를 위해 작성된 코드가 될 수 있다.

 

 

*프로세서 구현

    - 하드웨어 상에서의 아키텍쳐의 완전한 실현이다.

 

 

5.1. 메모리 계층구조

 

 

- 메모리는 대부분의 기계에서 연속된 8비트인 바이트로 나타내진다. 대부분 컴퓨터에서 메모리 계층구조를 도입하여 더 자주 쓰이는 것은 더 가까이 두는 방식을 도입했다.

- 전형적 메모리 계층구조는 접근 시간과 용량으로 비교할 수 있다.

- 메모리 계층구조의 세 가지 레벨은 레지스터, 메모리, 디바이스이다.

- 레지스터는 단일 클럭 사이클에 접근되어야 하며 디스크나 테이프의 시간은 물리적인 부품의 움직임에 의해 결정된다.

 

 

*컴파일러

    - 레지스터를 명시적으로 관리하고 필요할 때 메모리로부터 로드하며, 작업이 끝나거나 레지스터를 다른 데 써야        할 때 메모리로 다시 저장한다.

 

 

*캐시

    - 하드웨어에 의해 관리되고 메인 메모리보다 작으며 빠르다.

    - 주로 지역성에 적합하게 설계된다.

    - 지역성이란 대부분 컴퓨터 프로그램이 메모리의 같은 위치 또는 인근의 데이터를 반복적으로 접근하는 경향이       다.

    - 지역성에 따라 저장된 데이터를 캐시에 옮김으로써 계층적 메모리 시스템은 성능을 큰 폭으로 늘릴 수 있다.

    - 루프와 명령문은 연속된 위치에서 로드되고, 구조체의 한 요소를 접근한 코드는 다른 것을 접근할 가능성이 크다.

    - 1차 캐시는 보통 프로세서와 같은 칩에 있으며 쌍으로 되어 있어 하나는 명령문, 다른 것은 데이터를 위해 쓰인       다. 둘 다 한 사이클에만 접근 가능하다.

    - 2차 캐시는 더 크고 느리나 메인 메모리보다는 여전히 빠르다.

    - 고급 데스크탑과 서버급 기계는 칩 외부의 3차(3L)캐시도 가진다.

    - 캐시는 대부분 기계의 하드웨어에서 전적으로 관리되나 컴파일러가 높은 수준의 지역성을 가지는 코드를 생성       하여 효율성을 높일 수 있다.

    - 캐시에서 데이터를 가져오는 메모리 접근은 캐시 히트라고 하며, 캐시에 없는 데이터의 접근은 캐시 미스라고 한       다.

    - 미스의 경우 하드웨어는 자동으로 요청된 위치를 포함한 데이터의 연속 블럭을 다음 단계 캐시나 메인 메모리에       서 불러온다.

    - 캐시가 가득 찼다면 다른 데이터(라인)를 보내고 자리를 비운다. 라인이 수정된 경우 메모리에서 다시 write한다.

 

 

*디바이스

    - OS 시스템 호출에 의해서 접근된다.

 

 

*레지스터

    - 매우 빠르게 접근될 수 있는 소량의 데이터이다.

    - RISC기계는 정수와 소수점 수 연산을 위한 두 세트의 레지스터를 가진다. 이것은 프로그램 카운터와 프로세서        상태 레지스터이다. 

    - 한 사이클에 접근 가능하고, 메모리는 일반적으로 그렇지 않아서 좋은 컴파일러는 자주 쓰는 데이터를 레지스터       에 두려고 노력을 기울인다. 그런 과정을 통해 메모리에서 레지스터로 데이터를 주고받는 시간을 최소화한다.

 

 

*프로그램 카운터(PC)

    - 다음 수행될 명령문의 주소를 가지고 온다. 대부분의 경우 명령문을 페치할 때 자동으로 증가한다.

    - 분기는 PC값을 명시적으로 변경함으로써 가능해진다.

 

 

*프로세서 상태 레지스터

    - 운영체제에 중요한 여러 가지 비트를 가진다. 

    (privilege level, interrupt priority level, trap enable bits)

    - 어떤 기계에서는 가장 마지막에 수행된 수식 및 논리 연산의 결과값이 0, 음수, 오버플로우인지 여부를 저장하기       도 한다.

 

 

*데이터 맞춤(alignment)

    - 컴파일러에 중요한 메모리의 한 가지 특성이다.

    - 대부분 기계는 1, 2, 4, 8받이트의 여러 크기 연산자를 다룰 수 있다.

    - 최근의 대부분 아키텍처는 n0바이트 피연산자가 메모리에서 n으로 나누어지는 주소에 나타나기를 요구한다.

    - 피연산자에 따라 주소 할당을 나누는 이유는 두 가지가 있다.

    - 첫 번째로 버스가 데이터를 비트병렬로 맞추어진 전달 통로를 통해 프로세서에 전달하도록 설계되어 있다.

    - 정수를 홀수 주소에서 로드하려면 비트가 시프트되어야 하여 데이터를 로드하는데 추가 로직이 필요하다.

    - 두 번째로 RISC 기계는 명령문에 연산과 피연산자의 전체 주소를 둘 다 기술할 충분한 비트가 없다.

    - 레지스터의 어떤 기준 위치로부터의 오프셋으로 표시하는 것이 보통이다.

 

 

*프로세서 / 메모리 갭

    - 역사적으로 프로세서의 속도는 메모리 속도보다 훨씬 빨리 증가했다. 또 메모리를 접근하는 데 필요한 프로세서        사이클 수도 계속 증가했다. 따라서 캐시는 성능을 위해 점점 더 중요해졌다. 

    - 캐싱의 효율을 높이기 위해 프로그래머는 데이터 접근 패턴이 높은 지역성을 가지는 알고리즘을 선택한다.

    - 높은 수준의 컴파일러도 접근의 지역성을 고려해서 프로그램에 대한 번역을 선택한다.

 

 

목표 달성도 

    5.1까지 완료

 

 

목표에 대한 평가

    여전히 공부한 부분을 기록하는데 손이 느려 시간이 소요된다.

    기계적인 파트인 5단원에 들어가며 평소 유심히 보지 않았던 RISC나 CISC등을 다시 공부하는 시간을 가지고 있       다.

    기술적으로 성숙한 프로그래머가 되기 위해서는 5단원도 성실히 공부하여 내 것으로 만들 필요성이 있다는 판단       을 하였다.