« Previous : 1 : 2 : 3 : 4 : 5 : Next »

컴퓨터그래픽에서 벡터 그래픽의 반의어로 픽셀과 비트맵을 다루는 체계를 래스터 그래픽이라고 흔히 부른다. 종이가 아니라 해상도가 상대적으로 낮은 모니터 화면이 주 무대이고, 면을 채우는 기본 단위가 scan line(주사선)이라는 관점에서 정립된 용어이다.

그리고 2D 비트맵(더 정확한 명칭은 래스터..?) 그래픽 API를 보면 어떤 플랫폼용 어떤 언어의 라이브러리이든지 점과 직선, 곡선을 그리는 함수가 있고, 사각형과 원을 그리는 함수가 있다. 이게 기본이다.
점이나 사각형이야 그리는 방식이 너무 trivial하니 제끼고, 원이나 곡선을 빠르게 그리는 원리는 기하 알고리즘의 일종으로 다뤄지기도 한다. 그 단순한 직선조차도 굵기가 2픽셀 이상이 되면 중심점을 생각해야 할 것이고, 무거운 부동소수점 연산 없이 anti-aliasing까지 하면서 그린다는 조건이 추가되면 결코 쉽지 않은 일이 된다.

그리기 기능 중에서 특정 픽셀부터 시작하는 flood fill은 무척 독특한 동작이다. 기하 알고리즘이라기보다는 스택 메모리를 동원해서 컴에게 길 찾기 재귀호출 노가다를 시키는 코딩의 영역이다. 빼곡한 미로의 내부에 있는 한 점에서 flood fill을 시켜 보면 이건 본질적으로 길 찾기와 다를 바 없다는 걸 알 수 있을 것이다.

글쎄, flood fill은 그래픽 에디터에서 사용자가 내리는 채우기 명령을 구현하는 형태로나 쓰이지, 직선과 곡선, 사각형과 원처럼 그림을 그리는 구성요소로서 프로그램이 내부적으로 사용할 일은.. 정말 아주 특수한 상황이 아니라면 없을 것이다. 도형 자체를 처음부터 내부가 채워진 형태로 그려야지, 도형의 윤곽만 그린 뒤에 도형 내부의 임의의 점을 따로 주고 채우는 건 몹시 비효율적이기 때문이다.

그래서 그래픽 라이브러리에는 다각형을 그리는 함수가 있다. 다각형의 경계선만 찍찍 그리는 것이야 LineTo만으로 얼마든지 할 수 있으므로, 이런 함수는 내부가 채워진 다각형을 그리는 것이 핵심이다. 그러니 이 함수는 다른 함수와 달리, 반드시 다각형의 꼭지점들이 담긴 배열을 전달받아야 한다.
옛날 도스 시절의 베이식은 타 언어들에 비해 그래픽 모드의 접근성이 좋았지만, 정작 다각형을 그리는 API는 없었다.

그럼 다각형을 채우는 기능은 어떤 방식으로 동작하는 걸까?
이걸 구현하기 위해서는 어떤 점이 다각형의 내부에 속하는지를 판단해야 한다. 더 나아가서 이 점에서 한쪽으로 scan line을 그어 나갈 때 어디까지가 동일하게 다각형의 내부 또는 외부인지를 판단해야 한다.

이걸 판단하는 방법은 의외로 간단하다. 그 점으로부터 아무 방향으로(예: x축 양의 방향) 한없이 직선을 그을 때, 그 선이 다각형을 구성하는 선분과 얼마나 몇 번이나 마주치는지를 판단하면 되며, 이걸 판단하는 방법도 크게 두 갈래로 나뉜다. 바로 (1) 홀짝 아니면 (2) 0여부이다.

홀짝법은 마주친 선분이 짝수 개이면 다각형의 외부이고, 홀수 개이면 내부라고 판단한다. 다시 말하지만 이 가상의 선은 정말 아무 방향으로나 그리면 된다. 다각형이 모든 방향으로 닫혀서 내부에 공간이 존재한다는 사실 자체가 이 판별법의 correctness를 보장해 준다.

0여부는.. 홀짝보다 더 절묘하다. 초기값이 0인 가중치라는 걸 두는데, 마주친 선분이 우리가 그은 가상의 선을 위에서 아래로 교차한다면 가중치에 1을 더한다. 그렇지 않고 아래에서 위로 교차한다면 1을 뺀다.
이렇게 해서 최종적으로 가중치가 양수든 음수든 0이 아닌 값이 나온 점은 다각형의 내부라고 간주하고, 0인 점은 외부라고 간주한다.

0이나 홀짝이나 그 말이 그 말 같은데.. 실제로 자기네 선분끼리 배배 꼬아서 교차하지 않는 일반적인, 평범한 오목/볼록다각형이라면 어느 판별법을 사용하든 결과에는 아무 차이가 없다.
하지만 당장 오각형 별표를 한붓그리기로 그린 궤적을 줘 보면 둘은 서로 차이를 보인다.

사용자 삽입 이미지
Windows API에서는 SetPolyFillMode라는 함수가 있어서 두 방식을 모두 사용해 볼 수 있다. 더 단순한 홀짝법이 ALTERNATE이고 기본값이다. 0여부는 WINDING... Windows 1.x 시절부터 존재해 온 오래된 고전 API여서 그런지, 매크로 상수의 앞에 접두사가 붙어 있지도 않다(PFM_* 같은?? ㅎㅎ).

오각형 별표에서 별의 중앙에 생긴 공간을 보면.. 그 옆으로 다각형 경계를 나타내는 선이 어느 방향이든 두 개가 존재한다(짝수). 그런데 이들은 방향이 둘 다 오르막 아니면 둘 다 내리막이며, 이 때문에 winding value는 nonzero가 된다. 그러니 ALTERNATE일 때는 이 공간이 비워지지만 WINDING일 때는 공간이 채워지는 것이다.

그 위의 더 복잡한 꼬인 사각형도 상황이 비슷하다. 잘 살펴보면 이 궤적도 홀수점이란 게 전혀 존재하지 않으며 한붓그리기가 가능하다.
그런데 WINDING일 때는 궤적이 꼬여서 생긴 내부의 사각형 공간 둘 중에서 좌측 하단 한 곳만 채워져 있다. 그 이유는 역시 저기서만 winding value가 nonzero이기 때문이다.

일반적으로 WINDING(0여부)이 판정하는 다각형 영역은 ALTERNATE(홀짝)의 상위 호환이다. ALTERNATE가 판정하는 영역을 100% 포함하면서 일부 영역을 추가적으로 더 판정한다는 뜻이다. 그렇다고 해서 모든 닫힌 영역을 한 치의 예외 없이 몽땅 내부라고 판정하는 건 아니다.

뭐.. 현실의 벡터 그래픽에서 이 따위 선끼리 교차하는 배배 꼬인 폴리곤을 생성하는 것은 애초부터 권장되지 않는 금지 사항이다. 가령, 속이 빈 오각별을 그리고 싶으면 저렇게 보이는 대로 삼각형 다섯 개로 풀어서 표현하라는 것이다. 윤곽선 폰트 등 벡터 그래픽 편집기들은 그렇게 폴리곤의 모양을 자동으로 수정해 주는 기능도 제공한다.
그러니 이렇게 fill mode의 차이점을 미주알고주알 관찰할 일이 현업에서는 거의 없을 것이고, 이런 건 그냥 학교에서 컴퓨터그래픽스 기초를 공부할 때 이런 방식도 있다는 걸 알기만 하고 넘어가면 될 것 같다.

하지만 그게 전부가 아니다. 다각형 채우기의 기능이 더 확장되면 다음 영역에도 도달하는데, 이때 fill mode의 차이점이 다시 드러나게 된다.

1. 여러 다각형을 한꺼번에 그리기
이건 내부에 구멍이 뚫린 다각형을 그릴 수 있다는 것에 의의가 있다. 구멍은 Polygon 함수를 연달아 호출하는 것으로는 표현할 수 없기 때문이다.

Windows에는 여러 다각형을 한꺼번에 그리는 PolyPolygon이라는 함수가 있다. 그런데 아까처럼 한 다각형에서 변들이 서로 교차하고 꼬였을 때뿐만 아니라, 변은 꼬이지 않았고 여러 다각형들의 영역이 서로 겹칠 때에도 fill mode의 차이는 유의미한 동작의 차이를 만들어 낸다.

사용자 삽입 이미지

위의 그림은.. 뭐 이론적으로는 한붓그리기가 가능하기 때문에 역시 꼬인 단일 다각형으로 궤적을 나타낼 수 있다. 하지만 앞서 예를 들었던 오각별이나 그 사각형 그림과 달리, 일부 점과 점이 겹치는 건 피할 수 없을 것이다. 무슨 말인가 하면, 저 궤적을 꼭지점 좌표의 배열로 기술했을 때, 4개의 선분과 만나는 점은 두 번 등장하는 부분이 생긴다는 것이다.

꼬인 단일 다각형이 아니라 영역이 일부 겹치는 사각형과 삼각형을 서로 떼어서 PolyPolygon으로 그린 경우.. ALTERNATE(홀짝)에서는 짝수 개의 다각형에 속하는 영역은 비우고, 홀수 개에 속하는 영역만 칠한다. 그러고 보니 동작이 뭔가 XOR스러워 보인다. 각 다각형들의 꼭지점이 기술된 방향은 어느 쪽이건 무관하다 (시계 or 반시계 방향)

그러나 WINDING(0여부)일 때는 그 특성상 방향이 같은 다각형들은 겹치더라도 영역을 모두 칠한다. 겉의 껍데기가 시계 방향이라면.. 그 안의 구멍은 반시계 방향으로.. 다른 방향으로 칠해져야 구멍이 비게 된다! 다시 말하자면, WINDING에서도 위의 그림의 왼쪽처럼 중앙이 비어진 그림을 그리고 싶다면 사각형과 삼각형의 좌표 방향이 서로 반대여야 한다.
꼬인 단일 다각형에서 fill mode의 차이점을 설명하는 프로그래밍 서적들이.. 다중 다각형까지 연계해서 동일 개념을 설명하는 경우는 내가 딱히 못 본 것 같다.

2. 직선뿐만 아니라 베지어 곡선까지 포함된 궤적의 내부를 채우기
위와 같은 구멍 감지에다가 곡선 지원까지 포함되면.. 이건 뭐 윤곽선 글꼴 래스터라이저가 번듯하게 완성된다. 물론 본격적인 폰트 엔진은 거기에다 작은 크기에 대비한 정교한 안티앨리어싱과 힌팅, 글꼴 글립 캐시, 더 나아가 복잡한 유니코드 문자 형태 분석까지 추가되는데 이것들 하나하나가 별개의 전문 영역일 정도이다.

FreeType 라이브러리는 그 중에서 제일 저수준인 그리기, 안티앨리어싱, 힌팅까지만 담당한다. 요즘 소프트웨어들은 글자 하나를 찍는 것도 겨우 8*16, 16*16 비트맵 글꼴 찍던 시절과는 차원이 다르게 더 복잡해져 있는 셈이다.
그건 그렇고.. Windows API에는 직선과 곡선이 포함된 도형을 한꺼번에 그리는 것은 윤곽선만으로 한정이다. PolyDraw라는 함수가 있다.

내부를 채우는 것은 한 함수로 지원되지 않으며, path라는 걸 써야 한다. 얘는 Windows GDI가 제공하는 강력한 벡터 그래픽 라이브러리로, 직선, 베지어 곡선, 원과 원호, 심지어 다른 트루타입 글꼴의 글립까지 몽땅 궤적으로 표현해서 한꺼번에 내부를 채울 수 있다. 구멍 처리도 물론 된다.
BeginPath (그리기) CloseFigure (그리기) EndPath 이런 식으로 말이다. 위의 1과 2를 모두 할 수 있다.

내 경험상 트루타입 폰트는 WINDING 방식으로 래스터라이징을 한다. 글꼴 글립을 그릴 때부터 제일 밖의 path는 시계 방향이고, 그 안의 구멍 윤곽을 기술하는 path는 반시계 방향이고, 구멍 안의 칠하는 영역은 또 시계 방향.. 이런 식으로 디자인을 해야 한다.

허나, 예전에 MS Office 2003 이하 버전에서 제공되던 클래식 WordArt는 이 원칙을 지키지 않고 트루타입 글꼴도 홀짝 ALTERNATE 방식으로.. 짝수 회 overlap 영역은 무조건 비웠던 것 같다.
그래서 composite glyph 형태로 표현되는 비완성형 한글 글꼴에서 글립이 겹칠 수 있는 복잡한 글자를 찍어 보면 저렇게 흰 부위 glitch가 발생하곤 했다. (아래 그림에서 ㅆ, ㅠ, ㅔ 부분 참고)

사용자 삽입 이미지

Office 2007 이상부터 제공되는 WordArt는 이 문제가 해결됐다. 그리고 아래아한글의 글맵시도 0여부 WINDING 방식으로 맞게 색칠을 하기 때문에 glitch가 발생하지 않는다.

그러고 보니.. MS Office는 지난 2007때부터 그래픽 엔진이 크게 바뀌었다. 워드아트의 글자 장식 기능도 리뉴얼 됐고 PowerPoint 같은 데서도 직통으로 사용 가능해졌는데, 정작 본가인 Word에서는 2003 이하의 클래식 워드아트가 제공됐다. 다음 버전인 Office 2010부터 Word에서도 동일하게 리뉴얼된 워드아트가 제공되기 시작했다.

Posted by 사무엘

2021/05/12 08:35 2021/05/12 08:35
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1885

0. 들어가는 말

세상에는 누구나 드나들 수 있는 공공장소와 누구나 신호를 주고받을 수 있는 통신망이 있지만, 사람마다 사생활도 있고 비밀도 있다. 이런 게 보장되지 않으면 인간이 정상적으로 살아갈 수 없으며, 사실은 생존에 필요한 기본 욕구를 충족시킬 수도 없어진다.
그렇기 때문에 어떤 장소나 어떤 정보는 내부의 믿을 수 있는 사람, 우리 조직 소속인 사람만 접근할 수 있게 해야 한다.

이를 구현하기 위해 전자에 대해서는 열쇠와 자물쇠라는 게 만들어져 왔고, 후자를 위해서는 암호 기술이라는 게 개발되었다. 열쇠가 없는 사람, 암호를 모르는 사람은 문을 열고 들어갈 수 없고 그 자료를 열람할 수 없다. 심지어 정보가 담긴 매체라든가 물건이 담긴 금고 자체가 유출되더라도 그 안의 물건에 손댈 수는 없다.

열쇠와 암호는 서로 담당하는 영역이 다르지만 심상이 아주 비슷한 구석도 있다. 요즘은 자물쇠도 열쇠 대신 일종의 숫자 암호인 디지털 도어락으로 바뀌고 있고, 암호학에서도 key라는 용어가 즐겨 쓰이니 말이다.

다만, password라는 개념의 '암호'와, 원본 정보를 password 없이는 알아볼 수 없게 변조하는 '암호화'(cryptography, encryption), 그리고 암호화가 적용된 텍스트를 작성하는 것 내지 그 결과물인 암호문(cipher).. 이게 우리말에서는 딱 부러지게 잘 구분되지는 않는다는 점을 감안할 필요가 있다. 마치 '뛰다'(jump/run), '다른'(other/different), '푸르다'(green/blue) 이런 게 잘 구분되지 않는 것처럼 말이다.

보이니치 괴문서처럼 암호 사용 여부와는 무관하게 단순히 체계와 의미가 파악되지 못한 물건도 decipher되지 못했다고 말한다.
그에 비해 한국어 ‘해독’은 동음이의어 한자의 특성 때문에 ‘독’에 read라는 뜻뿐만 아니라 poison이라는 뜻까지 있어서.. 미묘하게 중의적인 심상을 자아낸다. 마치 ‘충전’의 중의적인 심상처럼 말이다. (전기도 충전, 금액도 충전..)

1. 패스워드의 관리, 해시 함수

먼저, 암호화와 무관하게 로그인 패스워드 자체에 대한 보안을 논하는 기본 원칙이 있다. 이 주제에 대해서는 본인이 마지막으로 글을 썼던 때가 무려 7년 전이었구나.;; (☞ 이전 글)
사용자의 입장에서는 “다양한 종류의 문자를 섞어서 최소한 10자 이상의 긴 문자열로 정할 것, 주기적으로 바꿔 줄 것” 이런 게 있다.

물론 누군가의 원한을 사고 있어서 작정하고 당신의 계정을 뚫으려 노력하는 공격자라도 있는 게 아니라면.. 이런 얘기에 지나치게 민감해할 필요는 없다. 암호 좀 허술하게 만든다고 해서 현실에서 당장 위험에 빠지는 건 아니다. 하지만 그렇다고 해서 1234, asdf, iloveyou, 생일, 전화번호 정도로 암호를 너무 성의없게 만드는 건 정말 피해야 한다.

그리고 사용자의 접속 정보를 저장하는 운영자의 입장에서는 암호를 절~대로 평문 그대로 저장하지 말아야 한다. 게다가 암호 문자열이 무슨 도스 시절 파일 이름마냥 10~15자 같은 제약이 걸려 있거나 특정 특수문자 기호를 지정하지 못한다면.. 그건 정말 기본적인 보안 관념도 없던 쌍팔년도 시절 사고방식의 미개한 사이트라고 욕 먹어야 할 것이다.

사용자가 암호를 잊어버렸다면 사이트 운영자라도 그 암호를 알 수 없는 게 정상이다. 본인 인증을 시행한 뒤에 임의로 지정한 새 암호를 알려줘야지, 기존 암호를 그대로 알려주는 사이트는 그 자체가 보안이 매우 허술하다고 실토하는 것과 같다.

그러니 이런 패스워드는 딱히 key 없는 단방향 암호화라는 변조를 거쳐서 저장해야 하는데, 이럴 때 해시 함수라는 게 쓰인다. hash란 어떤 임의의 길이의 원본 문자열이 주어졌을 때 원본과는 완전히 다르고 무질서하게 변조된 다른 고정된 길이의 문자열 내지 바이트 시퀀스를 되돌리는 수학 함수이다. 원본 문자열이 조금만 바뀌어도 완전히 확 달라진 결과값이 나와야 한다.

F(X) → Y인데, 역으로 Y로부터 X를 복원하는 것은 수학적으로 불가능하다. 그리고 F(A) ≠ F(B) 라면 절대적으로 A≠B이지만, F(A)=F(B)이면서 여전히 A≠B일 확률은 매우 매우 낮은 확률로 존재한다. 비유하자면, 퀵 정렬의 시간 복잡도를 n^2로 만드는 input을 우연히 만들 수 있을 정도의 확률로 존재한다.

패스워드의 비교는 사용자가 입력한 문자열을 hash한 결과와, 저장된 패스워드 hash값을 비교함으로써 행해진다. 평문을 비교하는 게 아니라는 것이다.
사실, 이런 해시 함수는 패스워드의 보관뿐만 아니라 방대한 파일이 정확하게 잘 전송되었는지 동등성을 검증하는 용도로도 즐겨 쓰인다. 수 GB짜리 파일의 해시값이 얼마가 나와야 정상인데 엉뚱한 값이 나왔다면 중간에 오류가 있었다는 뜻이 되기 때문이다.

해시 함수가 튼튼하고 안전하려면 (1) F(X)로부터 X를 역으로 추적하는 것이 불가능해야 하고, (2) 서로 다른 두 입력 A, B에 대해서 동일한 해시값이 산출되는.. 다시 말해 ‘충돌’ 사례가 없어야 한다. 전자는 암호화된 값으로부터 패스워드를 복원하는 것이고, 후자는 의도하지 않았던 엉뚱한 암호로 원래 패스워드의 사칭을 가능하게 하기 때문이다.

성능 좋은 해시 알고리즘으로는 MD5니, SHA-256 이런 부류가 공개되어 쓰이고 있다. 이들도 한번 만들고 끝이 아니라 버전과 출력 해시의 길이가 올라가는 편인데, 기를 쓰고 공격하려 애쓰는 연구자에 의해 충돌하는 입력값 쌍이 발견되기도 한다. 그러면 그게 해당 알고리즘을 사용하는 소프트웨어의 잠재적 보안 결함으로 이어진다.

그리고 해시값으로부터 원래 입력을 역추적하기 위해 요즘은 상상을 초월하는 물량 데이터빨이 동원된다. “MD5 해시값을 자동으로 계산해서 구해 드립니다”라는 웹페이지를 개설한 뒤, 전세계 사용자가 입력했던 문자열과 그 해시값 수십억 개를 몽땅 보관해 놓는 것이다. 그래서 산술 연산을 하는 게 아니라 DB를 조회함으로써 해시값 복원을 한다. 우리가 남들도 떠올릴 만한 평범한 문자열로 패스워드를 만들면 위험한 이유가 이 때문이다.

2. 대칭 키 암호화

이게 우리가 흔히 생각하는 데이터 암호화이다. 발신자가 A라는 텍스트를 K라는 패스워드(혹은 key)를 이용해서 E라는 암호화 함수로 암호화해서 B라는 암호문을 얻는다. 수식으로 표현하면 B=E(A, K) 정도? 그러면 수신자는 D라는 복호화 함수를 이용해서 D(B, K)를 돌림으로써 A를 얻는다.
이걸로 끝.. ‘대칭’이라는 말은 발신자와 수신자가 K라는 동일한, ‘대칭인’ key를 공유하는 암호 체계라는 뜻이다.

암호화 함수는 해시 함수와는 달리 복호화 함수도 존재하며, key만 안다면 원문 복원이 가능하다는 차이가 있다. 해시 함수는 애초에 어떤 입력에도 128비트 같은 동일한 길이, 즉 동일한 정보량을 가진 해시값이 돌아오지만, 암호화 함수는 출력의 정보량이 입력의 정보량과 대등하다. 그러니 용도가 서로 근본적으로 완전히 다르다.

컴퓨터가 없던 시절에도 마치 VMS로부터 WNT (Windows NT)라는 명칭을 만드는 것처럼 글자들을 일정 간격 앞뒤의 것으로 변조하거나, 심지어 key 문자열의 형태를 토대로 각 글자들을 가변적인(하지만 규칙과 패턴은 물론 있는) 오프셋만치 변조하는 기법 정도는 응당 쓰였다. 모든 글자를 고정적인 오프셋만치 변조하는 암호는 각 글자들의 빈도수 분석을 통해 비교적 금방 깰 수 있을 것이다.

2차 세계 대전까지만 하더라도 전자식 컴퓨터라는 게 사실상 없던 시절이었고, 지금에 비하면 매우 단순하고 원시적인 암호가 쓰였다. 연합군이 승리한 것에는 적국(일본, 독일) 군대의 암호를 풀어낸 것이 아주 큰 기여를 했음이 주지의 사실이다.
컴퓨터가 등장한 뒤부터는 매우 컴퓨터 친화적인 비트 회전과 XOR 연산이 암호화에 즐겨 쓰이고 있으며 그 수준이 과거엔 상상도 할 수 없을 정도로 복잡해졌다. 문자열을 암호화하기 위해서는 문자열을 구상하는 각 문자들을 숫자처럼 취급하는 게 필수이다.

정보 보호라는 업계에서 이런 암호화와 관련하여 통용되는 철칙이 있다. 암호화 알고리즘은 자신의 동작 방식과 로직이 소스 코드 차원에서 몽땅 공개되어 있더라도 절대적으로 안전해야 한다는 것이다. 데이터의 안전은 key가 보장해야지, 알고리즘을 공격자가 알고 있는지의 여부와는 무관해야 한다.
그렇기 때문에 이 바닥은 소스를 공개할 수 없는 우리만의 초특급 비밀 노하우 원천기술 같은 게 없다. 모든 게 투명하게 공개돼 있고 심지어 취약점이 발견된 것, 수정 내역도 공개된다. 이런 열린 관행 덕분에 컴퓨터 세계는 역설적으로 더 안전해질 수 있었다.

여담이지만, 지난 2009년엔 ‘코드소프트’라는 정체를 알 수 없는 어느 스타트업 기업에서 상금까지 걸면서 자기네 암호화 알고리즘에 대한 크랙 공모전을 주최했으나 비슷한 시기의 T-max 운영체제 같은 흑역사만 남겼던 적이 있다. 자기네 핵심 기술이라던 암호화 알고리즘은 허술하기 짝이 없어서 겨우 몇 시간 만에 뚫려서 큰 망신을 당했으며, 그 회사도 얼마 못 가 통째로 폐업했기 때문이다(소스는 비공개이고 임의의 데이터에 대한 암호화 결과 확인만 가능). 걔들은 암호화 알고리즘의 전문가는 고사하고 보안에 대한 기본 관념이 있긴 한 기업이었는지가 의문이 든다.

대칭 키 암호화 알고리즘으로는 AES, DES 같은 기성 표준 알고리즘이 있고 이것도 버전 내지 사용하는 정보량(비트수)이 올라가고 있다. AES는 오늘날의 컴퓨터 성능으로는 뚫리는 데 걸리는 시간이 위험할 정도로 짧아졌기 때문에 이제 사용이 권장되지 않는 지경이 되었다.
우리나라에서도 SEED, ARIA, LEA라는 알고리즘을 자체 개발해서 국가 표준으로 지정한 바 있다. 국정원 내지 한국 인터넷 진흥원 이런 데서 날고 기는 수학 박사를 채용해서 머리 굴려서 개발한 듯하다.

문서나 압축 파일에 암호를 거는 기능에도 응당 이런 암호화 알고리즘이 쓰인다.파일 내부에다가 패스워드를 평문으로 저장하는 미친 짓을 하지는 말아야 할 것이다. 혹은 파일 내용 자체를 암호화하지 않아서 헤더의 패스워드 부분만 건너뛰고 나면 나머지 내용을 고스란히 읽을 수 있게 하는 것도 치명적으로 잘못된 설계이다.
틀린 패스워드를 주면 해독이 잘못되어 완전히 엉뚱한 파일이 생성될 텐데, 패스워드가 맞는지 틀린지를 확인하는 건 정상적으로 해독됐을 때의 파일 checksum hash 같은 걸 별도로 둬서 확인해야 한다. 암호화 알고리즘 다음에 붙는 CBC, GCM 같은 모드 명칭은 바로 이런 검증 방식을 가리킨다.

안전한 암호화 알고리즘이라면 평문이나 key가 조금만 달라져도 이들의 원래 형태와 통계적 특성을 전혀 알 수 없는 엉뚱한 암호화 결과가 출력으로 나와야(혼돈과 확산) 안전할 것이다. 이 점에서는 해시와 비슷한 구석이 존재하며 심지어 난수 생성 알고리즘과도 비슷하다고 볼 수 있다. 입력을 0, 1, 2 .. 순차적으로 주고 이를 hash시킨 결과가 난수나 마찬가지이고 암호화도 이와 크게 다르지 않을 테니까.. 하지만 암호화, 해시, 난수는 전문적으로 들어가면 지향하는 목표가 다른 분야라고 한다.

말이 나왔으니 말인데.. 임의로 생성 가능한 문자열과 이를 hash한 문자열을 혼합하면.. 올바른 번호와 잘못된 번호의 구분이 존재하는 일련번호(serial key) 체계도 생성할 수 있다.
간단하게는 우리나라 주민등록번호가 대표적인 예이다. 검증용으로 쓰이는 마지막 자리 숫자가 hash 함수의 결과값이니까 말이다.

소프트웨어에서 불법 복제를 감지하고 예방하기 위해 발급되는 제품 key도 다 이런 원리로 발급된다. 상업용 소프트웨어야 처음부터 고정된 시리얼 키가 제품 패키지에 들어있으니 사용자 이름과 무관하겠지만, 셰어웨어 등록판을 생성하는 시리얼 키는 사용자의 이름도 공식에 응당 반영된다.

규칙에 어긋난 잘못된 문자열을 입력하면 해당 제품의 설치 프로그램은 에러 메시지를 내뱉으면서 다음 단계로 진행을 하지 않을 것이다. key의 생성 규칙은 그 제품 개발사의 중대한 영업 기밀이다.

하지만 이렇게 수학적인 방법, 소프트웨어적인 방법은 역공학을 통해서 뚫리기도 너무 쉽다. 암호학에서 알고리즘이 아니라 key만 믿어야 한다고 괜히 강조하는 게 아니다.
옛날에 불법 복제 어둠의 경로를 가 보면 도대체 어떻게 알아냈는지 특정 소프트웨어의 제품 key를 생성해 주는 툴이 버젓이 굴러다니곤 했다. 그렇기 때문에 요즘은 이 제품 key가 실제 구매자가 사용하는지의 여부를 제품 개발사 서버로부터 일일이 추가로 확인받곤 한다.

설계 차원에서 결함이 있지 않은 안전한 암호화 알고리즘이라면 key 없이 복호화를 하는 방법이 존재하지 않는다. 그렇기 때문에 이런 암호문은 그냥 a부터 z까지 모든 글자 조합을 무식하게 일일이 대입하는 brute force 방식으로만 풀 수 있다.
패스워드의 길이가 한 글자 늘어날 때마다 공격에 소요되는 시간이 수십 배씩 기하급수적으로 늘어나니.. 이 시간 덕분에 오늘도 인간의 컴퓨팅 세계는 딱히 금융 사고나 개인정보 유출 사고가 별로 없이 안전하게 돌아가고 있다.

하지만 컴퓨터의 계산 성능은 하루가 다르게 향상되고 있고 암호 공격은 병렬화에도 아주 유리한 분야이다.
이런 brute force 공격을 저지하기 위해 요즘 암호를 입력받는 프로그램, 웹사이트 등에서는 암호가 틀릴 때마다 수 초씩 딜레이를 일부러 주고, n번 이상 암호를 틀리면 계정을 정지시키는 조치까지 취한다. 그 어떤 암호 시스템도 하나하나 다 대입해 보는 제일 무식하고 원천적인 전수조사에는 언젠가 뚫릴 수밖에 없는데.. key가 너무 허술하게 만들어져 있으면 거기에 더욱 취약해질 것이다.

3. 공개 키 암호화

지금까지 key를 따로 받지 않는 대표값 변조(해싱), 그리고 key를 받는 대칭 키 암호화까지 얘기가 나왔다. 대칭 키 암호화는 입력 데이터를 받아들이는 방식에 따라 블록 암호화 내지 스트림 암호화로도 나뉘는데, AES/DES 같은 것들은 블록 암호화로 분류된다.
그런데 1970년대에는 이런 것과는 성격이 근본적으로 다른 완전히 새로운 암호화 분야가 개척되었다. 바로 대칭이 아닌 비대칭, 또는 공개 키 알고리즘이라는 개념이 제안되고 개발된 것이다.

왜냐하면 제아무리 날고 기는 복잡 정교한 암호화 알고리즘이 있다 하더라도 그것들은 발신자와 수신자가 모두 동일한 key를 알아야 하고 보안을 위해서는 수시로 교체해야 하고, 그 바뀐 key를 모든 구성원에게 전해 줘야 한다는 원천적인 한계와 위험성이 있기 때문이다. 군대에서 새 암구호를 어떤 절차를 통해 전파하는지를 생각해 보자.

key를 주고 받는 건 암호라는 걸 운용하는 모든 조직이 감내해야 하는 어쩔 수 없는 숙명인 것 같다. 하지만 암호화 때 사용되는 key와 복호화 때 사용되는 key가 다르고(비대칭), 전자는 마음대로 주변에 공개해도 되는(공개 키) 알고리즘은 이런 한계를 극복해 준다. 이런 마법 같은 일이 어떻게 가능할까..?

이 암호화는 수학적 비가역성 내지 난해함에 근거를 두고 있다. 자릿수가 많은 두 수를 곱하는 건 사람의 입장에서 몹시 고된 노가다이겠지만.. 역으로 이미 곱해진 듣보잡 수를 아예 소인수분해 하는 것은.. 뭐 차원이 다를 정도로 난감하고 시간이 오래 걸리는 일일 것이다. 페르마 수 같은 게 n이 조금만 커져도 합성수인 것만 알려졌을 뿐 완전한 소인수분해가 안 된 놈들이 부지기수인 게 이 때문이다.

공개 키 암호화 중 하나로 유명한 RSA는 수십 자리 이상의 엄청나게 큰 소수 둘을 골라서 이 원천으로부터 공개 key와 개인 key pair를 만들어 낸다. 즉, 처음부터 동일한 원천으로부터 두 key 쌍이 계산에 의해 만들어진다는 것이다. 비록 계산이긴 해도 한 key로부터 나머지 key를 역으로 유도하는 것은 무진장 어렵고 시간이 많이 걸린다.

이런 공개 키 암호화는 제약이 크다. 앞서 살펴봤던 재래식(?) 암호화처럼 임의의 메모리 블록이나 문자열을 취급하지는 못하며, 평문과 key, 암호화 결과 모두 그냥 숫자 달랑 하나일 뿐이다. 숫자에다가 문자열에 맞먹는 정보를 담으려면 그 수가 엄청나게 커져야 한다.
그리고 얘는 그런 주제에 계산량이 차원이 다르게 많다. 컴퓨터 친화적인 XOR이나 비트 회전 같은 게 아니라 거대 정수에 대한 산술 연산을 무진장 해야 하기 때문이다.

그렇기 때문에 이런 암호화 알고리즘은 재래식 블록 암호화처럼 몇 MB짜리 데이터 자체를 암호화하는 단순한 용도로 쓸 수 없다.
그 대신 재래식 암호화 알고리즘에다가 돌릴 짤막한 key만을 암호화하는 용도로 병행해서 쓰인다. 멀티스레드 프로그램에서 TLS 슬롯은 공간이 한정되어 있으니 거기에다가 생 데이터를 몽땅 저장하는 게 아니라 그냥 포인터만 저장해 놓는 것과 비슷한 이치랄까..? 마치 손실 압축과 비손실 압축만큼이나 고유한 용도가 있는 셈이다.

https 보안 사이트 내지 인터넷 뱅킹에서 로그인을 할 때 시간이 0.n초나마 랙이 있는 이유는 네트워크 트래픽 때문이 아니라 순수하게 계산 때문에 그렇다. 그때마다 암호 해독을 위해 OpenSSL에 구현된 BigInt 같은 라이브러리의 코드가 실행되면서 큰 수 연산과 값 비교가 행해진다고 생각하면 되겠다.

공개 키 암호화로서 RSA가 아주 유명하지만 이런 소수와 소인수분해 말고 타원곡선이나 이산로그 같은 다른 어려운 연산에 기반을 둔 알고리즘도 있다.
이 암호화 기술은 인터넷의 발달과 더불어 우리의 생활을 크게 바꿔 놓았다. key를 위험하게 통째로 주고받지 않아도 되는 암호화 덕분에 인터넷 상으로 금융 거래도 할 수 있게 되고 디지털 서명, 인증서라는 것도 존재할 수 있게 됐기 때문이다!

보통은 공개 key로 암호화를 해서 개인 key로 복호화를 하는데, 반대로 어떤 문서를 개인 key로 암호화하고 공개 key로 복호화하게 하면 된다. 그러면 이 문서는 누구나 열람은 가능하지만, 만든 사람은 그 공개 key에 대응하는 개인 key의 소유자밖에 없다는 것이 입증된다. 놀랍지 않은가?

hash가 어떤 데이터가 원본과 동일한지 무결성을 보장한다면, 공개 key 암호화는 어떤 데이터가 반드시 특정인으로부터 만들어졌고 변조되지 않았다는 것을 보장할 수 있다.
도장이고 자필 서명이고 자물쇠와 열쇠 같은 모든 실물은 조작이 가능하며 컴퓨터야 뭐 0과 1 무엇이건 해킹이 가능한 디지털 세상이다. 이런 바닥에서 믿을 것은 수학적인 비가역성밖에 없어진다는 것이다.

이 세상에서 생명을 다루는 의료 다음으로 중요하고 착오가 절대로 없어야 하는 크리티컬한 임무는 아무래도 돈 거래, 기밀 거래일 텐데, 이 정도 기반은 갖춰진 뒤에야 인터넷이 안전하게 돌아가고 있다. 그러나 초창기에 인터넷은 기반 프로토콜 차원에서 이런 암호화 알고리즘이 제공되지 않았다.

그렇기 때문에 전자상거래나 인터넷 뱅킹을 남보다 일찍 서둘러 구현하려면 특정 운영체제에 종속적인 온갖 비표준 편법 기술이 동원돼야 했다. 오죽했으면 2000년대 초에 SEED 같은 알고리즘까지 개발한 걸 보면 공개 키뿐만 아니라 대칭 키 암호화까지 모두 허술했던가 보다.
허나 이게 바로 우리나라 특유의 IE + ActiveX 의존이라는 독이 되어서 오늘에 이르고 있다. 일본이 일찍부터 철도 왕국이 되긴 했지만 협궤 때문에 발목 잡힌 것과 비슷한 현상이랄까..?

이상이다. 인증서가 어떻고 공개 키, 개인 키, 디지털 서명 이러는 바닥은 통상적인 hash나 블록 암호화와는 영역이 상당히 다르다는 것, 그리고 이런 공개 키 암호화 덕분에 인터넷 보안 수준의 차원이 달라졌다는 것이 핵심이다. 이 바닥도 날고 기는 괴수들이 너무 많다..;;

Posted by 사무엘

2021/03/31 08:33 2021/03/31 08:33
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1871

1. 프로그래밍 용어· 명칭과 타 분야 비교

  • 2의 제곱근 vs 제곱근(루트) 2: 어순의 차이로 의미가 달라지는 new operator/operator new, 함수 템플릿 vs 템플릿 함수 같은 C++ 용어와 상황이 비슷하다. 특히 다들 전자가 후자를 포함하는 편이기도 하다.
  • 전철역에서 논현, 신길, 양평: namespace가 필요한 좋은 예이다..;;; (1) 얘들은 고유명사 지명이 지역간에 충돌하는 사례이고 (2) 월드컵경기장이나 시청은 보통명사 시설명이 충돌하는 예이다.
    (3) 수색, 광명, 신촌 같은 건 지역이 아니라 일반열차 역과 지하철역이 충돌하는 경우이다. 그리고 (4) '회송'은 역의 이름으로는 쓰이지 않는(쓰일 수 없는) reserved word 정도에 대응할 것이다.
  • 배의 명칭 A급 B함/호(포항급 초계함 천안함, 올림픽급 타이타닉 호, 베헤모스급 배틀크루저 히페리온): 프로그래밍으로 치면 A는 타입명이고 B는 변수명이다.

2. 마침표와 세미콜론

우리는 년월일 날짜를 간략하게 표기할 때 2020. 10. 5. 같은 식으로 숫자 뒤에 마침표를 찍곤 한다. 이때 일을 나타내는 마지막 마침표도 생략하지 말고 반드시 다 찍는 것이 한글 맞춤법에 규정된 원칙이다. 각각의 점이 년, 월, 일을 나타내기 때문이다.

이는 마치 파스칼 언어에서는 세미콜론이 문장의 구분자(separator)인 반면, C/C++에서는 문장의 종결자(terminator)인 것과 비슷하다.
파스칼에서는 end나 else 직전에 등장하는 구문의 끝에 ; 이 생략되지만 C/C++은 그렇지 않다. 날짜 숫자의 뒤에다 찍는 .는 파스칼이 아닌 C/C++의 세미콜론과 같은 성격이라고 생각해야 한다.

3. 병렬화

같은 용량의 데이터가 있을 때 압축을 하는 것은 압축을 푸는 것보다 계산량이 훨씬 더 많은 어려운 작업임이 주지의 사실이다.
그 대신, 압축 "하기"는 CPU 멀티코어를 활용해서 속도를 쭉쭉 끌어올릴 수도 있는 반면, "풀기"는 그런 병렬화는 안 되고 그냥 단일 코어에서 linear한 작업에 의존할 수밖에 없다. 기껏해야 풀어야 하는 압축 파일 자체가 여러 개일 때에나 여러 CPU에다가 던져줄 수 있을 것이다.

C/C++ 파일을 빌드하는 절차도 이와 비슷해 보인다. '컴파일'은 아무래도 분산 처리와 병렬화가 가능하지만, 모든 결과물이 하나로 집약되는 '링크'는 그게 불가능한 최종 병목이 될 수밖에 없다.

4. 버퍼의 크기

일상적으로 무슨 모임 같은 데서(본인의 경우는 교회에서 청년부 모임 같은..) 인원수대로 프린트물이나 간식 같은 것을 준비해야 할 때가 있다. 평소에 모임 참석자가 얼마 정도 되는지에 대한 대략의 데이터는 있지만 딱 정확하게 몇 명인지는 알 수 없을 때는 준비물을 몇 개 정도 챙겨야 너무 남거나 모자라는 일이 없이 최대한 딱 맞을 수 있을까?

이건 나름 통계적인 노하우가 필요한 일이다. 가끔 모자라는 일이 발생해도 괜찮은지, 아니면 모자라는 일은 절대 없어야 하는지에 따라서도 전략이 달라진다. 프로그래밍으로 치면 static한 배열의 크기를 잡는 것과 매우 비슷해 보인다는 생각을 본인은 오래 전부터 했다. ㅎㅎ

문자열 클래스의 경우, 사소한 문자열까지 늘 동적 메모리를 할당하는 건 번거로우니 자체적으로 자그마한 배열도 갖고 있고, 그 배열 크기를 초월하는 긴 문자열을 배당할 때만 동적 메모리를 사용하게 하는 구현체도 존재한다. C++의 표준 string 클래스도 반드시 저렇게 동작해야 한다는 조건은 없지만 대체로 이런 식으로 구현된 걸로 본인은 알고 있다.

이런 것 말고도

  • 건물을 지을 때 이 정도 건물에서는 화장실에 변개를 몇 개 설치하는 게 좋을까?
  • 엘리베이터는 어느 정도 크기로 몇 개 설치하는 게 좋을까?
  • 이 정도 도로의 교차로 내지 횡단보도에서는 신호 주기를 어느 정도로 주는 게 좋을까?

같은 문제도 치밀한 공학 및 통계 계산의 산물이지 싶다. 동시에 사용하는 사람의 수가 최대인 시간대에 대기 시간이 너무 길어지지 않게 하는 한편으로, 나머지 한산한 시간대에 시설들이 사용자 없이 놀면서 발생하는 비효율도 최소화해야 하기 때문이다.

5. 훅킹

훅(hook) 내지 훅킹이란 컴퓨터 소프트웨어가 돌아가는 과정을 몰래 들여다보고 필요하면 변조도 하는 메커니즘을 말한다. 훅킹은 대체로 시스템 프로그래밍 분야에 속하며 꽤 강력한 고급 테크닉으로 간주된다.

(1) 메시지 훅
Windows에는 SetWindowsHookEx라는 엄청난 함수가 있어서 시스템과 응용 프로그램 사이에서 오가는 메시지들을 매우 수월하게 들여다볼 수 있다. 그러니 Spy++ 같은 프로그램을 만들 수 있다.
권한 문제만 없다면 심지어 다른 프로그램의 메시지를 들여다볼 수도 있다. 이 경우, 훅 프로시저가 내 프로세스가 아니라 그 메시지를 받은 프로세스의 문맥에서 실행된다는 점을 주의할 것. 32비트와 64비트별로 DLL을 따로 만들고, 프로세스 간의 통신 같은 잡다한 수고만 좀 해 주면 된다.

(2) API 훅
다른 프로그램이 그냥 기계어 수준에서 운영체제의 특정 함수를 호출하는 것을 감지하고, 그 함수 대신 내가 심은 함수가 호출되게 할 수 있다. C 언어 형태의 클래식 API가 제일 쉽고, COM도 결국은 CoCreateInstance 같은 함수를 훅킹하면 이론적으로 가능하다. 실행되는 기계어 코드를 변조하는 게 아니라 import 섹션 주소를 변조하는 고전적인 테크닉이 있다.

16비트 시절에는 API 훅을 시스템 전체에다 걸어서 운영체제 외형을 통째로 마개조 할 수도 있었지만 32비트 이후부터는 그 정도까지는 어렵다. 다만, 시스템 전체에다가 설치한 메시지 훅과 CreateRemoteThread 등 다른 어려운 테크닉들과 연계하면 API 훅도 어느 정도 global하게 설치하는 게 가능은 하다.
과거에 한컴사전이 GDI 그래픽 API에다가 훅을 걸어서 단어 자동 인식 기능을 제공했던 적이 있다. 마우스 포인터 주변의 화면 캡처 + 필기 인식이 아니다~!

(3) 패킷 훅
심지어 시스템 전체에서 오고 가는 네트워크 패킷을 모니터링 할 수도 있다. 이게 기술적으로 가능하니까 packet sniffer이라고 불리는 유틸리티들도 존재 가능할 것이다. 이에 대해서는 본인도 더 아는 게 없다.
macOS는 Windows와 달리 메시지 훅이고 API 훅 같은 건 존재하지 않는 것으로 본인은 알고 있다. 하지만 macOS라도 패킷 모니터링은 아마 가능할 것이다.

packet sniffer이라든가 심지어 VPN 툴 같은 건 어떤 API를 써서 어떻게 만드나 모르겠다. 신기한 물건이다.

Posted by 사무엘

2021/02/01 08:34 2021/02/01 08:34
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1849

1. 그레이스케일

이건 이미지를 흑백 형태로 바꾸는 것이 핵심이다. 색을 구성하는 정보량의 차원이 줄어들고(3차원 → 1차원으로) 결과적으로 전체 색깔수가 줄어들기는 하지만(수천~수십만 종류의 색상 → 256단계의 회색), 그래도 아예 B&W 단색으로 바꾸는 건 아니다.
각 픽셀들은 색상과 채도가 제거되고 명도만 남아서 흑부터 백 사이에 다양한 명도의 회색으로 기계적으로 바뀐다. 같은 색의 픽셀이 인접 픽셀이 무엇이냐에 따라서 다르게 바뀐다거나 하지는 않는다.

그런데 그레이스케일 공식이 딱 하나만 존재하는 게 아니다. RGB 세 성분의 산술평균을 주면 될 것 같지만, 그렇게 그레이스케일을 하면 그림이 굉장히 칙칙하고 탁하게 보이게 된다.

똑같이 최대값 255를 주더라도 빨강(255,0,0), 초록(0,255,0), 파랑(0,0,255) 각 색별로 사람이 인지하는 명도는 서로 일치하지 않는다. VGA 16색 팔레트를 다뤄 본 사람이라면, 밝은 빨강이나 밝은 파랑을 바탕으로는 흰 글자가 어울리지만, 밝은 초록은 그 자체가 너무 밝아서 흰 글자가 어울리지 않는다는 것을 알 것이다.

사용자 삽입 이미지

그러니 공평하게 33, 33, 33씩 가중치를 주는 게 아니라 거의 30, 60, 10에 가깝게.. 초록에 가중치를 제일 많이 부여하고 파랑에는 가중치를 아주 짜게 주는 것이 자연스럽다고 한다.

이런 공식은 누가 언제 고안했으며 무슨 물리 상수처럼 측정 가능한 과학적인 근거가 있는지 궁금하다. 옛날에 흑백 사진을 찍으면 색깔이 딱 저 공식에 근거한 밝기의 grayscale로 바뀌었던가?
본인은 저 그레이스케일 공식을 태어나서 처음으로 접한 곳이 아마 QBasic 내지 QuickBasic의 컬러/팔레트 관련 명령어의 도움말이었지 싶다.

2. 디더링

이건 그레이스케일과 달리, 색깔수를 확 줄이는 것이 핵심이다. 그 대신 반드시 단색 흑백이어야 할 필요가 없다. 가령, 트루컬러를 16색으로 줄이는 것이라면 RGB 원색이 살아 있는 컬러라 할지라도 디더링의 범주에 든다. 이런 것처럼 말이다.

사용자 삽입 이미지

디더링에서는 어떤 원색을 직통으로 표현할 수 없을 때 근처의 비슷한 여러 색들을 고르게 흩뿌려서 원색과 비슷한 색감을 나타낸다. 팔레트까지 임의로 지정이 가능하다면 256색 정도만 돼도 생각보다 그럴싸하게 원래 이미지를 재현할 수 있다. 그런 게 아니라 그냥 흑백 단색 디더링이라면 아까 그레이스케일처럼 어떤 공식에 근거해서 명도를 추출할지를 먼저 결정해야 할 것이다.

0부터 1까지 가중치별로 점을 골고루 균등하게 뿌리는 공식은 이미 다 만들어져 있다. 이건 Ordered dither이라고 불리며, 보통 8*8 크기의 64단계 격자가 쓰이는 편이다. 그 구체적인 방식에 대해서는 수 년 전에 이 블로그에서 이미 다룬 적이 있다.

그레이스케일은 RGB(0.4,0.2,0.6)라는 색을 0.3이라는 명도로 바꾸는 것에 해당하는 기술이고, 단색 디더링은 이 색이 0.3, 0.3, 0.3 … 이렇게 쭉 이어지는 것을 1, 0, 0, 1, 0, 0 이런 식으로 이산적으로 표현하는 것과 같다.
그런데 원색을 기계적으로 이런 격자로 치환하기만 하면 보기가 생각보다 굉장히 좋지 않다.

원래 0.3을 표현해야 하는데 지금 지점에서 부득이하게 1을 찍어 버렸다면 0.7이라는 오차의 여파를 인접 픽셀에다가 떠넘겨서 거기서 계속해서 감당하게 해야 한다. 즉, 그레이스케일은 그냥 인접 픽셀을 신경 쓰지 않고 픽셀 대 픽셀 변환만 했지만 디더링은 그렇지 않다. ordered dither 내지 더 단순무식한 nearest color 찍기 신공이 아니라면.. 이전 픽셀에서 발생한 오차를 수습하는 error diffusion을 동원해야 부드러운 결과물이 나온다.

사용자 삽입 이미지

그런 지능적인(?) 디더링 알고리즘은 196~70년대에 컴퓨터그래픽이라는 분야가 등장한 초창기부터 연구되어 왔으며, 고안자의 이름을 따서 Floyd-Steinberg, Burks, Stucki 같은 알고리즘이 있다. 이미지를 다루는 사람이라면 포토샵 같은 그래픽 편집 프로그램에서 저런 명칭들을 본 적이 있을 것이다.
이런 알고리즘들은 ordered dither와 달리, 점들이 일정 간격으로 산술· 기계적으로 단순 투박하게 찍힌 게 아니라 뭔가 한땀 한땀 손으로 입력된 것 같고 부드러운 느낌이 든다. 그리고 무엇보다 색깔이 크게 바뀌는 경계 영역이 훨씬 더 선명하다.

3. 하프톤 (망점)

그레이스케일이 색깔 표현에 제약이 없는 아날로그 영상물(특히 흑백 필름 사진) 같은 느낌이 나고, 디더링은 초기에 해상도와 색상이 부족했던 디지털 영상과 관계가 있다면.. 하프톤은 색상이 부족한 대신 해상도가 높은 '인쇄물'과 관계가 깊은 기법이다.

사용자 삽입 이미지

하프톤은.. 일정 간격으로 망점을 두두두둑.. 찍고, 그 망점의 크기/굵기만으로 명도를 조절한다. 깨알같은 점들의 양과 배치 방식을 고민하는 통상적인 디더링과는 문제 접근 방식이 약간 다르다.
출력물의 해상도가 영 시원찮은 데서 하프톤을 동원하면 망점이 너무 커서 눈에 거슬릴 수 있다. 회색을 만들려고 했는데 하양과 검정이 그대로 눈에 띄게 된다는 것이다.

그러나 아무 인쇄물에서나 흑백이든 컬러든 문자 말고 음영(색깔 배경)이나 사진을 하나 보시기 바란다. 전부 촘촘한 점들로 구성돼 있다.
컴퓨터용 프린터나 전문 출판물 인쇄기들이 무슨 수십~수백 종에 달하는 물감을 갖고 있지는 않다. 잉크는 3원색에다가 검정 이렇게 4개만 갖고 있고, 나머지 색은 전부 얘들을 적절한 배율로 섞은 망점의 조합만으로 표현한다.

컴퓨터에서 보는 사진 이미지를 프린터로 출력하기 위해서는 가산 혼합 기반인 RGB 방식의 색을 감산 혼합 CMYK 방식으로 변환하고, 색 축별 망점 배합을 계산해야 할 것이다. 이건 디더링과는 다른 영역이다. 프린터 드라이버가 하는 일 중의 하나가 이것이며, 전문적인 사진이나 출판 프로그램 역시 색 축별로 인쇄 형태의 저수준 데이터를 export하는 기능을 제공한다.

그래도 요즘은 사진조차 필름 현상이 아니라 디지털 카메라로 찍은 뒤에 포샵질을 하고 고급 인화지에다 '인쇄'해서 만들어 내는 세상이니.. 컬러 인쇄 기술도 예전보다 굉장히 많이 좋아지고 저렴해졌다. 1990년대까지만 해도 가정용으로 컬러 레이저 프린터라는 걸 생각이나 할 수 있었겠는가?

유니코드 문자 중에 U+2591 ~ U+2593은 단계별 음영을 나타낸다.
굴림· 바탕 같은 통상적인 Windows 글꼴은 이를 하프톤 형태로 표현한 반면, 함초롬바탕은 디더링 형태로 표현했음을 알 수 있다.
윤곽선 글꼴의 래스터라이즈 방식의 특성상 디더링보다는 하프톤이 부담이 덜하기도 하다. 윤곽선 글꼴은 뭔가 덩어리· 군더더기가 늘어날수록 출력 성능이 급격히 떨어지기 때문이다. 그래서 매끄러운 곡선 말고 오돌토돌, 쥐 파먹은 효과 같은 걸 표현한 글꼴은 크기도 크고 처리하기 버거운 편이다.

사용자 삽입 이미지

이상이다. 이 사이트에도 그레이스케일, ordered 디더링, 기타 휴리스틱-_- 디더링, 그리고 하프톤까지 다양한 사례가 소개되어 있으니 관심 있는 분은 참고하시기 바란다.

우리가 사는 공간이 3차원일 뿐만 아니라, 시각 정보의 기본 단위인 색깔이라는 것도 어떤 방식으로 분류하든지 결국 3개의 독립된 축으로 귀착된다는 게 신기하지 않은가? 신학에서는 이게 하나님의 속성인 삼위일체와 관계가 있다고도 말하는데, 그건 뭐 결과론적인 해석이며 물증이라기보다는 심증 수준에서 만족해야 하지 싶다.

음악에서는 음계, 음정, 화성학 같은 이론이 수학과 연결되어 오래 전부터 연구돼 왔다.
그럼 일명 color theory라고 불리는 분야는 언제부터 누구에 의해 연구돼 왔을까? RGB, CMY, HSL 사이를 변환하는 공식 같은 것 말이다. 더디링은 컴퓨터그래픽 영역이겠지만 순수하게 색에 대한 수학적인 분석은 미술과 전산학 어디에도 딱 떨어지지 않아 보인다. 내가 알기로는 전파로 영상을 주고 받는 텔레비전 기술이 개발된 20세기 초쯤에야 이런 분야가 개척됐다.

본인이 색과 관련하여 감이 오지 않고 아직도 좀 알쏭달쏭한 걸 얘기하면서 글을 맺도록 하겠다.
모니터 화면 같은 건 그 본질이 빛이며, 빛은 색을 태생적으로 지니고 있다. 그러나 다른 세상 만물들, 특히 인쇄물은 색만 있지 빛은 지니고 있지 않다. 조명을 받아야만 자기 색을 비춰 보일 수 있다.

빛은 원색이 RGB라고 여겨지며, 섞일수록 더 밝아져서 최종적으로 white에 도달하는 ‘가산 혼합’을 한다. 그러나 빛이 없는 나머지 사물의 색들은 섞일수록 더 어두워져서 최종적으로 black에 도달하는 ‘감산 혼합’을 한다. 이 정도는 이미 초등학교 미술 시간에 배운다.

다만, 빛의 3축과 색의 3축은 노랑/초록 말고 빨강/파랑 부분도 서로 미묘하게 차이가 난다는 것부터는 고등교육 이상의 영역으로 보인다. 본인도 그걸 대학 이후에나 접했기 때문이다.
왜 그런 차이가 나는지에 대해서는 논외로 하더라도, 빛이건 색이건 두 색상을 물리적으로 섞지는 말고 디더링 하듯이 오밀조밀 가까이 배치시켜 놓으면 멀리서 볼 때 정확하게 어떤 혼색이 나타날까? 이걸 잘 모르겠다.

이건 빛이든 인쇄된 색이든 차이가 없어야 하지 않을까? 그리고 만약 차이가 없다면 그 결과는 감산 혼합이나 가산 혼합 중 어느 것과도 정확하게 일치하지 않을 것이다. 그 관계가 무엇일까? 마치 산술/기하/조화 평균처럼 서로 비슷하면서도 미묘하게 다른 결과가 나오지 않을까 싶다.

이해를 돕기 위해 아래 그림을 살펴보자. 반드시 확대/축소 왜곡되지 않은 원래 크기 형태로 볼 것!! (화면 확대 배율도 96DPI/100%로 맞춰야 함)
왼쪽은 그냥 RGB(0,0,0) 검정과 RGB(255,255,255) 하양을 1:1로 섞은 것이고, 오른쪽은 RGB(0,0,255) 파랑과 RGB(0,255,0) 초록을 섞어서 청록을 만든 것이다. 여기서 섞었다는 것은 픽셀 디더링을 말한다. 그리고 우측 하단에는 본인이 보기에 이들과 가장 비슷하다고 생각하는 순색(solid color)을 칠해 보았다.

사용자 삽입 이미지

255짜리 순색 파랑 255짜리 순색 초록을 섞어서 가산 혼합이 됐다면 RGB(0,255,255) 밝은 cyan 청록이 나타나야 하겠지만 실제로는 전혀 그렇게 되지 않았다. 그런데 그렇다고 해서 평균인 128짜리의 훨씬 어두운 색이 된 것도 아니다.
본인이 보기에 가장 비슷한 순색의 명도는 대략 180~184 정도이다. 생각보다 제법 밝다. 아.. 그래서 옛날에 VGA 팔레트도 어두운 색을 무식하게 128로 지정하지 않고 170 정도의 값을 줬는가 싶은 생각이 든다. 다른 색과 섞었을 때 재현 가능한 색이 나오라고 말이다.

이런 현상을 설명하기 위해 감마 보정 등 다른 어려운 이론들도 나온 게 아닌지 추측해 본다. 디더링 알고리즘이란 게 내가 생각했던 것보다 여러 단계로 나뉘고 더 어렵고 복잡할 수도 있겠다는 생각이 든다. 적절한 명도 추출하기, 적절히 대체색으로 분비하기, 오차 보정하기 등..;; 시각 디자인의 세계는 오묘하다.

Posted by 사무엘

2020/07/06 08:35 2020/07/06 08:35
, , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1770

원소가 0 아니면 1 두 종류밖에 없는 N*N 크기의 어느 정사각행렬 M(N)이 있다. N은 2의 거듭제곱 형태로만 가능하며, M(N)의 생성 규칙은 이러하다.

일단, 크기가 1인 M(1)은 그냥 {0} 하나뿐이다.
그 뒤, M(2*n)은 M(n)의 각 원소들에서 0과 1을 뒤바꾼 놈을 M(n)의 왼쪽과 위쪽, 그리고 좌측 상단에 총 3개 카피를 씌우는 형태로 생성된다. 그러므로 M(2)는

(1 1)
(1 0)

이 되며, 다음 M(4)는 쟤를 뒤바꾼 놈을 또 덧붙여서

(0 0 0 0)
(0 1 0 1)
(0 0 1 1)
(0 1 1 0)

이 된다. 생성 규칙이 이런 식이다.
이런 식으로 M(16), 더 나아가 M(256)을 그림으로 나타내면 이런 모양이 된다.

사용자 삽입 이미지

흐음. 무슨 프랙탈 같기도 하고..
이런 행렬은 고안자의 이름을 따서 Walsh 행렬이라고 부른다.
그런데, 모노크롬이나 16색을 어렵지 않게 접할 수 있던 옛날에 이런 무늬를 화면에다 깔아 놓으면 꽤 근사해 보였을 것 같다~!

이걸 갖고 더 재미있는 장난을 칠 수도 있다.
옛날옛적에 학교에서 전자· 컴공을 전공했던 분이라면 혹시 그레이 코드(gray code)라고 기억하는 분 계신가?
통상적인 2진법과 달리, 숫자가 1씩 증가할 때 언제나 1개의 비트만이 바뀌게 숫자를 특이하게 표현하는 방식이다. 즉, 01111이다가 10000으로 바뀌는 식의 격변이 없다는 것이다. 물론 이런 숫자 인코딩을 갖고 진지한 산술 연산을 할 수는 없지만, 다른 용도로 쓸모는 있다고 한다.

사용자 삽입 이미지

0 1 3 2 6 7 5 4 ...
난 저걸 봐도 비트를 flip하는 규칙 자체를 잘 모르겠다. 숫자가 커질수록 긴가민가 헷갈린다. 솔까말 이거 규칙을 찾는 걸로 IQ 테스트 문제를 내도 될 것 같다.
그런데 일반 숫자를 그에 상응하는 그레이 코드로 바꾸는 공식은 어이없을 정도로 간단하다. x^(x>>1), 다시 말해 자신의 절반값과 자신을 xor 하면 된다.

자, 여기서 끝이 아니다.
저 수열에서 각 숫자들을 구성하는 2진법 비트의 배열 순서를 뒤집어 보자. 10진법으로 치면 1024를 4201로 바꾸는 것과 같다.

비트를 shift나 rotate하는 게 아니라 reverse 하는 건 내가 알기로 왕도가 없다. 그냥 for문 돌려서 1비트씩 차근차근 처리하는 수밖에 없다. 마치 주어진 숫자를 2진법으로 표현했을 때 1의 개수를 구하는 것처럼 말이다.
비트 수가 고정돼 있고 속도가 무진장 빨라야 한다면 미리 계산된 테이블을 참조해야 할 것이다.

N=8이라면 비트 자릿수는 3일 테고.. 0 1 3 2는 2진법으로 각각 000, 001, 011, 010일 텐데,
이걸 뒤집으면 차례대로 000, 100, 110, 010.. 즉 0 4 6 2 ... 형태로 바뀐다. 이 정도면 완전 난수표 수준으로 숫자를 뒤섞은 거나 마찬가지로 보인다.

저 수열이 확보됐으면 그걸 토대로 기존 Walsh 행렬을 개조해 보자. 즉, 지금 줄 배열이 0 1 2 3 4..의 순인데, 그걸 0 4 6 2 ... 즉, 둘째 행(1)에다가 다섯째 행(4)을 집어넣고, 다음 행에다가 일곱째 행(6)을 넣는 식으로 순서를 바꾼다. 그러면..

사용자 삽입 이미지

이런 인상적인 모양의 무늬가 만들어진다. 이것도 크기별로 규칙성이 있다. 좌측 상단은 사각형이 큼직하고, 우측 하단으로 갈수록 픽셀이 조밀해진다.
얘는 여러 명칭으로 불리는데, 통상적으로는 Walsh 행렬의 Hadamard 변환이라고 불린다.
실제 저 행렬/테이블을 구하는 건 아무 프로그래밍 언어로나 10분이면 코딩 가능할 것이다. 참고로 비트 reverse 같은 난감한 동작 없이 저 행렬을 얻는 다른 계산법도 존재한다.

이런 무늬 행렬은 전자공학 신호 처리 쪽에서 쓰인다고는 하는데.. 난 그쪽을 전공하지 않아서 잘 모르겠다. 단지 0과 1만 갖고도 인간 두뇌의 잉여력이 얼마나 폭발할 수 있는지에 경이로움을 느낄 따름이다.

수 년 전에 디더링 패턴의 생성 규칙에 대해 글을 쓰고 나서 이런 재귀적인 무늬를 주제를 다룬 건 무척 오랜만이다. xor은 온갖 난수와 암호도 만들어 내는 이산수학과 정보 이론의 진수임이 틀림없어 보인다. 세상에 이런 게 있다는 걸 알게 됐다는 차원에서 간단히 글을 남기게 됐다.

(xor은 x-y 2차원 평면에다가 진리표를 나타냈을 때 0과 1 결과를 직선 하나로 단순 분류할 수 없는 유일한 연산이기도 하다. 일명 XOR 문제..;; 마치 특정 그래프에 대해 한붓그리기가 불가능한 것과 비슷한 인상을 주는데.. 과거에 한때는 퍼셉트론 무용론과 함께 AI 겨울을 야기한 이력이 있다.)

Posted by 사무엘

2020/06/09 08:35 2020/06/09 08:35
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1760

1. 완전히 새로운 알고리즘 분야

컴퓨터는 정말 대단한 기계이다.
정보의 최소 단위인 0과 1을 분간하고, 임의의 주소가 가리키는 메모리의 값을 읽거나 쓸 수 있고 프로그램의 실행 지점도 메모리의 값을 따라서 변경 가능하게 했더니(튜링 완전) 그야말로 무궁무진한 양의 정보를 가히 무궁무진한 방식으로 처리가 가능해졌다.

이런 이론적인 근간이 마련된 뒤에 반도체의 집적도가 더 올라가고 메모리와 속도가 더 올라가고 가격이 더 내려가는 건 그냥 시간 문제일 뿐이었다.
그런데.. 단순히 복잡한 계산이나 방대한 검색을 빠르게 해내는 것만으로 컴퓨터가 인간의 고유 영역을 완전히 침범하고 대체했다고 보기는 곤란하다. 그건 그냥 자동차가 인간보다 더 빠르고 중장비가 인간보다 더 힘센 것만큼이나, 기계가 인간의 역할을 일부 보조하고 확장하는 것일 뿐이다.

물론 단순히 동력과 관련된 분야는 말이나 소 같은 동물도 인간보다 약간이나마 더 우위에 있긴 했다. 그에 비해 정보 처리 분야는 자연계에 지금까지 인간의 라이벌 자체가 아예 존재한 적이 없었다는 차이가 있다.
그러나 인간도 속도가 느리고 개인의 능력이 부족할 뿐이지.. 많은 인원을 동원하고 많은 시간만 주어지면 기계적인 정보 처리 정도는 '유한한 시간' 안에 언젠가 다 할 수 있다. 일을 좀 빠르고 정확하게 수행하는 것만 갖고 '창의적이다', '인간을 닮았다'라고 평가해 주지는 않는다.

정렬과 검색에, 다이나믹이니 분할 정복이니 하는 최적해 구하기처럼 고전적인 분야에서 고전적인(?) 방법론을 동원하는 알고리즘은 이미 수십 년 전에 다 연구되어서 깔끔한 결과물이 나왔다. 그런 건 이미 대학교 학부 수준의 전산학에서 다 다뤄지는 지경이 됐으며, 정보 올림피아드라도 준비하는 친구라면 아예 중등교육 수준에서 접하게 됐다.

그런데 현실에서는 그렇게 깔끔하게 떨어지지 않는 더 복잡하고 난해한 문제를 풀어야 한다. 깔끔하게만 접근했다가는 시간 복잡도가 NP-hard급이 되어 도저히 감당할 수 없는 문제를 적당히 타협하여 풀어야 한다.
중· 고등학교의 고전역학 문제에서는 "공기의 저항은 무시한다" 단서가 붙지만, 대학교에 가서는 그런 것까지 다 고려해야 하는 것처럼 말이다.

대수적으로 답을 구할 수 없는 문제에 대해 근사치를 효율적으로 구하기 위해 수치해석이라는 기법이 등장했듯, 전산학에도 각종 휴리스틱과 근사 알고리즘이라는 게 존재한다. 압축 알고리즘으로 치면 무손실이 아닌 손실 분야인 셈이다. 구체적인 건 학부 수준을 넘어 대학원에 소속된 별도의 연구실에서 다룰 정도로 난해하다.

그런데.. 이런 것들은 여전히 사람이 범접하지 못하는 분량의 계산 문제를 최대한 빠르게 효율적으로 푸는 방법에 대한 연구이다. 그런 것 말고 사람은 간단히 하는데 컴퓨터에게는 굉장히 난해해 보이는 일이 있다.
컴퓨터로 하여금 텍스트나 음성 형태의 인간의 자연어를 알아듣고 타 언어로 번역한다거나, 그림으로부터 글자 같은 정보를 알아보게 할 수 없을까?
컴퓨터를 바둑· 장기· 오목 같은 게임의 고수로 키울 수 없을까?

이건 단순히 TSP(순회하는 세일즈맨 문제)를 더 그럴싸한 가성비로 다항식 시간 만에 푸는 것과는 분야가 다르다.
저런 걸 기계가 인간과 비슷한 속도와 정확도로만 해내더라도 굉장한 이득이다. 기계를 부리는 비용은 인간을 고용하는 인건비보다 넘사벽급으로 저렴한 데다, 기계는 인간 같은 감정 개입이 없고 지치지 않고 실수도 전혀 하지 않기 때문이다.

하물며 속도와 정확도가 인간 전문가의 능력을 능가하게 된다면 게임 끝이다. 기계적인 단순 노동력이나 판단력만을 요구하는 일자리는 사라지며, 인간은 기계가 대신할 수 없는 더 창의적이고 전문적인 일자리로 갈아타야 할 것이다.

2. 흑역사

소위 '인공지능'에 대한 연구는 진공관이니 트랜지스터니 하던 무려 1950년대 컴퓨터의 초창기 때부터 천조국의 날고 기는 수학자, 컴퓨터 공학자들에 의해 진행돼 왔다. 특히 세부 분야 중 하나로서 기계번역도 연구됐으며, 1954년에는 조지타운 대학교와 IBM이 공동 연구 개발한 기계번역 솔루션이 실제로 출시되기도 했다.

인류 역사상 최초로 기계번역이란 게 연구된 언어는 러시아어 → 영어이며, 이는 전적으로 냉전 덕분이다. 하긴, 2차 세계 대전 때는 번역이 아니라 암호를 해독하는 기계가 개발되긴 했었다. 적성국가들의 언어 중 일본어는 영어와의 기계번역을 연구하기에는 구조가 너무 이질적이고 어려웠을 것이다.

그래도 인간 번역가가 아닌 컴퓨터가 러시아어 텍스트로부터 영어 텍스트를 허접하게나마 뱉어 내자 학계와 업계는 흥분했다. 이런 식으로 조금만 더 연구하면 컴퓨터가 금방이라도 세계의 언어 장벽을 다 허물어 줄 것 같았다.

그때는 학자들이 자연어에 대해서 뭔가 순진 naive하게 원리 원칙대로 규칙 기반으로, 낭만적으로 접근하던 시절이었다. 인간의 언어도 무슨 프로그래밍 언어처럼 유한한 문법과 생성 규칙만으로 몽땅 다 100% 기술 가능하고 parse tree를 만들고 구문 분석이 가능할 거라고 생각했다. 물론 그 규칙이 간단하지는 않겠지만, 촘스키 같은 천재 언어학자 몇 명을 외계인과 함께 골방에다 갈아 넣고 며칠 열나게 고문하면 다 찾아낼 수 있을 것이고.. 그러면 언어의 기계 분석은 게임 끝이지 않겠냐 말이다.

궁극적으로는 전세계 모든 언어들의 교집합과 합집합 요소를 망라하는 중간(intermediate) 언어도 만들 수 있을 것이라고 생각했다. 세계 각국의 언어들을 그 중간 언어와 번역하는 시스템만 만들면 전세계 사통발달 언어 번역 시스템을 만들 수 있지 않겠는가? 이 정도 생각은 나조차도 한 적이 있다.

그랬으나.. 뚜껑을 열어 보니 영광은 거기서 끝이었다.
기계번역은 빵점 백지 상태에서 4, 50점짜리 답안을 내놓는 것까지는 금방 할 수 있었지만, 거기서 성적을 7, 80점짜리로라도 올려서 실용화 가능한 상품은 오랫동안 연구비를 아무리 투입해 줘도 선뜻 나오지 않았다.
인간의 언어라는 게 절대로 그렇게 호락호락 만만하지 않고 매우 불규칙하고 복잡한 구조라는 게 연구하면 연구할수록 드러났기 때문이다. 지금까지 사용한 연구 방법론 자체가 약발이 다하고 한계에 다다랐다.

이 때문에 1970년대에는 돈줄을 쥔 높으신 분들이 "인공지능"이란 건 밥값 못 하는 먹튀 사기 허상(hoax)일 뿐이라고 매우 비관적이고 보수적으로 생각하게 됐다. 컴퓨터는 그냥 계산기일 뿐, 그 돈으로 그냥 인간 번역가나 더 양성하고 말지..
이 단어 자체가 학계의 흑역사 급으로 금기시되어 버렸다. 인공지능이란 게 키워드로 들어간 논문은 저널에서 믿고 걸러냈으며, 관련 연구들의 연구비 지원도 모조리 끊길 정도였다.

이 현상을 학계에서는 제1차 AI 겨울(혹한기, 암흑기, 쇼크, 흑역사 등등...)이라고 부른다. 과거의 무슨 오일 쇼크 내지 게임 업계 아타리 쇼크처럼 말이다.
그렇게 고비를 겪었다가 더 발달된 연구 방법론으로 활로가 발견되고, 그러다가 또 2차 겨울을 극복한 뒤에야 요 근래는 인공지능의 중흥기가 찾아왔다고 여겨진다.

3. 문제는 데이터

지금은 기계번역이건, 게임 AI이건, 패턴인식이건 무엇이건.. 인공지능의 주재료는 규칙이 아니라 데이터이다.
기계번역 시스템을 개발하는데 언어학 문법 지식이 동원되지 않으며, 보드 게임 AI를 만드는데 통상적인 게임 규칙 기반의 알고리즘이 동원되지 않는다. 이상한 노릇이 아닐 수 없다.

그러는 게 아니라 요즘 인공지능이라는 것은 아이디어는 매우 간단하다. 기출문제와 정답을 무수히 많이, 인간이 상상할 수도 없을 정도로 많이 주입시켜 준 뒤, 이로부터 컴퓨터가 규칙성을 찾아내고 새로운 문제가 주어졌을 때 정답을 추론하게 하는 방법을 쓴다. 해결하고자 하는 문제를 기술하고 정답의 조건을 명시하고 알고리즘을 구현하는 걸 인간이 일일이 하는 게 아니라.. 그 자체를 컴퓨터가 알아서 하게 하는 것이다.

이것이 '기계학습', 그 이름도 유명한 machine learning이다. 이것이 이전에 인공지능을 구현하는 방법론이던 '전문가 시스템'을 대체했다. 이런 무지막지한 방법론이 적용 가능할 정도로 요즘 컴퓨터의 속도와 메모리가 매우 크게 향상된 덕분이다.

인간의 입장에서 기계학습을 시키는 방식은 지도(supervised learning) 또는 비지도 학습으로 나뉜다.
그리고 기계의 입장에서 학습(?)을 실제로 수행하는 방법으로는 인공 신경망, 앙상블, 확률(은닉 마르코프 모델), 경사/기울기 하강법 같은 여러 테크닉이 있는데, 기울기 하강법은 복잡한 선형 방정식을 푸는 심플렉스와 비슷하다는 느낌도 든다.

인공 신경망은 생물의 신경망이 동작하는 원리에서 착안하여 만들어진 기계학습 모델이라고는 하지만 당연히 실제 인간 뇌의 작동 방식에 비할 바는 못 된다.
MLP니 CNN이니 RNN이니 하는 신경망 용어들이 존재하며, 그리고 이 인공 신경망을 어떻게 하면 잘 갖고 놀 수 있을까 고민하는 연구 분야를 '딥 러닝'(심층학습)이라고 한다. 마치 네트워크 계층의 다양한 기술 용어만큼이나 AI에도 계층별로 다양한 기술 용어가 존재한다.

게임 AI라면 단순히 뭔가를 인식하고 분류만 하면 장땡인 게 아니라 뭔가 극한의 최적해를 찾아가야 할 텐데.. 이런 걸 학습시키는 건 딥 러닝의 영역이다. 알파고처럼 말이다. 그런데 알파고 하나가 지구상의 최고의 인간 바둑 기사를 이긴 것은 물론이고, 다른 재래식 알고리즘으로 십수 년간 개발되어 온 기존 바둑 AI들까지도 다 쳐발랐다니 무서운 일이 아닐 수 없다. 뭐, 개인용 PC 한 대만으로 그렇게 동작하는 건 아니긴 하지만 말이다.

오늘날 연구되고 있는 인공지능은 무작정 인간과 동급으로 생각하고 창조하는 기계는 당연히 아니고, 컴퓨터의 막강한 메모리와 계산 능력으로 지금까지 주어진 데이터를 토대로 새로운 상황에 대처하는 답안을 꽤 그럴싸하게 제시하는 '약한 인공지능'일 뿐이다.

쉽게 말해 "서당 개 삼 년이면 풍월 읊는다"처럼 말이다.
추리소설를 한 1000편쯤 읽고 나니 다른 새로운 추리 퀴즈에도 설정이 뻔히 보이고 답이 보인다.
드라마를 1000편쯤 보고 나니 비슷비슷한 드라마들은 스토리 전개가 어찌 될지 '안 봐도 비디오'처럼 된다. 그런 것 말이다.

그런데 저게 말처럼 쉬운 일인 건 아니다.
학습 대상인 무수한 텍스트· 이미지· 음성 데이터 내지 각종 게임 복기 데이터를 어떤 형태로 수치화해서 벡터 형태로 표현할 것인가?
그리고 '학습'이라는 걸 하는 동안 해당 AI 엔진의 내부에서는 구체적으로 무슨 계산 작업이 행해지는가?
컴파일러만 해도 결과물로 OBJ 파일이라는 게 생기는데, 그 내부적인 학습 상태는 어떤 형태로 표현되며, 이것만 따로 저장하고 불러오는 방법이 존재하는가? 본인은 AI알못이다 보니 전혀 모르겠다. ㅡ,.ㅡ;;

수천, 수만, 아니 그 이상 셀 수 없이 많은 숫자들로 이뤄진 벡터 I가 또 수없이 많이 있다고 치자. 이 숫자들은 현실 세계를 표현하는 미세한 자질을 표현한다.
그런데 어떤 블랙박스 함수 f가 있어서 f(I_1..n)에 대한 결과가 O_1..m라는 벡터 집합으로 나왔다고 한다.

컴퓨터는 이 I와 O 사이에서 규칙성을 찾아서 I에 대해 O와 최대한 비슷한 결과를 산출하는 함수 f를 구한다. 그러면 이제 임의의 다른 아무 input에 대해서도 수긍할 만한 출력 결과를 얻을 수 있을 것이다.
패턴 인식? 기계번역? 유사 작곡이나 창작? 현실에서 해결하려는 문제가 무엇이건 machine learning이 하는 일은 본질적으로 저걸로 요약된다. 내가 AI 쪽으로 아는 건 이게 전부이다.

지금은 TensorFlow 같은 범용적인 기계학습 엔진/라이브러리까지도 구글 같은 괴물 기업에 의해 오픈소스로 몽땅 풀려 있으며, 이걸 파이썬 같은 간편한 스크립트 언어로 곧장 돌려볼 수도 있는 세상이 됐다.
그런 라이브러리를 직접 개발하고 유지보수 하는 것까지는 바라지도 않는다. 방대한 현실 데이터를 수집해서 저기에다 적절하게 집어넣고, 이로부터 고객이 원하는 유의미한 추세나 분석 결과를 얻는 것만 해도 뭔가 프로그래밍 코딩과는 별개로 아무나 할 수 없는 전문 영역의 일이 돼 있다.

오늘날 AI 엔진의 연구를 위해서는 근간 이론이라 할 수 있는 선형대수학, 편미분, 확률 통계는 무조건 먹고 들어가야 된다. 엔진 코드를 직접 다루지 않고 쓰기만 하는 사람이라도 엔진이 어떤 원리로 돌아가는지 정도는 알아야 가장 적절한 방법론/알고리즘을 선택할 수 있을 테니 저런 것들을 맛보기 수준으로라도 알아야 할 것이다.

과거에 정보 사냥 대회가 있었던 것처럼 이제는 주어진 데이터로부터 새로운 문제를 잘 푸는 기계학습 모델을 설계하는 것이 경진대회의 아이템 내지 학교와 직장의 과제가 될 것으로 보인다. 컴퓨터가 할 수 있는 일이 더 늘어난다면 사람만이 할 수 있는 일은 그보다 더 수준 높고 추상적인 쪽으로 이동할 수밖에 없으니 말이다.

아무리 그래도 그렇지 자연어의 문법과 어휘 자체를 전혀 모르는 상태에서 데이터 학습만으로 댓글이 선플인지 악플인지를 기계가 분간할 수 있는 걸까? 그래도 클레멘타인 영화에 늘어선 댓글이 선플인지 악플인지 판단하려면 그에 대한 특별한 학습-_-;;이 필요할 것 같다.

그리고 변화무쌍 복잡한 필기체의 인식이 아니라, 그냥 자동차 번호판의 정향화된 숫자 내지 겨우 QR 코드의 인식 정도는.. '영상 처리 기술'의 영역이지, 저 정도의 거창하게 기계학습이니 뭐니 하는 인공지능의 영역은 아니다. 그건 구분해서 생각할 필요가 있다.

오래 전부터도 각종 전산학 알고리즘 용어를 검색할 때 종종 걸려 나오긴 했는데.. 국내 개인 사이트 중에 AIStudy라는 곳이 있다. 나모 웹에디터가 있던 시절부터 존재했던 정말 옛날 사이트이다. 그런데 운영자가 내 생각보다 굉장히 어린 친구이다. 정말 대단할 따름이다.
당연히 과학고-카이스트 라인이려나 생각했는데 그렇지는 않고 일반고-서울대 테크를 타 있다. 앞날에 건승을 빌어 본다.

Posted by 사무엘

2019/07/06 08:33 2019/07/06 08:33
, , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1637

요즘 컴퓨터 프로그램들은 상업용이라 해도 과거처럼 디스켓이나 씨디가 담긴 패키지 박스의 형태로 배포되는 경우가 거의 없어졌다. 바이너리의 배포 자체는 인터넷으로 하며, 패키지가 있다 해도 그 안에는 시리얼 번호 쪽지 정도나 달랑 들어있다. 그 뒤, 사용자가 구매한 카피 개수만큼만 프로그램을 동시에 구동하고 있는지, 사용 기한이 경과하지 않았는지 같은 인증은 인터넷으로 진행한다.

정식 사용자가 확인되지 않은 프로그램은 일단 평가판 모드로 동작한다. 정품 인증이 안 됐고 평가판 기간도 경과했다면 그 다음부터는 기능 제한 모드로 동작한다. 문서나 데이터를 취급하는 업무용 프로그램이라면 이제 문서를 편집할 수 없고 일종의 viewer 형태로만 동작하게 된다.

과거에는 소프트웨어 개발사들이 사용자에게 자기 제품의 기능을 자랑하는 한편으로 정품 구매에 대한 동기를 부여하기 위해 셰어웨어, 평가판, 데모 같은 것을 따로 배포하곤 했다.
하지만 지금은 인터넷이 워낙 발달한 덕분에.. subset 구분 없이 전체 제품을 통째로 뿌린다. 그 뒤 제품을 구입하고 해금 비밀번호/일련번호를 받은 사용자에게만 전체 기능이 제공되도록 한다. 아래아한글, MS Office 같은 거대한 프로그램들도 이제는 다 이런 식으로 동작하고 있다.

과거에는 그런 일련번호를 수학 공식을 기반으로 생성하곤 했다. 하지만 오프라인 환경에서 소프트웨어적인 알고리즘에만 의존하는 copy-protection은 역공학을 통해 뚫릴 수 있다. 마치 주민 등록 번호 자동 생성기처럼 말이다. 어둠의 경로를 개척하는 사람들이 이만저만 똘똘한 게 아니기 때문이다.

하지만 인터넷이 소프트웨어의 배포와 불법 복제만 편하게 만든 건 아니며, 까놓고 말해 개발사가 사용자의 사용 패턴을 미주알고주알 파악하는 것도 더 용이하게 만들었다. (뭐, 서버 유지 비용은 부담해야 하지만..) 제아무리 클라이언트 프로그램을 복제하고 뿌려도 온라인 게임을 돈 안 내고 즐기는 건 불가능하며, 스타크래프트 불법 복제 립버전으로 배틀넷 접속은 할 수 없다. 또한, 주민 등록 번호는 생성하더라도 방대한 DB에 일일이 접속하는 실명 인증은 수학 공식만으로 뚫을 수 없지 않던가? 그런 식으로 창과 방패는 발전하는 것 같다.

물론 업무용 프로그램들은 온라인 게임과 달리 업데이트 체크나 인증을 할 때만 인터넷에 접속하지, 나머지 동작은 전부 어차피 오프라인에서 행해지는 게 대부분이다. 그런 부류라면 실행 파일을 분석해서 인증을 시도하는 부분만 변조하고 크랙해 버릴 수도 있다. 파일이 전부 암호화되지 않고 헤더에만 암호가 기록돼 있다면 그 부분만 건너뛰어 버리면 되듯이 말이다. 그렇기 때문에 오프라인에서의 소프트웨어 보안도 예나 지금이나 여전히 필요하다.

이렇게 소프트웨어의 정품 사용 여부를 파악하기 위해서 반드시 필요한 절차가 있다. 바로 프로그램이 돌아가고 있는 기기 내지 제품의 사용자를 중복 없이 유일하게 식별하는 것이다!
매번 변하는 난수 씨앗이야 그냥 현재 시각을 기반으로 생성한다고 하지만, 한 컴퓨터에만 고유하게 적용되는 시리얼 키 같은 걸 생성하려면 전세계에서 유일하고 불변하고 전무후무한 식별 번호가 하드웨어 차원에서 부여되어 있어야 한다.

하다못해 예전에 도스용 게임 중에서도 저장 기능이 없는 대신, 각 레벨별로 암호 코드가 부여되는 게 있었다. 그 코드값을 알면 나중에 상위 레벨에서부터 게임을 시작할 수 있다.
그런데 문제는 그 코드값이 컴퓨터마다 제각각으로 생성된다는 것.. 그러니 매 컴퓨터에서 게임을 새로 시작해서 상위 레벨에 직접 진입을 해야만 코드값을 알 수 있었다.

그리고 어떤 소프트웨어가 정품 인증을 받았다거나, 셰어웨어의 경우 등록판이 생성되었는데.. 그 인증 정보는 레지스트리나 파일 형태로 저장되곤 한다. 물론 꼼꼼하게 암호화해서 말이다.
그 인증 정보에는 당연히 특정 컴퓨터의 식별자도 들어있어야 할 것이다. 그래야 그 인증 정보만 다른 컴퓨터에다가 슬쩍 복사해서 집어넣더라도 명의가 도용되지 않을 테니 말이다.
이런 식으로 컴퓨터의 고유 식별자는 프로그램 개발자의 입장에서는 매우 다양하게 활용될 수 있다.

자동차에는 외부에 노출된 번호판과 별개로, 자동차 껍데기 자체를 식별하는 차대 번호라는 게 있어서 엔진룸이나 도어 한구석에서 확인할 수 있다.
노트북 PC는 시리얼 번호가 밑바닥에 적혀 있다. 그리고 스마트폰도 기기를 식별하는 고유 번호가 있는 건 마찬가지이다. 사용자를 식별하는 USIM과는 당연히 별개로 말이다.

그런데 PC는 컴퓨터를 유일하게 식별하는 깔끔한 단일 통합 메커니즘이 의외로 존재하지 않는다.
먼 옛날에 펜티엄 3~4 시절에는 CPU의 일련번호를 얻어 오는 명령이 있었던 듯하나.. 예제 코드가 어셈블리어여서 이식성이 전혀 없으며, 요즘 CPU에서는 통하지도 않는다고 한다.

컴퓨터를 식별하기 위해서 지금까지 제일 만만하게 쓰여 온 방법은 맥 어드레스(mac address)라는 48비트짜리 숫자이다. 요즘은 휴대전화 번호가 사람을 식별하는 준 주민 등록 번호나 마찬가지이지 않는가? 그것처럼 통신망에서의 주소는 기기 식별 용도로 나쁘지 않은 선택이다.
하지만 얘를 쓰기에는 요즘 컴퓨터 네트워크는 계층과 종류가 너무 다양해졌으며, 사용자가 값을 변조도 그리 어렵지 않게 할 수 있기 때문에 여러 모로 약발이 다했다.

하드웨어적인 방법에만 너무 의존하면.. 사용자가 램을 더 달거나 하드디스크를 교체한 것만으로 프로그램 정품 인증이 실패하는 불상사가 벌어진다. 도대체 한 컴퓨터의 정체성을 결정하는 것이 무엇인가 하는 본질적인 고민에 부딪히게 된다.
소프트웨어적인 방법으로는 HKEY_LOCAL_MACHINE 상에 있는 Windows의 product ID라든가 Machine GUID가 있는데.. 이것은 변조하기 쉽고 운영체제를 다시 설치하는 것만으로도 무력화될 수 있는 게 약점이다.

최근엔 본인도 이런 쪽으로 고민할 일이 좀 있었다. 그러다가 WMI(Windows Management Instrumentation)라는 DB인지 API인지.. 정체를 알 수 없는 방대한 물건을 최근에야 난생 처음으로 접했다. 여기에 시스템 정보와 관련된 것들이 다 있었다. 하드디스크의 시리얼 번호, 마더보드의 시리얼 번호, 뭐 별별 것까지 다.. 그야말로 끝판왕이었다.

그 중 Win32_ComputerSystemProduct라는 클래스에 있는 uuid 값이.. SMBIOS, 즉 펌웨어 레벨에서 새겨져 있는 불변 유일한 컴퓨터 식별자 역할을 얼추 하겠다는 결론을 내리게 됐다. 최소한 맥 어드레스보다는 더 믿을 만하지 않을까? 명령 프롬프트에서는 wmic csproduct get uuid라고 하면 얻을 수 있다.

WMI에 접근하는 건 C#에서는 꽤 간단하고 쉽게 할 수 있어 보이던데 C++에서는 COM을 초기화하고 온갖 복잡한 인터페이스를 몇 단계씩 생성해야만 가능했다. DB 아니랄까봐, COM으로도 모자라서 팔자에 없는 SQL 쿼리까지 날려야 되더라! SELECT * from Win32_ComputerSystemProduct 같은 식으로 말이다.
누가 클래스 라이브러리 하나 만들어 놓은 것조차 없는 듯... 저 GUID 하나만 달랑 얻어 오는 용도로 쓰기에는 낭비가 꽤 심해 보인다.

Windows와 달리 mac 계열은 하드웨어/소프트웨어가 워낙 딱딱 들어맞는 일체형이니 저 정도의 복잡한 고민은 필요 없을 듯하다. 시스템 정보를 보면 나오는 시리얼 번호와 하드웨어 UUID만으로 식별과 관련된 모든 고민이 끝이지 않을까?
게다가 gethostuuid라는 함수 한 방으로 그 값을 바로 구할 수 있었다.

이미 유비쿼터스니 사물 인터넷 IoT니 뭐니 하면서 운영체제 불문하고 수많은 기기들이 인터넷에 접속하고 있으며, 그에 맞춰서 주소 공간이 월등히 넓어진 ipv6도 서서히 보급되고 있다. ipv6는 주소 공간의 크기가 UUID의 그것과 동일한 128비트이다.

그러니 Windows/mac, 그리고 안드로이드/iOS를 불문하고 전세계 전무후무 유일불변이 보장되는 기기 식별자 같은 것도 제정되지 않을까 싶다. 이미 제정돼 있는데 본인이 아직 모르는 것일 수도 있겠지만, PC 한정으로는 내가 알기로 딱 떨어지는 답은 아직까지 없다. 심증에 속하는 여러 정황상의 단서들을 모아서 물증인 것처럼 편의상 활용하고 있을 뿐이다.

Posted by 사무엘

2019/04/06 08:35 2019/04/06 08:35
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1605

1. 파일 포맷 분석

얼마 전에 본인은 Windows용 MS 한글 IME가 내부적으로 사용하는 한자 사전 파일의 분석을 시도한 적이 있었다.
그 결과 파일의 내부 구획과 구조가 상당수 파악되었다. 해당 프로그램이 한글 독음으로부터 한자 정보를 어떻게 얻어 오고 한자로부터 독음, 부수, 획수 등의 정보를 어떻게 얻는지까지도 모두 알아 냈다.
정체를 알 수 없는 구조체들의 숫자들이 의미하는 것도 규칙성을 파악해서 과반은 해독했다. 데이터가 대놓고 압축이나 암호화가 돼 있지는 않은 덕분이었다.

하지만 제일 중요한 문자열 단어 단위로 저장된 정보들은 끝내 얻지 못했다.
이런 것들을 저장하는 용도로 동일한 포맷의 spell trie/tree 같은 게 여러 개 있는데, 이 중에서 가장 간단하고 작은 형태의 테이블을 얻었으며(노드 몇백 개짜리..) 이걸 풀이해 낸 레퍼런스 결과물까지도 역으로 얻었다.
다시 말해 이 작은 테이블로부터 저 결과물이 어떻게 나오는지 그 방식만 알아낼 수 있으면 나머지 진짜 해독해야 하는 더 방대한 테이블까지 사실상 몽땅 해독이 가능해진다.

여기까지 진행됐는데도 더 나아가는 건 도저히 무리였다. 그 이상부터는 각 노드와 노드가 어떻게 연결돼서 한자어나 한자어 훈이 나오는지.. 탐색을 어디부터 시작해야 할지, 단서를 어디서 얻을 수 있을지 추적이 되질 않았다.
지금까지 투입한 시간이 참 아깝기도 했지만 어쩔 수 없이 여기서 눈물을 머금고 포기하게 됐다. 이게 제일 중요한 정보였는데 말이다.

마소의 한국어 IME야 일본어 IME의 내부 구조의 영향을 받은 게 굉장히 많은데, 저 파일의 자료구조 역시 일본어 IME의 DB 내부 구조를 아는 사람에게는 절대로 낯선 형태가 아닐 것이라고 짐작만 해 본다.
답답한 마음에 MS 한글 IME의 내부를 디버깅까지 해 봤다. 그래 봤자 아무 디버깅 단서가 없는 복잡한 0과 1 기계어 천지 속에서 의미 있는 결과가 나오지는 못했고, 단지 쟤들이 DB 파일 전체를 memory mapped file로 걸어서 사용한다는 결론만을 얻을 수 있었다.

아, 내가 갑자기 이거 추적을 한 이유, 동기, 계기는 뭐냐 하면..
날개셋 한글 입력기에서 '조합 안에 조합 생성' 입력 도구를 사용해 봤는데, MS IME의 API를 사용하는 한글-한자 단어 변환 기능이 유난히 동작이 느리고 랙이 심한 걸 발견했기 때문이다. 실제로 테스트를 해 보니 MS IME API는 주어진 한글 단어로부터 한자어를 얻는 게 수십 ms 이상이 걸릴 정도로 느린 편이었다.

그래서 DB 파일 포맷을 알면 내가 저 파일로부터 한자어 리스트를 직접 더 빨리 얻을 수도 있지 않을까 하는 무모한 도전을 해 봤는데.. 뭐 소기의 목적을 다 이루지는 못했다. 파일의 전체 구조가 어떨지 궁금하다.
다만, 내 추측에 따르면 내부의 파일 구조가 검색에 그렇게 효율적인 형태는 아닌 것 같다. 짐작건대 수백~수천 회 이상의 선형 검색이 발생하기라도 하지 않나 싶다.

뭔가, 남이 짜 놓은 C/C++ 코드를 읽는 것뿐만 아니라 이렇게 복잡한 바이너리 파일 구조를 읽으면서 오프셋을 따라가고 추적하는 것..
메모리와 레지스터, 스택 상태를 살피면서 남이 짜 놓은 프로그램을 디버거로 분석하는 것은 프로그래머 내지 소프트웨어 엔지니어로서 필요한 또 다른 스킬인 것 같다.

2. 매크로/스크립트 기능

어느 정도 규모가 있는 업무용 프로그램에는 일괄 처리와 자동화를 위한 매크로 기능이 있다. 도스 시절에는 key 매크로가 많이 쓰였지만, 그건 요즘 같은 GUI 운영체제의 사정에는 잘 맞지 않기 때문에 스크립트 기반으로 가는 추세이다.
어떤 형태로든 이런 기능은 어느 프로그램에서나 어렵지만 강력한 고급 기능으로 취급받곤 한다.

헥사(바이너리) 에디터에 이런 프로그래밍 기능이 있어서 열어 놓은 파일 전체를 거대한 바이트 배열로 접근 가능하고 현재 cursor 위치가 인자로 주어진다면..
여기서부터 분석을 시작했을 때 구조체에 채워지는 값, 여기서 몇 오프셋에 있는 값만치 이동한 곳에 있는 다른 구조체의 값 등등... 을 출력하는 스크립트를 작성할 수 있고 이걸 이용하면 아까 같은 바이너리 파일 읽기나 역공학, 디버깅 같은 작업을 아주 수월하게 할 수 있을 것이다.

그래픽 에디터도 마찬가지다. 2차원 그래픽 툴은 디자이너가 보는 관점과 프로그래머가 보는 관점이 약간 차이가 있다.
프로그래머는 새로운 그림을 그린다기보다는 화면 캡처나 기존 그림을 보정하는 용도로 이런 프로그램을 사용한다.

그렇기 때문에 좌표 같은 걸 매번 마우스로 힘들게 지정하는 게 아니라.. '정확하게 이 구간의 화면 캡처를 몇 번 하라, 색깔이 이런 조건을 만족하는 구간의 테두리를 짤라내라' 이런 명령은 프로그래밍 가능한 체계가 있으면 매우 도움이 된다.
뭐, 거대한 2차원 캔바스에 공식만으로 기하학적인 그림을 그리는 프로그래밍은 덤이고 말이다.

또한 요즘 그래픽 데이터에는 픽셀이 RGB로만 구성된 게 아니라 알파 채널이란 게 있다. 이건 RGB처럼 색깔을 유일하게 구성하는 요소가 아니라 색깔에 추가적으로 붙는 정보이다.
이건 색깔 자체가 아니다 보니 편집하는 방식이 에디터마다 통일돼 있거나 일관성이 있지가 않다. 색깔은 전혀 안 건드리고 알파만 50%로 할지, 아니면 색깔도 건드리면서 알파도 건드릴지, 기존 알파값에다 상대적으로 알파를 더할지 같은 것 말이다.

이런 식으로 일정 구역이나 조건을 만족하는 픽셀에 대해 알파값을 일관되게 고치고 싶을 때 프로그래밍 기반 매크로가 있으면 굉장히 유용하다.
포토샵 같은 그래픽 에디터를 띄웠는데 화면 하단에 마치 옛날 QuickBasic의 immediate 윈도우처럼 그림을 명령어로 조작하는 입력란이 있다면.. 뭔가 그래픽 에디터답지 않은 공대감성이 느껴질 것 같다. =_=;;

3. 개체에 대한 드래그 좌표의 자동 보정

2차원 공간에서 사각형 형태의 여러 개체들을 만들고 배치하는 기능이 있는 프로그램을 생각해 보자. 벡터 드로잉 기능이 있는 파워포인트나 워드 프로세서 같은 프로그램들이 떠오를 것이다.
그리고 일반인이 쓸 일은 잘 없겠지만 개발툴에 내장돼 있는 대화상자/폼 디자이너도 좋은 예이다.

요즘은 그런 프로그램들이 워낙 똑똑해진지라, 개체를 하나 클릭해서 몸통을 마우스로 끌어 보면 개체가 그냥 곧이곧대로만 움직이지 않는다. 옆이나 위의 주변 컨트롤과 나란히, 가지런하게 배치되게 반경 n픽셀 이내의 대충 비슷한 위치를 방황하고 있으면 프로그램이 알아서 '요기?'라고 가이드라인을 제시해 준다.

그 상태에서 마우스 왼쪽 버튼에서 손을 떼면 프로그램이 제시한 정확한 위치에 개체가 달라붙는다. Shift나 Alt 같은 modifier key를 누른 상태로 마우스를 끌어야 그런 보정 위치가 아니라 곧이곧대로 마우스 포인터가 있는 위치에 개체가 자리잡게 된다. Ctrl은 드래그 하는 개체에 대해서 이동/복사 모드를 전환하는 key이니까..

Visual Basic/C# .NET이 제공하는 폼 편집기는 Visual C++이 제공하는 구닥다리 rc 파일 기반의 재래식 대화상자 편집기보다 훨씬 더 똑똑하기 때문에 주변 컨트롤의 위치들을 토대로 온갖 방식으로 자동 달라붙기를 시도해 준다.
xcode도 만만찮았던 걸로 기억한다.

개인적으로는 이런 부류의 기능이 비트맵 그래픽 에디터에도 있으면 좋겠다는 생각을 한다.
그림의 일부 영역을 select할 때.. 색깔이 급격하게 변하는 경계 근처에 가 있으면 정확하게 보정된 위치에 착 달라붙게 프로그램이 보정 위치를 자동으로 제시하는 것 말이다. 그래서 사용자가 불편하게 이미지를 확대해서 조심스럽게 다루지 않아도 되게 말이다.

물론 방대한 크기의 비트맵으로부터 그런 경계 패턴을 인식하는 것은 많아야 수십 개의 개체 좌표값만 계산하면 되는 벡터 드로잉이나 대화상자 폼 편집기보다 힘든 일일 것이다. 그리고 그래픽 에디터라는 게 프로그래머보다는 디자이너가 더 자주 다루는 물건이고, 디자이너는 컴퓨터스럽고(= 도트 노가다 지향적이며) 경계 구분이 분명하고 기하학적인 이미지보다는... 실물 사진을 더 많이 다룰 테니, 수지가 안 맞아서 저런 기능이 들어가지는 않을 것이다. =_=;;

4. 디버깅과 범죄 수사의 유사점

혼자 오랫동안 해 온 생각인데..
강력 범죄 수사랑 컴퓨터 프로그램 디버깅은 절차와 성격 면에서 서로 좀 비슷한 구석이 있어 보인다.
사건이 발생했다는 112 신고는 프로그램에서 무슨 문제가 발생했다는 버그 신고와 같다.
핏자국, 시신, 어지럽혀진 사건 현장 같은 건 프로그램이 뻗은 모습 내지 각종 디버그 로그와 메모리 덤프에 대응한다.

경찰이 사건을 재구성하고 원인을 추적하는 건 두 말할 나위 없이 프로그래머가 동일 상황을 재현해서 버그를 발견하려고 애쓰는 것과 같다.
버그를 잡는 건 당연히 범인 검거이다. 반대로 미제 상태로 공소시효가 끝나는 건 버그를 못 잡은 채로 해당 프로그램의 지원 내지 버전업이 끝나는 것과 같다.

예전에 본인은 법조인이 컴퓨터 프로그래밍과 비슷한 구석이 있다고 글을 쓴 적이 있는데..
형사가 범죄 현상에서 이상한 것 하나라도 놓치지 않고 실마리를 발견하여 사건을 해결하는 능력은 프로그래머의 디버깅 능력과 일맥상통하는 구석이 있어 보인다. 음, 그러고 보니 그걸 한 단어로 추리력이라고 부르는구나.

5. 롤러코스터

2008년 3월, 에버랜드에는 'T 익스프레스'라고 세계구급의 목재 롤러코스터가 개장했다.
그리고 그로부터 10년 뒤인 지난 2018년 5월, 경주월드에서는 낡은 기존 롤러코스터이던 '스페이스 2000'을 철거하고 '드라켄'이라는 더 높고 짜릿한 신기록급 롤러코스터를 개장했다.

사용자 삽입 이미지사용자 삽입 이미지

선박이나 건물뿐만 아니라 롤러코스터를 봐도 목재와 철재의 차이를 알 수 있어 보인다.
목재인 T 익스프레스는 무슨 비행기도 아닌 것이 운행하는 데 날씨 제약을 많이 받으며 혹한기 동계 휴무도 있으며.. 또 결정적으로 빽빽한 지지대들을 설치하느라 층 위에 층을 놓을 수 없다. 롤러코스터 하면 생각나는 배배 꼬인 나선형 선로조차도 만들 수 없다.

항공 사진에서 복층으로 입체 교차하는 듯이 보이는 일부 구간은 거기에만 부득이하게 금속 기둥과 지지대가 예외적으로 조금 설치돼 있다.
그에 반해 드라켄은 얼마나 뻥 뚫린 황량한 형태인지..?? =_=;; 저런 걸 목재로는 구현할 수 없다.

층 위에 층을 만들 수 없는 게 프로그래머의 눈으로 보기엔 무슨 과거 Doom 엔진 기반의 맵 같다. 물론 Doom 엔진으로는 경사진 바닥도 표현할 수 없긴 하다만.. (오로지 평평한 바닥만)
3D 맵으로 뫼비우스의 띠나 클라인 항아리 같은 걸 편법으로라도 구현할 수 있으려나 모르겠다.. ^^

Posted by 사무엘

2018/09/11 08:34 2018/09/11 08:34
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1531

오늘은 기초 전산학/컴공 상식을 좀 복습해 보고자 한다.

※ 지금과 같은 컴퓨터의 근간이 갖춰진 과정

1. 순 전자식

이로써 인간이 발명한 계산 기계는 엔진 달린 주판 수준을 넘어서 자신의 모든 내부 상태를 전자 신호만으로 광속으로 표현할 수 있게 됐다. 에니악이 순 전자식 컴퓨터로서는 거의 최초 원조라 여겨진다. 이거 이후로 컴퓨터는 진공관, 트랜지스터, IC, (V)LSI 회로 순으로 그야말로 엄청난 공간 워프를 거듭하면서 작아지고 빨라지기 시작했다.

전자식이 아니라면? 컴퓨터도 엔진이나 모터가 달린 채로 만들어졌을 것이다. 19세기에 영국의 수학자 찰스 배비지는 '프로그래밍 가능한 보편적인 계산 기계'인 '해석 기관'이라는 걸 제안하고 만들려 했다. 시대를 아득히 앞서 간 물건이었는데, 그걸 가동하기 위해서 무려 증기 기관을 접목할 생각까지 했었다. 지금 같은 눈부신 전자 공학 기술이 없던 시절이니 당연히 기계식밖에 선택의 여지가 없었던 것이다.

그리고 1940년대 초에 에니악 이전에 등장했던 '하버드 마크 1'이라는 기계는 '전자식 계산기'라기보다는 '전동식 계산기'에 더 가까웠다. 복잡한 배선과 릴레이뿐만 아니라 4마력짜리 모터가 달려 있었다. 이건 냉각팬 모터가 아니며 하드디스크 같은 기계식 보조 기억장치용 모터도 아니고, CPU의 실제 계산 동작을 위한 모터였다..;;

2. 2진법 기반

사람이나 열 손가락이 달려 있으니 10진법이 편하지, 기계는 단순한 2진법이 더 편하다. 컴퓨터가 전자식으로 바뀐 뒤부터는 그 차이가 더욱 두드러졌다.
하지만 극초창기에는 숫자 진법을 변환하는 것조차 쉬운 작업이 아니었고, 정수가 아닌 부동소수점으로 가면 숫자를 표현하는 난이도가 더 올라갔다. 더구나 컴퓨터는 처음부터 포탄 탄도 예측, 풍동 실험, 일기예보 시뮬, 모의 핵실험처럼 천상 실수 연산이 잔뜩 필요한 과학 영역에서 쓰였다.

그러니 에니악 같은 컴퓨터는 10진법 기반으로 만들어졌다. 4비트를 한 자리로 묶어서 0~9를 표현하는 BCD 코드 기반이었지 싶다. 하지만 10진법 숫자를 처리하기 위해서 어차피 2진법 기반의 각종 논리 연산 회로를 구현해야 했을 것이고, 후대의 컴퓨터들은 얼마 가지 않아 native 2진법 기반으로 다 바뀌었다.

3. 튜링 완전

프로그램이 하드코딩된 고정된 변수가 아니라 메모리에 기록된 값을 토대로 또 임의의 위치의 메모리를 읽고 쓸 수 있고(= 배열, 포인터 등을 이용한 복합 자료형. 공간 확장),
런타임 때 결정되는 값의 조건에 따라 반복과 분기가 가능하다면 (= 시간 확장)
그런 계산 모델은 Turing-complete하다고 여겨진다. 즉, 단순 계산기를 넘어 뭔가 본격적으로 프로그래밍이 가능해진다는 것이다.
그 열악한 에니악조차도 설계 구조는 튜링 완전한 형태였다고 한다.

4. 프로그램 내장형

컴퓨터에게 시킬 작업을 변경하기 위해 매번 회로 배선을 뜯어고치고 바꾸는 게 아니라, 한 메모리에서 코드와 데이터를 일체로 내장시킨다. 이 개념까지 정립됨으로써 비로소 컴퓨터는 정말 유연하고 무한한 확장성을 지닌 물건으로 변모했으며, 컴퓨터에서 하드웨어와 별개로 '소프트웨어'라는 것이 존재할 수 있게 됐다.
또한, 메모리가 컴퓨터의 성능에서 차지하는 비중이 아주 커졌다. 프로그램을 메모리에다 처음으로 입력시킬 때는 과거엔 천공 카드 같은 불편한 매체가 쓰였지만, 나중에는 더 간편한 키보드로 대체됐다.

저 아이템들 하나하나가 그야말로 병아리가 알을 깨고 세상으로 나오는 급의 대격변이고 혁신이었다.
인류 역사상 이런 네 조건을 모두 만족하는 컴퓨터가 발명되고 등장한 지 아직 100년이 채 지나지 않았다. 자동차와 비행기의 역사는 100년을 넘었지만 컴퓨터는 아직 그렇지 않고 오히려 2차 세계 대전 이후 냉전 때부터 발전해 왔다.
그 짧은 기간 동안 컴퓨터가 인류 역사상 유례가 없이 세상을 바꿔 놓은 걸 보면.. 정말 전율이 느껴지지 않을 수 없다.

※ 메모리 계층

컴퓨터는 모름지기 정보를 다루는 기계이다. 그리고 앞서 언급했던 프로그램 내장 방식의 특성상, (1) 실행할 코드와 (2) 그 코드가 처리할 데이터가 모두 메모리에 담겨 있어야 한다. 쉽게 말해 정보를 담을 그릇이 필요하다.
그런데 컴퓨터가 취급하는 메모리라는 게 여러 종류가 있고, 이들은 속도와 용량, 단위 용량당 가격이 극단적으로 반비례하는 관계이다. 그렇기 때문에 종류별로 일종의 '메모리 계층'이 존재한다.

1. 레지스터(수십~수백 byte)

CPU 구성요소의 일부이다. 당연히 CPU 차원에서 최고속으로 직통으로 값을 읽고 쓸 수 있다.
현재 프로그램이 실행되고 있는 지점(메모리 위치), 수만 번씩 실행되는 for 문 loop 변수, C++ 함수의 경우 this 포인터, 산술 연산 명령에 쓰이는 피연산자와 연산 결과 같은 정~말 원초적인 값들이 이곳에 저장된다.
실행되는 스레드의 context가 바뀌면 레지스터의 값도 자기 상태의 것으로 바뀐다.

2. 캐시 메모리(수백 KB~수 MB)

CPU 자체는 아니지만 여전히 CPU의 연장선 격이며 접근 속도가 매우 빠르다. CPU가 사람 두뇌이고 레지스터가 손의 손가락이라면 캐시는 의수 정도는 된다.
얘는 CPU 속도와 메모리 속도의 격차가 커지면서 메모리로 인한 병목을 줄이기 위한 버퍼 차원에서 도입되었다.

캐시도 레벨 1, 레벨 2로 나뉘긴 하는데, 인텔 x86 CPU에서 제일 원초적인 L1 캐시는 80486 때 8K짜리가 도입된 것이 최초이다. 반대로 펜티엄 2이 나왔던 시절에 셀러론 프로세서는 L2 캐시를 제거하거나 용량을 팍 줄인 저가형 모델이었다.

3. 일반 메모리(수십 GB)

CPU의 외부에 있기 때문에 위의 것들보다는 느리지만, 그래도 보조 기억장치보다는 여전히 훨씬 빠르다. 이들 메모리는 전원이 끊어지면 내용이 다 지워지는 휘발성 메모리이다. 이제 신체 접근성으로 치면 의수를 넘어서 핸들과 버튼으로 따로 조작하는 로봇 팔과 비슷하다고 볼 수 있겠다.

4. 하드디스크(수 TB)

디스크부터는 보조 기억장치이기 때문에 이건 CPU의 명령만으로는 직접 접근조차 할 수 없다. 운영체제라는 소프트웨어가 구현해 놓은 파일 시스템에다 해당 운영체제 API를 통해 요청해야만 데이터를 읽고 쓸 수 있다. 파일 시스템은 열고 닫는 상태를 따로 보관하고 관리해야 하며, 프로그램의 입장에서는 여는 작업이 실패하는 상황에 대한 대비가 필요하다.
사람으로 비유하면 내 손으로 뭔가를 직접 조작하는 게 아니라, 남에게 말로 부탁을 해서 간접적으로 뭔가를 요청하고 움직이는 형태가 된다.

그 대신 보조 기억장치는 전원이 끊어진 뒤에도 기록을 남기고 보존할 수 있다. persistency를 보장하려다 보니, 하드디스크는 컴퓨터에서 전자식이 아닌 기계식으로 동작하는 얼마 안 되는 부품 중 하나가 돼 있다. 플래시 메모리는 '일반 메모리'의 성격에 더 근접해 있는 기억장치이지만, 가격과 용량 문제 때문에 하드디스크를 완전히 대체하는 구도는 못 된다.

캐시 메모리에서 캐시 미스가 나서 더 느린 일반 메모리까지 내려가서 데이터를 가져오는 게, 아래의 운영체제의 가상 메모리 체계에서 페이지 폴트가 발생해서 디스크의 페이지 파일에서 데이터를 가져오는 것과 비슷한 구도이다. 메모리 공간 자체가 CPU의 일부는 아니지만, 보호 모드 가상 메모리 구현을 위한 주소 변환은 CPU 차원의 지원을 따로 받아서 이뤄진다.

메모리가 비싸고 귀하고 부족하던 옛날에는 가상 메모리라는 게 디스크를 메모리 보충분처럼 사용하는 메커니즘이기도 했다. 비록 속도는 안드로메다로 가 버리지만, 그래도 아예 안 돌아가는 것보다는 나으니 better late than never이다. 요즘 운영체제들은 memory mapped file이라고 디스크를 반쯤 메모리 다루듯이 포인터로 접근시켜 주는 API를 제공하는데, 가상 메모리를 구현하면서 내부적으로 구현된 기능을 사용자도 적절하게 활용하라고 떼어 준 것에 가깝다.

또한, 가상 메모리와는 별개 개념으로.. 레지스터와 메모리 사이에 '캐시 메모리'가 있듯이, 메모리와 디스크 사이에 '디스크 캐시'라는 계층이 존재한다. 이게 잡아먹는 메모리 양이 만만찮지만 도스 시절에 smartdrv 유틸로 수백 KB~2MB 남짓만 캐시를 잡았어도 체감 성능 향상 효과가 장난이 아니었다. 이거 없이 곧이곧대로 찔끔찔끔 디스크에 접근해서는 오늘날의 방대한 컴퓨터 시스템이 돌아가질 못한다. 그만치 메모리와 디스크 사이의 속도 격차 병목이 엄청나다는 뜻이다.

5. 자기 테이프(수백 TB~수 PB)

아주 극단적인 보조 기억장치이다. 느리고 랜덤(임의 위치) 접근이 안 된다는 엄청난 단점이 있지만, 용량이 가히 압도적이고 가격이 저렴하다. 그렇기 때문에 서버 전체 내지 매일 생성되는 방송국 동영상 같은 엄청난 양의 데이터를 오로지 백업· 보존만 할 목적으로 일부 연구소나 기업에서 테이프가 여전히 사용되고 있다. 마치 국제 화물 운송에서 선박이 차지하는 위상(느리지만 엄청난 수송량)과 비슷하고, 프린터계에서 도트 프린터의 먹끈 카트리지(원시적이지만 타의 추종을 불허하는 저렴함)와 비슷하다.

메모리야 컴퓨터 프로그램들이 맨날 하는 짓이 저걸 건드리는 것이고, 보조 기억장치는 파일을 읽고 쓰는 운영체제 API를 통해 사용 가능하다.
레지스터의 경우, C/C++ 언어에는 특정 정수 변수를 가능한 한 저기에 얹어 달라고 컴파일러에게 요청하는 register이라는 키워드가 있다. 함수에 inline이 있다면 변수는 저게 있는 셈이다. for문 loop 변수가 레지스터에 올라가면 좋다.
물론, inline 함수는 재귀호출을 해서는 안 되며, 레지스터 등재 변수는 주소 참조(단항 & 연산자)를 해서는 안 된다.

이렇게 타 메모리나 디스크나 레지스터와는 달리, 캐시 메모리만은 적중률을 올리기 위해 소프트웨어가 직접 접근하고 개입하는 방법이 딱히 존재하지 않는다. 멀티코어 병렬화를 위해서는 CPU 직통 명령인 인트린식 같은 것도 있는데 캐시는 활용 방식이 소프트웨어가 아닌 오로지 CPU의 재량인가 보다.
이렇게 존재감이 없음에도 불구하고 캐시 메모리의 양과 성능은 클럭 속도 다음으로 컴의 속도에 직접적인 영향을 끼치는 요인이다.

※ 인텔 x86

인텔 x86은 전세계의 PC 시장을 완전히 석권한 기계어 아키텍처이다. 애플 맥 진영이 x86으로 갈아탄 지 이미 10년이 넘었고, 슈퍼컴퓨터조차도 Cray 같은 슈퍼컴 전용 아키텍처가 진작에 다 망하고 x86이 코어 수를 늘려서 야금야금 파고들고 있다.

하지만 x86은 CPU를 만들던 기술과 방법론이 지금과 같지 않던 초창기, 특히 메모리 가격이 왕창 비싸던 시절을 기준으로 기반이 설계되었으며 16, 32, 64비트로 올라가는 과정에서도 하위 호환성을 잘 유지하고 있다. 그래서 넘사벽급의 범용성과 시장 경쟁력은 확보했지만, 내부 구조가 갈수록 왕창 지저분해지고 스마트폰용 ARM 같은 후대의 최신 CPU들의 유행과는 영 동떨어진 형태가 됐다.

  • 범용 레지스터 수가 유난히 매우 적음. R## 이렇게 수십 개씩 번호가 붙는 게 아니라 EAX EDX ESI EBP 등 꼴랑 8개로 끝인 건 x86이 예외적이고 특이하기 때문이다. 함수에다가 매개변수를 올리는 주 방식도 x86은 당연히 레지스터가 아닌 스택 기반이다. 이 때문에 컴파일러 백 엔드를 개발하는 방법론이 x86 타겟 계열과 타 아키텍처 계열은 서로 완전히 다르며, x86은 오늘날 컴공과에서 컴파일러 제작 교육용 교보재로 쓰이기에는 영 좋지 못한 타겟 아키텍처이다.
  • 메모리를 조밀하고 compact하게 쓰는 대신에, 디코딩이 복잡하고 더 어려운 CISC 가변 길이 방식으로 명령어를 기술한다. 한 인스트럭션으로 연산에다 메모리 조작까지 몽땅.. 이런 식으로 많은 지시를 함축하고 있는 편이다. 자동차 엔진으로 치면 회전수가 낮은 대신 실린더의 스트로크가 긴 디젤처럼..
  • machine word align이 맞지 않은 메모리 주소의 값을 fetch하는 것을 굉장한 비효율(여러 클럭수 소모)을 감수하고라도 CPU 차원에서 아무 문제 없이 잘 처리해 준다. 요즘 CPU 같았으면 그냥 예외 날리고 끝이었을 텐데.. 이 역시 메모리를 아끼기 위한 조치이다.

레지스터가 부족하면 나중에라도 더 보충하면 되지 않냐고?
레지스터는 추가로 더 꽂기만 하면 되는 메모리가 아니라 CPU 그 자체이다. 그걸 뒤늦게 확장한다는 건 CPU의 아키텍처, 세부 설계와 생산 라인이 다 바뀐다는 뜻이다. 컴파일러도 그에 맞춰 바뀌고 프로그램도 몽땅 다시 빌드되어야 추가된 레지스터 덕을 볼 수 있다. 사람으로 치면 가방 크기를 더 키우는 게 아니라 생물의 유전자 차원에서 손의 크기, 손가락 개수를 더 키우고 늘리는 것과 같은 엄청난 변화이다.

x86이 너무 지저분하다는 건 제조사인 인텔도 누구보다 잘 알고 있었기 때문에 과거 2000년대 초, 64비트 CPU를 내놓는 김에 애플처럼 하위 호환성을 싹 버리고 현대적인 디자인 트렌드를 따라 과감한 물갈이를 하려 했다.
마소 역시 새천년 Windows 2000에 맞춰 64비트 에디션을 당당히 내놓으려고 벼르고 있었다. Windows SDK 헤더 파일에서 INT_PTR, INT64 이런 typedef가 등장하고 GetWindowLong이 GetWindowLongPtr로 감싸진 게 이 시기의 준비 작업이었다.

하지만 모두의 예상을 깨고 IA64 Itanium라는 새 아키텍처는 CPU와 컴파일러 개발이 제대로 되지 않고 호환성도 안습했기 때문에 철저히 망하고 실패했다.
결국 지금은 기존 x86을 그대로 수용하면서 Itanium보다 훨씬 더 현실과 절충한 x86-64라는 다른 아키텍처를 기반으로 64비트 컴퓨터가 쓰이게 됐다. 이 아키텍처는 인텔이 아니라 경쟁사인 AMD가 최초로 개발했다.

Windows 2000은 과거 NT 3~4 시절에 지원했던 한물 간 구형 CPU들의 지원은 다 끊었고(Alpha, PowerPC, MIPS 등), IA64는 베이퍼웨어이고, 지금 같은 ARM이나 x64는 아직 안 나왔다 보니 NT로서는 이례적으로 사실상 x86 전용으로만 출시되어야 했다.

그런데.. 인텔 x86이 저렇게 메모리 아끼려고 CPU 본연의 효율까지 희생하면서 헝그리하게 설계된 건 과거 PC의 역사를 살펴보면 충분히 이해가 된다.
32비트 80386 CPU가 이미 1985년에 개발됐는데도 Windows NT, OS/2 같은 이상적인 32비트 운영체제의 도입과 보편화가 10년 가까이 너무 늦었고 Windows 9x 같은 요물이 몇 년간 쓰여야 했던 이유는 32비트 가상 메모리를 운용하고도 남을 정도로 컴의 메모리가 충분치(못해도 수~십수 MB) 못했기 때문이다. (CPU 말고 그래픽 카드는 1987년에 VGA가 개발되자 못해도 2~3년 안으로 프로그램들이 다 지원하기 시작함)

64비트로 넘어갈 때도 마찬가지다. IA64가 개발되던 1990년대 말엔 아직 가정용 컴의 메모리는 100~200MB대에 불과했다. 32비트를 벗어나야 할 이유가 전혀 없었다. 64비트 CPU는 대용량 데이터 처리 분야에서 속도가 좀 더 올라갈지는 모르지만, 같은 명령과 데이터를 수행하더라도 메모리 소모가 훨씬 더 많아지는 건 피할 수 없었다. 이러니 가정용 PC에서 64비트의 대중화는 Windows 2000/XP 시기는 어림도 없고, 본격적으로 램 용량이 4GB를 넘어선 2000년대 후반 Vista/7급은 돼서야 이뤄지게 됐다.

Posted by 사무엘

2017/12/11 08:31 2017/12/11 08:31
, ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1436

컴퓨터 프로그램이 뻗는 방식을 분류하면 크게 다음과 같이 정리된다.

1. 아무 뒤끝 없이 그냥 뻗음(crash)

제일 단순하고 흔한 형태이다. 코딩을 잘못해서 잘못된 메모리에 접근하다가 튕긴 것이다. 그 예로는 null 포인터(null로부터 유도된 인근의 잘못된 주소 포함), 초기화되지 않은 포인터, 초기화되지 않은 배열 첨자 인덱스, 이미 해제된 메모리 포인터 등 참 다양하다.
혹은 애초에 메모리를 할당하는데 할당량에 엉뚱한 값이 들어와서 뻗은 것일 수도 있다. 가령, 음수만치 할당은 저 문맥에서는 대체로 부호 없는 정수로 바뀌면서 도저히 감당 불가능한 엄청난 양의 메모리 요청으로 바뀌기 때문이다.

2. CPU 사용 없는 무한루프

단독으로 돌아가는 프로그램이 제발로 이렇게 되는 경우는 잘 없다. 이건 스레드 내지 프로세스 간에 서로 아귀가 안 맞는 상호 대기로 인해 deadlock에 걸려서 마취에서 못 깨어난 상황이다. 그러니 엄밀히 말해 무한루프보다는 무한대기에 더 가깝겠다.
굳이 커널 오브젝트를 직접 취급하지 않고 윈도우 메시지를 주고받다가도 이렇게 될 수 있다. 가령, 스레드 A가 타 프로세스/스레드 소속의 윈도우 B에다가 SendMessage를 해서 응답을 기다리고 있는 중인데, B는 또 스레드 A가 생성한 윈도우에다가 SendMessage를 했을 때 말이다. 요 데드락을 해소하려고 ReplyMessage라는 함수가 있다.

3. CPU 쳐묵과 함께 무한루프

종료 조건을 잘못 명시하는 바람에 loop에서 빠져나오지 못하는 경우이다. 부호 없는 정수형으로 변수를 선언해 놓고는 while(a>=0) a--; 이런 식으로 코딩을 해서 무한루프에 빠지는 경우도 있다. 얘는 그래도 다행히 메모리 관련 문제는 없는 상황이다.

4. stack overflow와 함께 뻗음

이건 단순 뺑뺑이가 아니라 재귀호출을 종료하지 못하고 비정상적으로 반복하다 이 지경이 된 것으로, 컴에 메모리가 무한하다면 3번 같은 무한루프가 됐을 상황이다. 하지만 현실에서는 물리적인 자원의 한계가 있고, 또 컴이 취급 가능한 메모리 주소 자릿수 자체도 무한하지 않기 때문에 언젠가는 뻗을 수밖에 없다.

재귀호출도 반드시 A-A-A-A-A... 이렇게 단일 함수만 쌓이는 게 아니라 마치 유리수 순환소수처럼 여러 함수 호출이 주기적으로 쌓이는 경우도 있다.
스택은 다음에서 다룰 heap 메모리와는 달리, 그래도 그 정의상 할당의 역순으로 회수되고, 회수가 반드시 된다는 보장은 있다.

5. 메모리 쳐묵과 함께 뻗음

이건 heap memory의 leak을 견디다 못하고 프로그램이 뻗은 것이다. loop 안에서 계속해서 leak이 발생하면 꽤 골치아프다. 또한, 금방 발견되는 leak은 그나마 다행이지, 프로그램을 몇 주, 몇 달째 돌리다가 뒤늦게 발견되는 것은 더 답이 없고 잡기 어렵다. 프로그램이 뻗은 지점이 실제로 문제가 있는 지점과는 전혀 관계 없는 곳이기 때문이다. 뭔가 컴파일 에러와 링크 에러의 차이와도 비슷한 것 같다.

요약하면, 메모리 쪽 문제는 가능한 한 안 마주치는 게 낫고, 마주치더라도 프로그램이 곧장 뻗어 주는 게 디버깅에 유리하다. 1과 5는 포인터를 대놓고 취급하지 않는 C/C++ 이외의 언어에서는 프로그래머가 직접 볼 일이 드물다.
요즘은 그래도 디바이스 드라이버 급이 아닌 평범한 양민 프로그램이라면 메모리 문제로 뻗는 경우 전적으로 혼자만 뻗지, 컴퓨터 전체를 다운시키는 일은 없으니 세상 참 좋아졌다. 이게 다 가상 메모리와 보호 모드 덕분이다.

Posted by 사무엘

2017/10/03 19:34 2017/10/03 19:34
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1412

« Previous : 1 : 2 : 3 : 4 : 5 : Next »

블로그 이미지

그런즉 이제 애호박, 단호박, 늙은호박 이 셋은 항상 있으나, 그 중에 제일은 늙은호박이니라.

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2024/04   »
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30        

Site Stats

Total hits:
2676489
Today:
1057
Yesterday:
2124