Unix를 비롯한 대부분의 시스템은 LF ( \n , 0x0A )를 개행 문자로 사용하기 때문에 CR ( \r , 0x0D )을 사용하는 경우가 거의 없다. 텍스트 애플리케이션에서 진행표시줄을 만드는 것이 진행표시줄을 만드는 것이 현대 컴퓨터에서 CR 을 사용하는 거의 유일한 경우라고 볼 수 있다. CR 을 사용하면 터미널에서 진행표시줄을 간단하게 구현할 수 있다.
개발을 위해 컴퓨터에 리눅스를 설치할 때 반드시 설치하는 프로그램이 있다. sl 이라는 프로그램이다. 이게 무슨 프로그램이냐면 커맨드 창에서 sl 을 치면 기차를 보여주는 프로그램이다. 실용성 있는 프로그램은 아니고, 터미널에서 많이 사용되는 ls 명령어의 오타를 냈을 때 화면에 기차를 보여줘 생각할 시간을 주는 프로그램이다. 이를 통해 오타를 낼 정도로 마음이 급한 상황에서 다른 실수를 하지 않고 조금은 침착해지라는 의미에서 항상 설치한다. 출처: https://github.com/mtoyoda/sl 터미널은 실행한 프로그램이 내보내는 두 출력 stdout 과 stderr 를 받아 화면에 보여주는 프로그램이다. stdout 과 stderr 은 순차적인 출력이다. 보통의 출력은 왼쪽 위에서 오른쪽 아래로 흘러간다. sl 처럼 이미 쓰인 화면에 새로운 글자를 그리기 위해서는 특별한 방법이 필요하다. 이 특별한 방법은 escape codes 라고 한다. escape codes는 터미널에 정의된 일종의 약속이다. 이 약속은 현재는 ISO 6429 에서 정의하는 표준을 따른다. 하지만 과거에는 통일된 규정이 없이 터미널 모델마다 다른 규정을 가지고 있었다. 과거의 컴퓨터를 모르면 터미널 모델마다 다르다는 것이 이해되지 않을 수도 있다. 이를 알기 위해서는 컴퓨터의 역사를 알아야 한다. 지금은 터미널이라고 하면 CLI 를 위한 애플리케이션을 의미한다. 하지만 과거 터미널은 문자 그대로 컴퓨터의 최종 단말기였다. 이 단말기는 메인프레임 컴퓨터 에 연결돼 메인프레임 컴퓨터의 입출력을 담당했다. 현대의 터미널 앱이 터미널 에뮬레이터 라고 불리는 이유가 이 터미널을 에뮬레이트 하는 것이기 때문이다. 과거 터미널은 ADM-3A , 지금도 유명한 IBM 사의 IBM 2260 과 IBM 3270 , 후에 HP에 인수되는 DEC 사의 VT 시리즈 등 다양한 회사에서 개발됐고, 이들은 각각 고유의 표준을 가지고 터미널을 조정했다. 출처: https://en.wik
RAII 는 C++에서 자주 쓰이는 idiom으로 자원의 안전한 사용을 위해 객체가 쓰이는 스코프를 벗어나면 자원을 해제해주는 기법이다. C++에서 heap에 할당된 자원은 명시적으로 해제하지 않으면 해제되지 않지만, stack에 할당된 자원은 자신의 scope가 끝나면 메모리가 해제되며 destructor가 불린다는 원리를 이용한 것이다. 원래는 exception 등으로 control flow가 예상치 못하게 변경될 때를 대비하기 위해서 쓰이던 기법이다. 아래의 예제를 보자. 첫 번째 코드는 위험하다. thisFunctionCanThrowException() 함수에서 exception을 발생시킨다면 resource를 해제하지 못한다. 두 번째 코드는 일단은 resource 를 해제하고 있다. 하지만 보기에 좋은 코드는 아니다. 그리고 유지하기도 어려워진다. 세 번째 코드는 RAII를 위해 c++ 11의 스마트 포인터인 unique_ptr 을 이용하는 방법이다. unique_ptr 은 소멸할 때 자신이 들고 있는 메모리를 해제시켜주기 때문에 함수 밖으로 나가면 resource 가 해제되는 것이 보장된다. 여기서 말하는 자원은 단순히 heap 메모리만을 말하는 것이 아니다. heap 메모리 이외에도 파일이나 database connection과 같은 것들도 전부 RAII를 이용해 안전하게 사용할 수 있다. 여기에 더 나아가서 특정 scope를 벗어나면 반드시 실행돼야 하는 코드들도 RAII를 이용해 처리할 수 있다. 즉, 다른 언어에서 finally 에 해당하는 구문을 RAII를 이용해서 처리할 수 있다. 실제로 C++의 아버지이자 RAII라는 용어를 처음 만든 Bjarne Stroustrub는 c++에 finally 를 집어넣지 않는 이유를 "RAII가 있는데 굳이 있을 필요가 없다." 라고 말하고 있다.
C++ 03 까지의 enum 은 여러 가지 문제를 가지고 있었다. 그래서 그 문제들을 해결하기 위해 C++ 11은 enum class 라는 것을 새로 만들었다. 이제부터 기존의 enum 에 어떤 문제가 있었고, 이것을 enum class 에서 어떻게 해결하였는지 살펴볼 것이다. 우선 기존의 enum 은 전방 선언할 수 없었다. 그 이유는 enumerator에 어떤 값이 들어있을지 알 수 없으면 그 크기를 정할 수 없기 때문 이다. 하지만 enum class 는 underlying type을 명시하지 않으면 int 타입과 같은 크기의 변수로 선언되고, int 값 안에 들어가지 못할 값을 지정하면 컴파일 에러를 발생시킨다. 만약 int 를 벗어난 범위의 값을 사용하고 싶다면, underlying type을 명시해주어야 한다. 기존 enum 의 또 다른 문제는 enumerator의 이름의 범위가 한정되지 않는다는 것이다. 예를 들어 아래와 같은 코드를 보자. IO 함수의 결과와 Parse 함수의 결과를 enum 으로 표현해 보았다. 하지만 이 코드는 컴파일되지 않는다. IOResult 의 Error , Ok 가 ParseResult 의 Error , Ok 와 겹치기 때문이다. 이를 해결하기 위해서는 다음과 같이 enumerator의 이름을 다르게 하거나 아래와 같이 namespace 를 이용해야 했다. 하지만 enum class 는 enumerator의 이름이 enum class 안으로 한정되기 때문에 이런 복잡한 과정이 필요 없이 그저 enum class 를 선언하여 사용하면 된다. 무엇보다 기존 enum 의 가장 큰 문제는 정수형 변수로 암시적으로 변환되는 약 타입(weak type) 변수라는 것이다. 하지만 enum class 는 정수형 변수로 암시적 변환이 되지 않는다. enum class 를 정수형 변수처럼 사용하려고 하면 컴파일 에러를 발생시킨다. 만약 정수형 변수로 사용하고 싶으면 static_cast 를 이용해
https://docs.google.com/presentation/d/12A5RlMCVDN6tA_zUnLrZ0TN5v08gz2GhlRdzxy3T3mU/edit?usp=sharing 회사에서 최근에 Log aggregator system으로 무엇을 사용해야 할지 조사해본 자료다. 우선 log aggregator가 무엇인지 한 문장으로 설명하면, 여러 머신에서 쌓인 로그들을 한 번에 분석할 수 있도록 수집하여 주는 시스템을 말한다. 요새는 특히나 클라우드 시스템이 유행하면서 같은 일을 하는 시스템임에도 다른 머신에서 돌아가는 일이 많아지면서 필요성이 크게 증가하였다. 이번 조사로 알게 된 것들을 적어보도록 하겠다. Scribe 우선 scribe 는 Facebook에서 제작하고 사용하던 log aggregator system이다. scribe: https://www.cnblogs.com/brucewoo/archive/2011/12/13/2285482.html 다른 로그 수집 시스템들을 보면 알겠지만, Scribe는 다른 시스템보다 간단한 구조로 되어 있다. Scribe는 일종의 message queue와 message queue에 쌓인 message를 DB에 저장해 주거나, DB가 실패하였으면 local에 저장하였다가 DB가 복구되었을 때 다시 DB에 저장해 주는 것만을 책임진다. 다시 말하면, message queue에 실제로 메시지를 보내는 부분은 사용자가 직접 작성하여야 한다는 것이다. 흔히들 말하는 scribe의 장점은 성능이다. C++로 만든 만큼 다른 시스템들의 3~5배 정도의 성능을 보여준다는 것이다. 하지만 실제 scribe 사용자들은 무엇보다도 Facebook이 실제로 사용하였던 솔루션인 만큼 성능과 안정성에서 신뢰도가 있다는 것을 장점으로 뽑는다. 하지만 나는 scribe를 사용하는 것을 추천하지는 않는다. 일단 가장 큰 문제는 더이상 Facebook이 Scribe를 사용하지 않는다는 것이다. Facebook은 이미 Ja
최적화는 귀찮다. 눈에 띄는 실수를 한 게 아니면 어떻게 고쳐야 할지 감이 오지도 않고, 대부분의 최적화는 가독성을 떨어뜨리기 때문에 버그가 발생할 확률이 늘어난다. 하지만 어떤 최적화 테크닉은 코드를 크게 수정하지 않고 큰 성능 향상을 가져온다. 메모이제이션 이 그 대표적인 예제다. 계산이 무겁거나, 디스크의 값을 읽거나, 네트워크 통신처럼 근본적으로 시간이 오래 걸리는 일은 그 실행 결과를 저장했다 재사용하는 것만으로 큰 성능향상을 가지고 온다. 파이썬은 메모이제이션을 쉽게 적용할 수 있는 데코레이터 를 제공한다. functools 모듈의 lru_cache 데코레이터 가 이것이다. 이 데코레이터를 붙이면 함수의 실행 결과를 캐싱해준다. 캐시의 크기는 maxsize 로 지정할 수 있다. 저장할 실행 값이 이 개수를 넘어가는 경우 LRU 알고리즘 에 따라 가장 오래전에 사용한 결과를 지우고 새 값을 캐싱한다. lru_cache 를 사용하면 쉽게 최적화할 수 있지만 아무 함수에나 사용할 수 있는 건 아니다. 함수의 인자를 캐시키로 사용하기 때문에 함수의 실행 결과가 함수의 인자 이외에 다른 요소에 의존적인 함수에는 사용하지 못한다. 즉, 랜덤 요소가 들어가거나 시간에 따라 결괏값이 변하는 함수에는 사용하면 안 된다. 결정성이 보장되는 함수에만 사용할 수 있다는 것은 모든 캐시의 공통적인 특성이다. 여기에 더해 파이썬이 제공하는 lru_cache 는 그 구현상의 문제로 한 가지 제약이 더 있다. 이 데코레이터는 값을 저장하기 위해 인자를 키로 가지는 dictionary 를 사용한다. 따라서 모든 인자가 hashable 타입이어야 한다. 다시 말해 mutable 하지 않은 dictionary, set, list 등을 인자로 받는 함수는 이 데코레이터를 사용해 캐싱할 수 없다. 이런 타입을 인자로 받던 함수는 그 인자를 frozenset 이나 tuple 같은 immutable 타입으로 변환해야 한다. 게다가 keyword argument 를