범위 바이너리 옵션 타입

마지막 업데이트: 2022년 5월 16일 | 0개 댓글
  • 네이버 블로그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 트위터 공유하기
  • 카카오스토리 공유하기
만약 이들을 바이너리 대신 16진수 표기법으로 표현한다면 아래와 같은 모습이 됩니다.

범위 바이너리 옵션 타입

[Numeric - Data Type]

1. Integers (whole numbers) of Type I

- 범위 : -2^31 (-2147483648) ~ 2^31 - 1 (2147483647)

- Counter, Item 수, Index 등에 쓰임

2. Packed numbers of Type P

- Two decimal digit are packed into one byte에서 유래됨

- 가용 Size 1~ 16byte이며, 소수는 최대 14자리 설정 가능

- 프로그램 속성 Setting 시 Fixed point arithmetic을 체크해야 함. 그렇지 않을 경우, Type P는 Integer로 표현됨

- 정확히 계산을 요하는 Business Calculation 목적으로 사용됨

3. Floating point number of Type F

- 값의 범위는 1*10^-307 ~ 1*10^308

- 지수 형태로 표현되므로 FLTP_CHAR_CONVERSION과 같은 Function Module을 이용해 다른 데이터 타입으로 변환하여 출력함

- Type F는 내부적으로 Binary System으로 전환될 때 반올림 에러가 발생할 수 있음(Rough Calculation)

- 높은 정확도를 요구한다면 Type P를 사용해야 하며, 매우 작거나 큰 수라면 Type F 사용을 권장함

- Value 범위가 넓은 경우나, 반올림 오류가 중요한 사항이 아닐 경우 Type F를 사용함(실수 값에 대한 근삿값을 가지는 타입이기 때문에 주의)

[Numeric - Operation]

범위 바이너리 옵션 타입
기호 의미 사용 예 기호와 동일한 키워드
+ 더하기

= + .

ADD TO .
- 빼기

= - .

SUBTRACT FROM .
* 곱하기

= * .

MULTIPLY BY .
/ 나누기

= / .

DIVIDE BY .
DIV Integer 나누기

= DIV .

MOD Integer 나누기의 나머지

= MOD .

** 제곱

= ** .

[Numeric - Data Type 함수]

함수 내역 사용 예
ABS 절대값을 리턴한다. ABS(-100)은 100을 리턴
SIGN 부호에 대한 결과를 리턴한다. 마이너스 -1, 0 -> 0, 플러스 + 리턴
CEIL 올림 CEIL(1.3), CEIL(1.7)은 모두 2를 리턴
FLOOR 내림 FLOOR(1.3), FLOOR(1.7)은 모두 1을 리턴
TRUNC 소수점을 버리고 정수만 남긴다. TRUNC(1.3), TRUNC(1.7)은 모두 1을 리턴
FRAC 소수점 이하 자리만 남긴다. FRAC('2.9')는 0.9를 리턴한다.

[Floating-Point 함수]

Function Meaning
ACOS, ASIN, ATAN; COS, SIN, TAN 삼각함수
COSH, SINH, TANH 쌍곡선 함수
EXP 지수함수
LOG 자연로그 함수 with base e
LOG10 상용로그 함수 with base 10
SQRT 제곱근 함수

[Character Type]

C 문자, 숫자, 특수문자에 사용.
N 숫자를 C 타입으로 표현. Integer 형태를 문자 타입으로 보여줌.
D 날짜 타입을 표현.
T 시간 타입을 표현.

별도로 알면 좋은 것들

1. C 타입은 데이터 선언 시에 문자 길이를 명시적으로 선언해야 한다. 문자 길이를 지정하지 않거나 Data Type을 선언하지 않으면 기본적으로 Character 1 자리로 정의된다.

2. 문자 길이를 지정할 경우에는 LENGTH 옵션을 이용하여 선언할 수 있는데 C, N, X, P 타입에서 사용 가능하다.

3. MOVE 명령은 = 기호와 동일하게 데이터를 할당하는 명령어이다.

4. MOVE-CORRESPONDING 구문은 구조체나 헤어라인이 존재하는 Internal Table에 사용되는 구문이다.

5. Strlen 명령어는 문자의 길이를 반환하는 기능을 수행한다.

6. SY-DATUM은 시스템 변수로 시스템의 오늘 날짜를 저장하고 있다.

7. SY-UZEIT은 시스템 범위 바이너리 옵션 타입 변수로 현재 시간이 포함되어 있다.

# 시스템 변수 SY-DATUM과 SY-DATLO의 차이점

SY-DATUM과 SY-DATLO는 둘 다 시스템 일자를 저장하고 있는데 SY-DATLO 시스템 변수는 User Time Zone을 이용해서 현지의 현재 시간을 인식할 수 있다.

범위 바이너리 옵션 타입

grep은 입력으로 전달된 파일의 내용에서 특정 문자열을 찾고자할 때 사용하는 명령어입니다. 리눅스에서 가장 많이 사용되는 명령어 중 하나이죠.

하지만 grep 명령어가 문자열을 찾는 기능을 수행한다고 해서, 단순히 문자열이 일치하는지 여부만을 검사하는 것은 아닙니다. 문자열이 같은지(equal)만을 검사하는 수준을 넘어, 훨씬 복잡하고 다양한 방식으로, 그리고 매우 효율적으로 문자열을 찾는 기능을 제공하죠. 이는 grep이 파일의 문자열을 검색할 때, 단순 문자열 매칭이 아니라, 정규 표현식(Regular Expression)에 의한 패턴 매칭(Pattern Matching) 방식을 사용하기 범위 바이너리 옵션 타입 때문입니다.

1.1 정규 표현식(Regular Expression)

정규 표현식(Regular Expression)이란, 특정 규칙을 가진 문자열 집합을 표현하기 위한 형식 언어로써, 주로 문자열 패턴 매칭을 검사하거나 또는 문자열을 치환하기 위해 사용됩니다.

문자열 검색에 정규 표현식을 적용하게 되면, 지정된 문자열의 문자가 단순히 "같은지(equal)" 여부가 검사되는 것이 아니라, 정규 표현식의 규칙에 매칭(Matching)되는지 여부가 검사됩니다.범위 바이너리 옵션 타입

예를 들어, 단순 문자열 검색에서 '*'은 문자 그대로 '*'을 의미하기 때문에, ('*' == '*')은 성립하지만 ('A' == '*')는 성립하지 않습니다. 하지만 정규 표현식에서 '*'는 0개 이상의 모든 문자를 의미하므로, ('*' == '*') 뿐만 아니라 ('A' == '*')도 TRUE로 판단됩니다.

정규 표현식을 모두 설명하려면 지면이 한참 모자라니, 여기서는 정규 표현식을 작성할 때 사용되는 메타 문자(Meta Character)에 대해서만 간략히 정리하겠습니다.

메타 문자
(Meta Character)
설명
. 1개의 문자 매치 (정확히 1개의 문자와 매치)
* 앞 문자가 0회 이상 매치
앞 문자가 정확히 n회 매치
앞 문자가 n회 이상 m회 이하 매치
[ ] 대괄호에 포함된 문자 중 한개와 매치
[^ ] 대괄호 안에서 ^뒤에 있는 문자들을 제외
[ - ] 대괄호 안 문자 범위에 있는 문자들 매치
() 표현식을 그룹화
^ 문자열 라인의 처음
$ 문자열 라인의 마지막
? 앞 문자가 0 또는 1회 매치 (확장 정규 표현식)
+ 앞 문자가 1회 이상 매치 (확장 정규 표현식)
| 표현식 논리 OR (확장 정규 표현식)

2. grep 명령어 옵션.

grep 명령에서 사용할 수 있는 옵션은 아래와 같습니다. (grep 명령에 대한 더 자세한 옵션은 "grep --help" 명령을 통해 확인할 수 있습니다.)

3. grep 명령 사용 예제.

grep을 사용하여 파일로부터 문자열을 검색하는 방법은 아래와 같습니다.

아래는 "FILE.txt"의 내용에서 "PAT"라는 문자열을 검색하고, 문자열이 존재하는 라인을 출력하는 예제입니다. 기본적으로 대소문자를 구분한다는 점에 주의하세요.

자주 사용하는 grep 명령어 사용 예제는 아래와 같습니다. 각 항목의 링크를 선택하면, 좀 더 자세한 설명과 사용 예제를 확인할 수 있습니다.

grep 사용 예 명령어 옵션
대상 파일에서 문자열 검색 grep "STR" [FILE]
현재 디렉토리 모든 파일에서 문자열 검색 grep "STR" *
특정 확장자를 가진 모든 파일에서 문자열 검색 grep "STR" *.ext
대소문자 구분하지 않고 문자열 검색 grep -i "STR" [FILE]
매칭되는 PATTERN이 존재하지 않는 라인 선택 grep -v "STR" [FILE]
단어(Word) 단위로 문자열 검색 grep -w "STR" [FILE]
검색된 문자열이 포함된 라인 번호 출력 grep -n "STR" [FILE]
하위 디렉토리를 포함한 모든 파일에서 문자열 검색 grep -r "STR" *
최대 검색 결과 갯수 제한 grep -m 100 "STR" FILE
검색 결과 앞에 파일 이름 표시 grep -H "STR" *
문자열 A로 범위 바이너리 옵션 타입 시작하여 문자열 B로 끝나는 패턴 찾기 grep "A.*B" *
0-9 사이 숫자만 변경되는 패턴 찾기 grep "STR[0-9]" *
문자열 패턴 전체를 정규 표현식 메타 문자가 아닌
일반 문자로 검색하기
grep -F "*[]. " [FILE]
정규 표현식 메타 문자를 일반 문자로 검색하기 grep "\*" [FILE]
문자열 라인 처음 시작 패턴 검색하기 grep "^STR" 범위 바이너리 옵션 타입 [FILE]
문자열 라인 마지막 종료 패턴 검색하기 grep "$STR" [FILE]

3.1 대상 파일에서 문자열 검색.

grep 명령에 문자열과 파일 이름을 지정하여, 파일에서 문자열을 검색할 수 있습니다. 이 때 문자열 검색 결과는 문자열이 포함된 라인 단위로 출력됩니다.

3.2 현재 디렉토리 모든 파일에서 문자열 검색

파일 이름에 "*" 문자를 사용하여, 현재 디렉토리에 있는 모든 파일에서 문자열을 검색할 수 있습니다. 단, 현재 디렉토리에 포함된 하위 디렉토리에 있는 파일은 탐색하지 않습니다. (하위 디렉토리를 탐색하려면 -r 옵션 사용.)

3.3 특정 확장자를 가진 모든 파일에서 문자열 검색

파일 이름 확장자 앞에 "*" 문자를 사용하여, 특정 확장자를 가진 모든 파일에서 문자열을 검색할 수 있습니다.

3.4 대소문자 구분하지 않고 문자열 검색

grep 명령에 "-i" 범위 바이너리 옵션 타입 옵션을 사용하여, 대소문자 구분없이 문자열을 검색할 수 있습니다.

3.5 매칭되는 PATTERN이 존재하지 않는 라인 선택

어떤 경우에는, 문자열이 매칭되는 라인이 아닌, 매칭되는 패턴이 존재하지 않는 라인을 선택해야 하는 경우가 있습니다. 이 때, "-v" 옵션을 사용합니다.

DEVOCEAN

KIDO 21.11.08

Python argparse 이용하기

  • Python 프로그램을 실행할때 아규먼트를 전달하여, 프로그램을 아규먼트에 따라 조작해야할 때가 많다.
  • 아규먼트 처리를 위한 방법을 스터디 하면서 정리해보았다.
  • Python은 argparse 라는 모듈을 제공하여 전달한 아규먼트를 파싱할 수 있도록 하고 있다.

Argument 전달 단순 예제

  • 우선 가장 간단한 아규먼트를 입력받는 파이썬 프로그램을 살펴보자.
  • argparse 를 import 수행한다. 입력되는 아규먼트를 argparse 모듈을 통해서 파싱할 수 있다.
  • parsing_argument() 라는 메소드를 정의했다. 아규먼트 파싱을 수행하고 파싱된 아규먼트 결과를 반환한다.
    • parsing_argument() 내부 내용은 차츰 알아볼 것이다. 여기서는 프로그램 마다 실행되는 부분을 메소드화 했다는 것만 확인하자.
    • parsing_argument() 메소드를 호출하여 결과로 args를 받고 있다.
    • print(f"Title : ") 를 이용하여 입력받은 아규먼트를 출력한다.
    • f"Title : " f 로 시작하는 문자열은 포매팅을 수행할 수 있는 문자열이다. '<>'로 감싸고 bracelet 내부에 참조값을 입력하면 출력될때 치환된 문자열로 반환된다.
    • python app.py 로 실행하면 name 변수에 main 이라는 값이 세팅된다. 그때 main() 이 실행될 수 있다.
    • 반면 모듈로 동작하면 name 값이 main 이 아니므로 main 함수는 실행되지 않는다. (즉, 모듈인경우 main() 메소드가 실행되지 않는다.)

    테스트 수행하기

    • 아규먼트로 hello 를 전달하여 결과를 확인할 수 있다.

    프로그램 관련 도움말

    • 아규먼트가 없는경우 도움말이 노출된다.
    • 또한 help 옵션을 이용하면 프로그램을 수행하기 위한 정보를 확인할 수 있다.
    • 아규먼트가 없는경우 사용법과 아규먼트 추가 하라는 내용이 출력된다.
    • '-h' 옵션을 이용하여 상세 사용법을 확인할 수 있다.
    • 아규먼트가 없을때 어떻게 수행해야하는지, 전달되는 아규먼트는 무엇인지 설명하고 있다.

    아규먼트의 종류

    • 아규먼트는 크게 3가지로 나뉜다. 위치 아규먼트, 옵션 아규먼트, 플래그 아규먼트
    • 위치 아규먼트:
      • 위치 아규먼트는 일반적으로 python 의 형태로 argument에 들어가는 내용이다.
      • 이는 프로그램 실행시 반드시 필요하며, 정의된 순서에 따라서 아규먼트가 매핑이 되어야 한다.
      • 옵션 아규먼트는 프로그램에 필수는 아니지만 옵션으로 수행할 수 있는 아규먼트이다.
      • 일반적으로 python . 의 형태로 옵션으로 아규먼트를 전달한다.
      • 옵션 아규먼트는 디폴트 값을 지정해서 옵션값이 들어오지 않는경우 처리를 프로그램 내에서 수행해 주어야한다.
      • 플래그 아규먼트는 boolean 으로 표현되며, 해당 아규먼트가 있으면 True, 없으면 False 가 된다.

      위치 아규먼트 알아보기

      • 위치 아규먼트는 이미 보았던 Super Simple Argument 샘플과 동일하며, 사용 방법에 따라 설정을 변경하면된다.

      기본 위치 아규먼트

      • 위치 아규먼트는 등록된 순서로 매핑이 된다.
      • 위치 아규먼트를 등록할때 순서는 매우 중요하며, 순서에 해당하는 아규먼트 타입이 매우 중요하다.
      • 위와 같이 첫번째 순서는 pos_str_01, pos_int_02 순서로 들어와햐 한다.
      • 그리고 순서대로 타입이 정확히 일치 해야한다.

      테스트 1)

      • 첫번째 아규먼트는 문자, 두번째는 숫자 로 입력한 결과가 출력 되었다.

      테스트 2)

      • 아규먼트 순서가 바뀌면 어떻게 되는지 확인해보자.
      • 보는 바와 같이 아규먼트 순서가 잘못되면 오류가 발생한다.

      복수개의 동일 아규먼트 받기

      • 위치 아규먼트로 전달되는 아규먼트를 복수개 받고 싶은 경우가 있다.
      • 이럴때 nargs 값을 조절하여 아규먼트의 개수를 조절할 수 있다.
      • 보는바와 같이 이전과 동일하지만 nargs='+' 가 추가 되었다.
      • nargs 에 들어갈 수 있는 형태는 다음과 같다.
        • '*': 0개 이상의 아규먼트
        • '?': 0, 1개의 아규먼트
        • '+': 1개 이상의 아규먼트

        결과 보기

        • 문자열은 배열로 입력을 받는다.
        • 그리고 마지막 아규먼트는 숫자를 받도록 하고 있다.
        • 마지막 숫자 1개를 제외하고 모두 문자열 아규먼트로 받았다.

        위치 아규먼트 WrapUp

        • 위치 아규먼트는 입력되는 아규먼트의 순서가 중요하다.
        • 또한 아규먼트 타입의 순서역시 매우 중요하며, 올바르게 입력되지 않은경우 오류를 표시한다.
        • 그리고 nargs 라는 아규먼트 개수 지정자를 이용하여 아규먼트 개수를 지정할 수 있다. 그리고 이 지정자에 들어올 값으로 정규표현식 '*', '?', '+'가 올 수 있음을 확인할 수 있다.

        옵션 아규먼트

        • 옵션 아규먼트는 일반형 (--옵션이름), 축약형 (-옵션축약단어) 를 이용하여 아규먼트를 입력 받을 수 있다.
        • 옵션은 선택사항이므로, 없는경우 디폴트 값을 설정할 수 있다.

        기본 옵션 아규먼트 테스트

        • 기본 옵션 테스트를 위와 같이 작성하였다.
        • 코드 구성은 parsing_argument(), main() 과 같이 동일하며 아규먼트 설정에서 옵션을 받을 수 있도록 수정되었다.

        '-t', '--title' 으로 단축형, 기본형 타입으로 옵션을 지정했다.

        metavar 를 이용하여 도움말에 표시할 타입을 지정했다.

        type은 전달된 아규먼트를 문자열 타입으로 받겠다는 의미이다.

        help는 도움말에 표시될 설명 문자열이다.

        default 값으로 옵션이 지정되지 않은경우 기본적으로 세팅될 값을 나타낸다.

        참고: 아규먼트를 조회할때에는 옵션 기본형 이름으로 접근할 수 있다. 즉, args.title 을 통해서 아규먼트 값을 조회할 수 있다.

        • 숫자 아규먼트 역시 문자와 다를것은 없다. 모두 동일하게 지정할 수 있으며, 기본값으로 0을 설정했다.
        • 이제 아규먼트 없이 어떻게 출력되는지 확인해보자.
        • 보는바와 같이 아규먼트가 없다면 기본값 default 의 값으로 대체된다.
        • 만약 기본값이 없다면 어떻게 될까?
        • 위와 같이 default가 없다면 결과는 다음과 같다.
        • 보는바와 같이 None 으로 값이 채워져 있음을 알 수 있다. 가능하면 기본값을 설정해주자.

        정상 케이스 테스트하기

        • 이제 정상적으로 옵션값을 세팅해보자.

        위와 같이 정상적으로 아규먼트가 잘 출력됨을 알 수 있다.

        문자열에 공백이 있다면 "" 으로 묶어주자. 그렇지 않으면 아규먼트를 찾지 못한다는 오류를 볼 수 있다.

        파일 옵션 아규먼트

        • 옵션 아규먼트에는 파일을 받을 수도 있다.
        • 일반적으로 파일 옵션 아규먼트는 -f, --file 을 이용하여 파일값을 받아 들이게 된다.
        • 파일 아규먼트 코드 형식은 이전과 동일하다.
        • 달라진 부분은 타입 부분에서 파일 타입을 쓰는 것이다.
        • 옵션으로 '-f', '--file' 을 지정했다.
        • metavar는 도움말에 보여줄 형식으로 파일 타입을 의미한다.
        • type 부분은 FileType('rt') 로 지정하여 읽기 텍스트의 의미이다.
          • r: 읽기모드
          • w: 쓰기모드
          • t: 텍스트 모드
          • b: 바이너리 모드
          • 테스트를 위해서 sample_text.txt 를 작성하자. 내부에는 아무 문자든지 여러 라인을 입력한다.
          • 그리고 다음 명령어로 파일 인수를 전달하자.
          • sample_text.txt 파일을 읽어서 화면에 출력하고 있다.

          오류 테스트

          • 아규먼트가 없다면 어떻게 될까?
          • 즉 우리는 None 으로 했기 때문에 파일을 읽을 수 없다. 그러므로 파일 내용을 읽을때 범위 바이너리 옵션 타입 항상 검사를 해 주어야한다.
          • 간단하게 파일 아규먼트가 None 인지만 검사했고 None인경우 이후 구문을 실행하지 않고 스킵한다.

          플래그 아규먼트

          • 플래그 아규먼트는 True/False 를 설정하는 아규먼트이다.
          • 플래그는 특정 행동을 수행할지 여부를 지정하기 위해서 주로 사용하며 샘플은 다음과 같다.
          • 위와 같이 플래그 아규먼트는 아규먼트 옵션에 따라서 True/False 를 설정한다.
          • action 은 아규먼트가 들어온경우 어떠한 값을 지정할지 행동을 지정한다.
            • store_true: 플래그 아규먼트가 세팅되면 True를 저장한다.
            • store_false: 플래그 아규먼트가 세팅되면 False를 저장한다.

            테스트하기

            • '-o' 옵션을 지정하면 True로 설정되었다.
            • 위와 같이 옵션이 업다면 False가 되었다.
            • 만약 action='store_false' 라고 했다면 위 예제와 반대로 처리 되었을 것이다.

            choices 로 지정된 아규먼트만 받기

            • 지금까지는 아규먼트 값을 특정 타입에 따라서 범위 바이너리 옵션 타입 입력 받을 수 있게 했다.
            • 그러나 특정 값의 범위만을 입력해야하는 경우 choices 를 이용할 수 있다.
            • 코드 형식은 동일하다.
            • 학생의 이름, 나이, 등급을 입력 받으며, 나이는 1 ~ 99세까지, 등급은 A ~ F 중 하나의 값이 들어올 수 있다.
            • 정상적인 값의 범위가 들어갔을때 위와 같이 원하는 결과를 얻을 수 있다.
            • 나이를 100으로 입력하면, 해당 범위에 속하지 못하므로 오류를 내고, 입력값의 범위를 지정하고 있다.
            • 역시 grade도 지정된 범위의 값이 아니면 오류를 내보낸다.

            정확한 아규먼트 개수범위 지정하기(커스텀 액션)

            • nargs='*', '+' 를 이용하면 아규먼트를 복수개 받을 수 있다고 했다.
            • nargs=2 를 이용하면 2개의 아규먼트만을 받게 된다.
            • 특정 아규먼트를 일일이 나열하지 않고 지정된 개수 범위 (2개 ~ 4개)를 받고자 한다면 어떻게 해야할까?
            • 이때 action을 이용하여 아규먼트 개수를 한번더 검증해 주는 코드를 이용하여, 아규먼트 개수를 정확히 제어할 수 있게 된다.
            • 자세한 내용은 Manuals 에서 확인하자.
            • 추가된 부분은 action=argument_count_range 를 추가했다.
            • 그리고 def argument_count_range 메소드를 구현했다.
            • nargs='+' 를 추가하여 1개 이상의 아규먼트를 받는것을 범위 바이너리 옵션 타입 알 수 있다.
            • action=argument_count_range(2, 4) 메소드를 이용하여 들어온 아규먼트를 테스트한다.
              • 아규먼트 액션은 RequiredLength 라는 클래스를 생성하고, call 함수를 구현하여 action 에서 해당 함수를 호출하도록 작성하였다.
              • 코드 내부는 아규먼트의 개수를 비교해서 범위내에 속하는지 확인하고, 속하지 않으면 ArgumentTypeError를 발생하도록 했다.
              • 그렇지 않은경우에는 setattr 을 통해서 아규먼트를 설정하고, 결과를 반환하도록 했다.
              • 아규먼트가 1개일때 우리가 지정한 아규먼트 개수 체크를 통과하지 못하고 ArgumentTypeError 를 반환하고 있다.
              • 정확히 범위내에 속하면 정상적으로 결과가 출력된다.

              WrapUp

              • 지금까지 Python 에서 제공하는 아규먼트 지정 방법에 대해서 몇가지 알아 보았다.
              • 이것보다 더 많은 내용이 있지만, 위 내용만으로도 프로그램에 필요한 아규먼트를 지정하고, 검증할 수 있다.
              • 좋은 프로그램은 친절한 피드백을 주어야하고, 이러한 피드백이 일정한 형식을 지니는 것이 중요하다고 생각한다.
              • argparse는 이러한 조건을 잘 만족시켜주는 모듈인것 같다.~

              KIDO 님의 최신 블로그

              관련 블로그

              python datetime 활용하기

              Python datetime 활용하기 날짜 처리는 프로그래밍에서 빠져서는 안되는 매우 중요한 요소이다. 날짜 정보를 포매팅하고, 원하는 날짜로 이동하는 등의 날짜 연산을 처리해 보자. 날짜 처리 패키지 모듈 알아보기 날짜처리나 패키지 모듈에 대해서 알아 보자. 임포트를 위해서는 2가지 방법을 이용할 수 있다. import inspect import datetime from datetime import datetime as dt print(inspect.getfile(datetime)) print(inspect.getfile(dt)) 결과 확인하기 /usr/local/Cellar/[email protected]/3.9.7/Frameworks/Python.framework/Ver.

              Python Package 생성 및 배포하기

              Python 패키지 생성하고 PyPI에 등록하기 Python을 이용하면서 pip install 명령어를 통해서 원하는 패키지를 등록하고, 프로그래밍을 하는게 보통이다. 그렇다면 Python 패키지는 어떻게 만들고, pip install 을 이용하여 파이썬 패키지를 설치하는 것은 어떻게 만들까? 이번 아티클에서는 패키지를 생성하고, 빌드, 긜고 PyPI 에 업로드 하는 방법에 대해서 알아볼 것이다. 진행과정 PyPI 에 접근하여 계정 등록을 한다. 프로젝트 구조 생성하기. 모듈 작성하기 패키지 파일 작성하기 pyproject.toml 파일 생성 meatdata 작성하기 README.md 파일 작성하기 LICENSE 파일 작성하기 빌드하기 빌드모듈 설치 빌드 패키지 .

              Amazon SageMaker로 ML 모델 서빙하기

              1. 배경 1) Machine Learning 모델 서빙 & Serverless ICT Infra센터의 업무 중, 현장 작업 내역을 수기로 작성해서 핸드폰으로 사진을 찍은 후 Tango GIS라는 시스템에 업로드 하는 절차가 있습니다. 기존에는 사람이 해당 이미지를 보고 다시 시스템에 입력하는 방식이었는데, 이 부분을 OCR을 이용해서 자동화하는 과제를 수행했습니다. 저희 조직에서 수행하는 RPA(Robot Process Automation)는 주로 Mydesk의 자원을 활용하는데, Mydesk에서는 이용 가능한 GPU 자원이 없어서 서버가 필요했고, 서버 운영 리소스를 절감하고자 Amazon Sagemaker와 API Gateway, Lambda를 이용해서 se.

              범위 바이너리 옵션 타입

              Nate Cook

              2016년 3월에 진행한 try! Swift Tokyo 행사의 강연입니다. 영상 녹화와 제작, 정리 글은 Realm에서 제공하며, 주최 측의 허가 하에 이곳에서 공유합니다.

              Swift는 강력한 타입 시스템, value semantics, 자동 메모리 관리 등을 통해 안전성(safety)을 제공하면서도 뛰어난 성능을 보입니다. 또한, Swift는 메모리를 직접 할당하고 조작할 수 있는 도구도 제공하고 있습니다. 이 강연에서는 Swift에서 사용하는 포인터에 관해 다루고 있으며, typed 포인터, raw 포인터, buffer 포인터, 암묵적 브리징(bridging) 및 캐스팅(casting), 안전한 unsafe API 사용법에 대해 살펴볼 것입니다.

              저는 오늘 Swift 표준 라이브러리에서 제공하는 unsafe 계열의 포인터 타입에 관해 이야기하고자 합니다. 강연에서는 Swift가 제공하는 여러 포인터 타입들의 동작 방식과 탄생 배경에 대해 살펴보고, 이들을 안전하게 사용하는 방법에 대해 알아보도록 하겠습니다.

              Swift의 안정성(Safety)

              unsafe 계열의 포인터 타입은 무엇이고, 무엇이 이들을 unsafe 하게 만들었는지에 대해 알아보기 전에, Swift의 안전성(safe)에 대해 우선 이야기해보겠습니다. Swift는 안전성을 최우선으로 하는 언어로 알려져 있는데 Swift에서 말하는 안전성이란 무엇일까요? Swift가 처음 소개되었을 때 Swift는 안전하다 라는 말은 정말 멋지게 들렸습니다. 앞으로는 크래쉬가 절대 발생하지 않는 프로그램을 짤 수 있게 되었으니 정말 엄청난 일이라고 생각했습니다.

              Swift는 다양한 방법으로 안정성을 제공하는데 Optional 이 대표적인 예입니다. Optional 덕분에 null 포인터 이슈를 굉장히 쉽게 처리할 수 있게 되었지요.

              ages 배열을 예로 들어볼까요? 이 배열에 관한 계산식을 작성하려 할 때, Swift의 안전성이 어떻게 동작하는지 살펴보겠습니다.

              Optional 이 에러를 방지하는 역할을 하고 있네요. Array 타입의 first 속성은 Optional 타입이기 때문에 1 을 추가할 때 컴파일러가 미리 문제점을 파악해서 알려주고 있습니다.

              어떻게 오류를 없앨 수 있을까요? 간단하게 언래핑 optional만 만들어주면 됩니다.

              위의 코드에서 문제점을 발견할 수 있나요? 아무 문제가 없어 보이네요. 만약 빈 배열인 경우 어떤 일이 생길까요? 크래쉬가 발생합니다.

              optional 바인딩이나 nil-coalescing 연산자를 이용해 안전하게 optional을 언래핑할 수 있는 여러 가지 방법이 있기 때문에 이것은 제 실수라고 할 수 있습니다. 크래쉬되지 않고도 값의 존재여부를 처리할 수 있으며, force unwrapping 연산자를 사용해 쉽게 크래쉬 상태를 만들 수도 있습니다. 왜 이러한 동작 방식이 Swift 언어의 한 부분이 된 것일까요?

              이번에는 배열에 있는 값들의 평균을 계산하는 코드를 작성해볼까요? for 루프 대신 reduce 함수를 이용해 함수적인(functional) 방식으로 평균을 계산해보도록 하겠습니다.

              optional에 대해 걱정할 필요 없는 깔끔하고 가독성 높은 Swift 코드네요.

              이번에는 빈 배열을 0 으로 나눴을 때 어떻게 되는지 볼까요? 역시, Swift에서도 크래쉬가 발생하네요.

              마지막으로 age 배열에서 마지막 요소에 접근하기 위해 ages[4] 를 사용했습니다. 하지만 배열 인덱스는 0 부터 시작한다는 사실을 깜빡했네요. 어떤 결과가 나올까요? “off by one” 에러가 발생했네요.

              이곳에서 무슨 일이 일어난 걸까요? Swift의 규칙에 따라 문제가 전혀 없어 보이는 코드를 작성했는데 프로그램은 크래쉬되었네요.

              이 언어를 안전하다고 말할 수 있을까요? 안전하다고 알려진 프로그래밍 언어가 이렇게 쉽게 크래쉬될 수 있는 거죠? Swift에서 이야기하는 안전성이 크래쉬로부터의 안전성이 아니라면 무엇으로부터 안전하다는 뜻일까요?

              이런 개발 뉴스를 더 만나보세요

              다시 좀 전의 예제로 돌아가 보죠. 네 번째 인덱스에 해당하는 요소가 없기 때문에 존재하지 않는 요소에 대해 접근을 시도한 예제 코드는 크래쉬되었습니다. Swift는 무엇으로부터 우리를 안전하게 지킨 걸까요? 크래쉬보다 더 나쁜 상황이 무엇일까요?

              프로그램이 크래쉬되지 않고 계속 실행된다면 last 변수에 어떤 데이터가 있는지 누가 알까요?

              시뮬레이터에서는 정상적으로 동작하더라도 실제 기기에서는 문제가 발생할 수도 있고 디버그 모드에서는 컴파일되었다가 릴리즈 모드에서는 컴파일이 실패할 수 있습니다. 또한, 문제가 바로 나타나지 않고 나중에 나타난다면 디버그하기 힘들고 문제 원인을 찾기도 어려워집니다.

              예상을 벗어난 동작(Unexpected Behavior)

              Swift는 예상을 벗어난 동작으로부터 우리를 지켜줍니다. Swift는 타입 시스템, value semantic, collection 및 numberic 타입의 바운더리 검사, 자동 메모리 관리 등을 통해 안전성을 제공합니다.

              하지만, 성능이나 보다 세밀한 제어를 위해 Swift가 제공하는 안전성을 사용하지 않는 경우도 있는데 이때 unsafe 계열의 API가 사용됩니다.

              Swift Unsafe APIs

              Swift가 제공하는 안전 영역에서 벗어났다는 것을 명확히 알려주기 위해 Swift는 각각의 포인터 타입에 Unsafe 로 시작하는 이름을 붙였습니다. Swift의 unsafe API를 사용한다는 것은 Swift가 기본적으로 제공하는 안전성을 포기한다는 것을 의미하며, 개발자는 직접 안전성을 제공해야 합니다. 포인터를 이용하면 메모리에 직접 읽고 쓸 수 있게 됩니다.

              Swift 언어의 메모리 레이아웃과 포인터

              Swift의 메모리, 메모리 레이아웃, 포인터 동작 방식을 Swift 코드와 함께 설명하겠습니다.

              우선, 메모리부터 살펴볼까요? Swift로 작성한 프로그램을 실행하면 우리가 작성한 타입과 함수들이 컴퓨터 메모리를 차지하게 됩니다. 변수나 상수를 만들 때마다 바이너리 형태로 저장된 값이 메모리에 존재하게 됩니다.

              01_binary_to_byte

              바이너리는 0과 1로 구성되어 있죠. 컴퓨터 메모리는 이들 바이너리로 가득 차 있습니다. 보통 바이너리는 아래와 같이 8개로 묶어 바이트 단위로 나타냅니다.

              02_byte_to_hexa

              만약 이들을 바이너리 대신 16진수 표기법으로 표현한다면 아래와 같은 모습이 됩니다.

              03_compact_hexa

              이들을 좀 더 압축된 형태로 표현하면 이런 모습이 됩니다.

              각각의 행은 8 바이트(64비트)이며 요즘 모든 기기에서 사용하는 64비트 프로세서에서는 word 라는 이름의 단위로 표현할 수 있습니다.

              메모리의 모든 위치에는 주소가 있기 때문에 이를 이용해 값을 저장하거나 불러올 수 있습니다. 아래 그림에서 주소는 왼쪽에 표기했습니다.

              04_address_in_memory

              주소에도 16진수 표기법을 사용했습니다. 그림을 자세히 살펴보면, 각각의 행에 대한 주소는 다음 행과 8 만큼 차이가 난다는 사실을 알 수 있습니다. 각각의 행이 8 바이트라고 했던 것을 기억하시나요? 이 때문에 메모리 주소도 바이트를 기준으로 표기했습니다. 참고로 메모리 주소와 관련해서는 0과 1이 아닌 바이트가 가장 작은 단위입니다.

              프로그램에서 작성한 값은 항상 메모리에 저장됩니다.

              05_storing_value

              위의 예제코드에서 age 는 Int 이며, Int 는 word 크기의 signed integer입니다. 64비트 기기라면, 이 값은 word의 64 비트 모두를 사용해 저장됩니다. 위의 그림에서 age 변수는 메모리에서 한 행 전체를 사용한 것을 알 수 있습니다. Swift의 withUnsafePointer 함수를 사용하면 값 대신 값에 대한 포인터에 일시적으로 접근할 수 있게 됩니다.

              예제에서는 withUnsafePointer(to:_:) 에 &age 를 인자로 전달했고, 함수 내부에서 실행되는 trailing closure에서 포인터 인자인 agePointer 를 얻을 수 있습니다. 이 포인터는 age 변수의 값이 아닌 age 변수의 주소를 나타냅니다.

              06_access_address

              agePointer 는 age 변수의 주소를 값으로 가지고 있습니다. agePointer 는 값이 아니라 메모리에서의 위치를 나타냅니다. 만약 포인터가 가리키고 있는 값에 접근하고 싶을 때는 포인터의 pointee 속성을 사용하면 됩니다.

              정리하자면, 포인터란 메모리에서 값의 위치에 접근하거나 변경하는 방법입니다.

              Unsafe 포인터 타입

              Swift 표준 라이브러리에는 8가지 unsafe 포인터 타입이 있습니다. 이것은 다시 4개의 포인터와 4개의 buffer 포인터로 나눌 수 있습니다.

              우선 4개의 포인터 타입에 대해 살펴보죠. 이들은 UnsafePointer , UnsafeRawPointer , UnsafeMutablePointer , UnsafeMutableRawPointer 이며 공통으로 메모리 주소에 대한 래퍼(wrapper) 기능을 제공하며 내부에서는 unsigned integer로 표현됩니다. 왜 4종류의 다른 타입이 존재할까요?

              이들은 바운더리 검사, 타입 안전성, 메모리 관리 등을 하지 않는 unsafe API지만, Swift는 여전히 당신을 도울 준비가 되어있습니다. 그래서 타입 시스템을 적용한 unsafe API가 있는 것이고, 이러한 관점에서 다시 4개의 타입을 두 개의 그룹으로 나눌 수 있습니다.

              Typed 포인터와 Raw 포인터

              unsafe API는 “typed 포인터”와 “raw 포인터” 그룹으로 나눌 수 있습니다. UnsafePointer , UnsafeMutablePointer 가 typed 포인터에 해당합니다. 이들 포인터가 가리키는 메모리 주소에는 특정 타입의 값이 저장되어 있습니다. 예를 들면, Int 타입이 저장된 값에 대한 포인터가 있다면, 포인터의 pointee 에 접근했을 때 Int 타입의 값을 얻게 됩니다. Swift는 같은 메모리에 서로 다른 타입들이 함께 접근하는 것을 제한하고 있습니다. 따라서, signed integer와 다른 타입이 같은 메모리 주소를 사용하려고 시도할 때 어떻게 동작해야 하는지에 대해서는 정의되어 있지 않습니다(undefined behavior). typed 포인터는 메모리를 일시적 또는 영구적으로 다른 타입과 연결해주는 메서드를 제공하여 이 문제를 해결합니다.

              typed 포인터는 그것이 가리키고 있는 타입의 크기와 alignment에 대한 정보를 알고 있기 때문에 typed 포인터를 사용할 때는 stride나 alignment에 대해 고민하지 않아도 됩니다. 앞선 예제에서 age 변수에 대한 포인터로 UnsafePointer 가 사용되었는데 typed 포인터이기 때문에 age 변숫값에 대한 위치(location)와 타입 정보를 알고 있습니다.

              또한, 연속된 요소들로 구성된 배열에 접근하는 경우 typed 포인터는 요소 하나에 하나의 인스턴스를 매칭시키기 때문에 실수로 인스턴스의 중간 부분에 매칭되는 경우는 발생하지 않습니다.

              반면 raw 포인터의 경우 저장된 값이 어떤 타입인지에 대한 정보가 없습니다.

              raw 바이트를 사용하거나 메모리에서 로드되길 원하는 데이터 타입을 직접 명시하는 방법을 이용해 raw 포인터가 가리키는 데이터에 접근할 수 있습니다. age 에 대해 typed 포인터 대신 raw 포인터를 사용한다면 이전 주소와 같은 주소를 참조하였더라도 해당 주소에 저장된 값에 대한 타입 정보는 알 수 없습니다. 즉, 해당 주소에 64비트 signed integer가 저장되어 있는지 알지 못합니다. raw 포인터는 주소 그 자체입니다. 바이트를 이용해 해당 주소에 접근할 수도 있고 저장된 값의 타입에 상관없이 그 값을 불러올 수 있으며, 메모리 바인딩을 통해 raw 포인터를 typed 포인터로 변환할 수도 있습니다.

              Mutable 포인터와 Immutable 포인터

              unsafe API를 mutability를 기준으로 분류할 수도 있습니다. 포인터는 어떤 값을 참조하기 위한 것이므로 포인터를 let 으로 선언하더라도 포인터가 참조하는 메모리의 값을 변경할 수 있습니다. Swift는 인스턴스 레벨에서 mutability를 제어하는 대신, 타입 시스템 레벨에서 mutable 포인터에 의해 참조되는 값을 변경할 수 있는 기능을 제공하여 mutability를 처리하고 있습니다.

              참조하는 메모리에 대해 읽기만 가능한 immutable typed 포인터, raw 포인터와 함께 이들에 대한 mutable 버전도 있는 것이죠. Swift는 범위 바이너리 옵션 타입 mutable 포인터에 대한 초기화, 비초기화(de-initialization), mutable 포인터 할당 등의 기능을 제공하고 있습니다.

              buffer 포인터

              이제 buffer 포인터에 대해 알아볼까요? buffer 포인터는 항상 count가 따라다닙니다. 특정한 하나의 주소에 저장하는 대신, buffer 포인터는 메모리의 범위를 지정합니다. buffer 포인터는 typed, raw, mutable, immutable 포인터 형태가 있습니다.

              buffer 포인터는 collection처럼 동작할 수 있습니다. 특정 메모리 영역에 저장된 내용을 이터레이트(iterate)할 수 있으며, Array에서 사용했던 대부분의 연산자를 사용할 수 있습니다.

              예제 코드에서는 withUnsafeBytes 함수에 age 를 전달했으며, 이 함수는 인자로 넘겨준 age 변수의 바이트를 참조하는 UnsafeRawBuffer 포인터 타입의 closure를 내부에서 호출합니다.

              buffer를 일반적인 collection처럼 사용할 수 있습니다. count도 이용했고, first 속성을 이용해 첫 번째 값에 접근했으며, subscript도 사용했습니다.

              이들이 메모리에 저장된 값의 raw 바이트라는 사실을 잊지 말아야 합니다. Int 가 8 바이트 크기이기 때문에 8이라는 count 값을 얻게 된 것이고, age 값의 첫 번째 바이트를 읽었을 때 5 라는 결과가 나온 것입니다.

              만약 age 의 값이 1바이트만으로 표현할 수 없을 때는 어떤 결과가 나올까요? 2,000살인 간달프의 나이를 다룬다고 가정했을 때 age 변수의 메모리에 대한 첫 번째 byte는 16진수 값으로는 d0 이고 정수형으로는 208이 될 것입니다. 다시 한번 강조하지만, 우리는 raw 포인터를 사용하고 있기 때문에 인스턴스 레벨이 아니라 바이트 레벨에서 이 값을 읽어야 합니다.

              C API 임포트

              unsafe 포인터 타입이 필요한 때는 언제일까요? 대부분의 개발자에게 이들이 필요할 때는 크게 두 가지 경우일 것입니다. unsafe 포인터 타입은 typed 포인터나 Void 포인터가 사용된 대부분의 C API와의 호환성을 제공합니다. 따라서 C API와 호환이 필요한 경우 unsafe API를 사용할 수 있습니다. 다른 하나는 unsafe 포인터 타입으로만 최적화 작업이 가능한 경우입니다. 예제를 통해 좀 더 자세히 살펴보도록 하죠.

              예제코드는 macOS 10 SearchKit 프레임워크가 제공하는 SKSearchFindMatches 함수 시그니처를 간략하게 보여주고 있습니다. 약간 복잡해 보이지만 in, out 인자를 가진 일반적인 C 함수 형태라고 보시면 됩니다. 세 가지 인자가 있는데 한 개는 함수에 대한 input이며 나머지 두 개는 output입니다.

              이 함수는 검색이 시작되면 반복적으로 호출됩니다. 매번 호출될 때마다 out 인자값에는 검색결과가 저장되며 검색이 완료되면 false 를 반환합니다. inMaximumCount 는 최대 검색결과 수를 의미하며, 첫 번째 out 인자값인 C 배열에 저장되는 documentID의 최대 개수를 설정하는 값이기도 합니다. 두 번째 out 인자는 반환되는 실제 검색결과 범위 바이너리 옵션 타입 범위 바이너리 옵션 타입 범위 바이너리 옵션 타입 수를 의미합니다.

              두 개의 out 인자들은 모두 typed mutable 포인터 타입이지만 pointee 타입은 다르네요. 흥미로운 사실은 이 함수를 호출할 때 unsafe 포인터를 직접 다룰 필요가 없다는 점입니다.

              Swift는 &documentIDs 와 같은 형태로 inout 문법을 사용하면 변수 또는 배열을 typed 포인터나 raw 포인터로 암묵적(implicit)으로 변환할 수 있습니다.

              documentIDs 배열과 foundCount 변수를 생성하고, inout 문법을 통해 SKSearchFindMatches 함수에 대해 인자로 전달했습니다. &documentIDs 는 documentIDs 배열의 요소들을 가리키는 포인터로 변환하는 것이고, &foundCount 는 foundCount 변수에 대한 포인터로 변환하는 것입니다.

              하지만, 암묵적 변환에는 제약사항이 있다는 것을 잊지 말아야 합니다. 암묵적 포인터 변환을 사용해 documentIDs 배열을 인자로 전달하면, 배열의 첫 번째 요소에 해당하는 포인터만 전달되는 것입니다. 이 포인터를 전달받은 함수는 배열의 크기에 대한 정보를 알지 못하고, 배열의 count 도 변경할 수 있는 능력도 없습니다. 때문에 inMaximumCount 만큼의 요소들을 저장할 수 있는 충분한 공간을 가지고 있는 배열을 넘겨주는 것이 중요합니다.

              또한, 암묵적인 변환으로 생성된 포인터는 함수가 호출된 시점에만 유효(valid)합니다. 따라서, 함수 실행 이후에 이들 포인터를 이용하는 것은 예상치 못한 결과를 가져올 수 있습니다.

              documentIDs 를 얻고 난 후 배열에 대한 루프를 수행할 수 있으며 각각의 검색결과를 처리할 수 있는 핸들러를 호출할 수 있습니다.

              예제 코드는 예상한 대로 잘 동작하고, 테스트도 무난히 통과할 테지만, 성능 테스트를 해보면 unsafe 포인터를 명시적으로 사용하여 이 코드 성능을 좀 더 향상할 수 있다는 것을 깨닫게 될 것입니다.

              약간의 속도 향상을 위해 Swift 언어가 제공하는 안전성을 포기해야 하는 상황에 직면했네요. 이러한 최적화가 정말로 필요하고 도움이 된다는 확신이 들 때만 최적화를 진행하길 바랍니다.

              Swift가 제공하는 안전성을 개발자가 직접 제공하기 위해서 예제 코드를 약간 수정해야 합니다. 우선, documentIDs 를 선언한 부분을 살펴보죠. 배열 초기화는 Array(repeating:count:) 를 이용해 documentIDs 를 위한 100개의 공간을 확보하고, 각각의 공간을 0으로 초기화했습니다.

              일반적으로는 이러한 초기화 방법은 괜찮다고 할 수 있습니다. Array 타입은 초기화가 완료되기 전까지는 요소에 접근할 수 없게 함으로써 안전성을 제공합니다. 하지만 우리 예제의 경우 배열 요소들에 접근하는 코드가 나타나기 전에 SKSearchFindMatches 함수에 배열이 전달되는 코드가 먼저 발생하며, 이 함수에서는 배열에 검색결과를 저장하는 작업을 수행할 것입니다. 때문에, 초기화 과정이 꼭 필요하지는 않습니다.

              다른 접근법을 고려해보죠. documentIDs 를 빈 배열로 선언한 다음, 충분한 공간을 확보하면 어떨까요?

              괜찮은 방법처럼 보이지만, 아쉽게도 이 방법은 좋은 해결책이 아닙니다. 함수에 documentIDs 를 전달했을 때 한 개의 포인터만 얻을 수 있었던 점을 기억하시나요? 배열에 대한 count 정보를 알 수도 없고 count를 변경할 수도 없습니다. 그 때문에 이 방법으로는 배열에 들어있는 요소들에 접근할 방법이 없습니다.

              추가적인 작업을 할 곳이 한 군데 더 있는데 documentIDs 의 요소 i 에 접근할 때입니다.

              배열의 subscript를 사용할 때마다 배열은 바운더리를 벗어나는 요소에 접근하는 시도를 막기 위해 바운드 검사를 수행합니다.

              이것은 굉장히 중요한 안전성 기능이지만 성능 향상을 위해 취할 수 있는 모든 방안을 고민해봐야 합니다. 이터레이팅을 수행하는 동안 우리가 생성한 바운더리 안에서 머물게 한다면, 굳이 요소에 매번 접근할 때마다 바운드 검사를 수행할 필요는 없습니다.

              앞서 언급한 두 가지 경우에 대한 해결책은 documentIDs 를 배열 대신 typed mutable 포인터로 선언하는 것입니다. allocate 함수를 호출하면 요소들 크기에 해당하는 공간을 확보하고 블록에 대한 시작 부분을 가리키는 포인터가 반환됩니다.

              메모리를 할당했으니 메모리 해제도 해야겠죠? 배열에서 unsafe 포인터로 변경했기 때문에 메모리 해제 작업이 필요합니다. Swift에서는 메모리 할당 코드 바로 뒤에 defer 블록을 만들고 범위 바이너리 옵션 타입 그 안에 메모리 해제 코드를 추가하면 안전하게 메모리를 해제할 수 있습니다.

              예제 코드에서 추가로 수정되어야 하는 부분은 인자로 전달될 때 사용되었던 & 를 생략하는 것입니다. 암묵적 변환 대신 포인터를 직접 넘겨주기 때문에 inout 문법을 사용하지 않아도 되는 것이죠.

              배열에서 subscript를 사용했던 것처럼 포인터에 대해서도 subscript를 사용할 수 있습니다. 그 때문에 나머지 코드들은 수정하지 않아도 됩니다. 지금까지 최적화 작업을 진행해봤습니다. 불필요한 초기화 코드와 바운드 검사를 제거했고 Swift가 기본적으로 제공했던 아래의 세 가지 기능을 직접 처리했습니다.

              • documentIDs에 대한 읽기 요청이 발생하기 전에 documentIDs를 초기화해야 한다.
              • 할당한 메모리는 반드시 해제해야 한다.
              • 할당한 공간의 바운더리를 벗어나면 안 된다.

              이제 마지막 예제네요. 제가 가장 좋아하는 정렬 알고리즘인 버블 정렬입니다.

              bubbleSort 함수는 Comparable 프로토콜을 준수하는 배열을 인자로 받아, 이 배열을 오름차순으로 정렬합니다. 버블 정렬 대신 다른 정렬 방법을 사용하여 성능을 향상하고 싶겠지만 여기서는 정렬 방법을 변경할 수 없다고 가정하겠습니다.

              Swift의 Array 타입은 항상 바운드 검사를 수행하는 Array 대신 unsafe buffer 포인터를 사용할 수 있는 메서드를 제공합니다. unsafe buffer 포인터를 사용하게 되면 디버그 모드만 바운드 검사가 수행되고 릴리즈 모드에서는 검사가 생략됩니다.

              배열에서 withUnsafeMutableBufferPointer 메서드를 호출하도록 예제 코드를 수정했습니다. 메서드만 변경했을 뿐인데 성능은 눈에 띄게 향상되었습니다.

              unsafe 포인터 사용을 통해 얻은 이점은 분명합니다. 요소들을 버퍼에 유지함으로써 배열과 유사한 인터페이스를 제공하면서도 바운드 검사를 생략하여 성능은 향상했습니다.

              지금까지 unsafe 포인터 타입에 대해 알아보고, unsafe 포인터 타입으로 안전한 코드를 작성하는 방법에 대해 살펴보았습니다. 이번에는 잘못된 포인터 사용법에 대해 알아보겠습니다.

              잘못된 포인터 사용법

              unsafe 포인터 타입을 사용할 때 가장 많이 실수하는 부분은 암묵적 포인터 변환 또는 withUnsafePointer , withUnsafeByte 함수를 통해 얻은 포인터를 escape 하는 것입니다. 예제를 통해서 이 문제를 좀 더 살펴보죠.

              age 변수를 선언하고 UnsafeMutablePointer 를 이용해 변수에 대한 포인터를 생성했습니다. 그리고 포인터 주소에 저장된 값을 변경했습니다. age 변수에 의해 사용된 같은 주소를 쓰고 있기 때문에 값이 변경됩니다.

              언뜻 보기에는 괜찮아 보이지만 이 코드에는 매우 큰 문제가 있습니다. 암묵적 변환 대신 명시적인 형태로 코드를 약간 수정해보겠습니다.

              암묵적 포인터 변환을 이용해 UnsafeMutablePointer 를 초기화하는 것은 withUnsafeMutablePointer closure에서 escaping 포인터를 전달받은 것과 같습니다. 문제없이 컴파일되지만 사실 이것은 예상치 못한 동작(undefined behavior)에 해당합니다.

              이러한 형태의 코드는 오류를 발견하기가 쉽지 않습니다. 명시적으로 escape를 선언하지도 않았고 함수에 변수를 넘겨줄 때 & 를 사용하는 것은 일반적인 형태니까요. 따라서 두 가지 규칙을 꼭 기억하시길 바랍니다. 첫째, withUnsafe- 에서 얻은 포인터는 절대 escape 하지 마세요. 둘째, 암묵적 변환으로 변수에 대한 포인터를 얻지 마세요.

              감사합니다. 이 강연 내용이 Swift unsafe 포인터 타입을 이해하는 데 도움이 되었으면 좋겠네요.

              Q: bindMemory 메서드와 assumingMemoryBound 메서드 차이점은?

              A: unsafe raw 포인터는 그들이 가리키고 있는 메모리에 대한 정보가 없지만 메모리 자체는 바운드된 상태일 수 있습니다. 그 때문에 integer 타입과 바운드된 메모리에 대한 raw 포인터를 같은 메모리지만 다른 타입과 바운드된 raw 포인터로 만들 수 있습니다. 인스턴스에 좀 더 쉽게 접근하기 위해 raw 포인터를 type 포인터로 변환할 때 메모리를 바인드해야 합니다. 즉 이 타입으로 메모리를 바인드할 것이라고 컴파일러에 알려주는 것이죠. bind 는 unsfae typed pointer를 반환합니다.

              assumingMemoryBound 를 호출하면 검사를 우회하는 것입니다. 컴파일러 측면에서 보면 실제로는 어떠한 바인드도 수행하지 않습니다. 대신 이것은 이미 바운드되어 있다고 가정하기 때문에 여러분은 메모리가 해당 타입과 이미 바운드되어 있다는 정보를 가지고 있습니다. 만약 아직 메모리가 바운드되지 않은 상태에서 raw 메모리를 할당하고 assumingMemoryBound(to:) 를 호출했다면 이것은 안전하지 않고 예상치 못한 동작에 해당하게 됩니다. 왜냐하면, 해당 타입과 실제로 바운드되지 않은 상태에서 메모리에 접근했기 때문이죠.

              Q: 마지막에 보여주신 예제에 대해 궁금한 점이 있습니다. 예제를 설명하시면서 포인터에 대한 escape를 하지 말라고 하셨는데 escaping 속성을 추가하더라도 절대 escape를 하지 말라는 뜻인가요?

              A: 네 escaping 속성을 추가한 경우라도 포인터를 가지고 escape하지 말라는 뜻입니다. 암묵적 포인터 변환을 위해, 변수를 인자로 전달해서 mutable 포인터로 변환하고 싶을 것입니다. 예제 코드의 UnsafePointer 초기화 코드처럼 말이죠. C 함수에 변수를 전달할 수도 있지만, C 함수는 일반적으로 반환하지 않기 때문에 C 함수에서 반환받을 수 있다는 보장이 없습니다.

              문제는 이것이 대부분의 경우 잘 동작한다는 것이죠. 실제로 마지막 예제는 playground에서도 잘 동작합니다. age 변숫값을 변경할 수 있죠. 하지만 computed 속성인 경우라면 의도대로 동작하지 않을 것입니다. 컴파일러는 포인터와 관련된 최적화를 수행하지 않게 설정할 수 있는데 만약 최적화 옵션을 적용해서 실행할 경우 해당 코드 라인을 삭제 처리하고 age 변숫값은 업데이트되지 않을 것입니다.

              2016년 3월에 진행한 try! Swift Tokyo 행사의 강연입니다. 영상 녹화와 제작, 정리 글은 Realm에서 제공하며, 주최 측의 허가 하에 이곳에서 공유합니다.


0 개 댓글

답장을 남겨주세요