2016년 2월 16일 화요일

POD(Plain Old Data)

Google C++ Style Guide ( https://google.github.io/styleguide/cppguide.html ) 를 확인하다보니 POD에 관한 내용이 나옵니다.

Static and Global Variables

Variables of class type with static storage duration are forbidden: they cause hard-to-find bugs due to indeterminate order of construction and destruction. However, such variables are allowed if they are constexpr: they have no dynamic initialization or destruction.
Objects with static storage duration, including global variables, static variables, static class member variables, and function static variables, must be Plain Old Data (POD): only ints, chars, floats, or pointers, or arrays/structs of POD.

POD(Plain Old Data)가 뭔지 몰라서 검색하고 정리합니다.

http://1stpasa.tistory.com/13 과 http://www.gpgstudy.com/forum/viewtopic.php?t=10067&sid=4598bc316bf03f5c1f90aecdad41e177 의 내용을 기반합니다.

POD란 C의 struct나 build-in-type과 동일한 메모리 구조를 갖는 Object를 의미합니다.

따라서 build-in-type과 같이 쓸 수 있는 타입이죠.

memset, memcpy에 의한 단순 메모리 복사가 가능한 구조이며 다음의 조건을 가진다고 합니다.

1. built-in type
2. 가상 함수가 없고 사용자 정의 할당자(allocator)와 소멸자를 가지지 않는 Class의 Object
3. non POD를 non-static 멤버로 가지지 않는 Class의 Object

경우에 따른 메모리 구조 변경이 일어나지 않으면 POD로 생각하면 될 것 같네요.

1은 이해가 되고, 3의 에서 static에 관한 내용은 static은 객체에 포함되는 멤버가 아니므로 non-POD가 static으로 있는 것은 괜찮지만 그렇지 않으면 당연히 POD가 안되겠지요. 좀 더 쉽게 쓰면 "POD만을 non-static으로 가져야 POD다." 가 됩니다. (말이 어려웠네요)

2번째 조건에서 사용자 정의 할당자(allocator)에 대해서는 조금 설명이 필요합니다.

사용자 정의 할당자는 정의가 되어 있건 아니건, 할당 위치에 영향을 미치고 객체 내부의 메모리 배치에는 영향을 주지 않습니다.

그럼 POD여도 될 것 같은데 그렇지가 않습니다.

위 사이트에 나온 Code로 설명하겠습니다.

다음의 코드의 할당문( = )과 memcpy의 결과는 동일합니다.

class POD
    {
    int x_;
    char* buf_;
    }
 
POD t1;
POD t2;
...
t1 = t2;
memcpy(&t1, &t2);

그러나 다음과 같이 사용자 정의 할당문( 연산자 "=" 오버로딩)을 만들면 memcpy와 결과가 다릅니다.


class NPOD
    {
    int x_;
    char* buf_;
 
    NPOD() : buf_(0) {}
    ~NPOD() { delete buf_; }
    NPOD& operator=(const NPOD& other)
        {
        x_ = other.x_;
        int bufLen = strlen(other.buf_);
        buf_ = new char[bufLen+1];
        memcpy(buf_, other.buf_, bufLen);
        }
    }
 
NPOD t1, t2;
...
t1 = t2;
memcpy(&t1, &t2);


주의 깊게 작성하면 동일 할 수도 있겠지만(아마도) 위와 같이 그렇지 않을 수도 있습니다.따라서 C++에서는 non-POD로 규정하여 C style 메모리 관리 함수 사용을 금지합니다.

행렬 곱셈과 고유값 분해 (eigendecomposition)

행렬 거듭 제곱에 대해서 생각난 것이 있어서 정리합니다.

오늘 알고리즘 스터디에서 행렬 거듭 제곱의 연산비용을 줄이는 이슈에 대해서 잠시 얘기를 했었는데요,

오는길에 생각해보니 선형대수학의 '고유값분해'를 사용하면 훨씬 효과적으로 될 것 같네요.

A라는 정방행렬을 n번 곱하는 A^n라고 한다면,

A^n = AAAAAAA... 이되겠죠. 만약 고유값분해가 가능하다고 한다면(불가능하기도 합니다)

A = SLS^-1 라고 표시됩니다. (S^-1 은 S의 역행렬, L은 대각행렬)

그러면,

A^n = SLS^-1SLS^-1SLS^-1...SLS^-1 = SL^nS^-1  이 되겠죠 (SS^-1 = 단위행렬이므로)

여기서 L은 대각행렬 이므로 n번 거듭 곱해도 일반적인 행렬 연산보다는 훨씬 쉽게 계산 됩니다.

이를 이용해서 A^n 를 계산 할 때마다 고유값분해가 가능한지 확인하고 가능한 시점부터는 알고리즘을 급격히 효과적으로 작성 할 수 있겠네요 :)

http://darkpgmr.tistory.com/105 에 이와 관련된 자세한 내용이 있으니 참고하세요.

추가:

octave를 깔 일이 있어서 깔고 생각난 김에 octave로 행렬의 거듭제곱을 해보니 바로바로 결과가 나옵니다.

octave:16> A = [0.5, 0.3; 0.4, 0.7]
A =

   0.50000   0.30000
   0.40000   0.70000

octave:17> A ** 2
ans =

   0.37000   0.36000
   0.48000   0.61000

octave:18> A ** 1000
ans =

   1.2028e-18   1.3849e-18
   1.8465e-18   2.1261e-18

어떤 최적화 기법(위와 같은)이 있지 않을까 추측해봅니다.

2016년 2월 1일 월요일

Python으로 BlockGame 풀기

알고리즘 Study 에서 이번에 BlockGame이라는 문제를 풀게 되었습니다.

https://algospot.com/judge/problem/read/BLOCKGAME

인데요, 풀이의 포인트는 다음의 두 가지로 정리됩니다.

1. 전수조사

3칸 or 2칸 block 을 골라야 하지만 편의상 2칸의 block만을 고른다고 할 때 대략
25C2 * 23C2 * 21C2 ... 3C2 입니다.
모든 경우에 3칸도 고를 수 있다고 치면 2를 매번 곱하면 되므로 2^12를 추가로 곱해주면 될 것 같네요.
3786916514485104000000L * 2^12 = 15511210043330985984000000L
정도의 경우의 수가 나오므로 이걸 다 저장하고 계산하는 것은 무리입니다.
하지만, [1,1,1,1,0] 이 [1,1,0,0,0] 다음에 생성되거나,[0,0,1,1,0] 다음에 생성되거나 결과는 동일하므로 순서가 중요하지 않고 검사 할 당시 배열의 상태만이 중요합니다. (설명을 위하여 1차원 배열로 기술하였으나 실제로는 2차원 배열)
즉, 가능한 모든 Board의 모양은 2^25 보다 적을 것 입니다.
2^25 는 33554432 이므로 이 정도라면 저장 할 수 있습니다.
즉, 메모이제이션을 쓴다면 탐색 시간이 아주 짧아지겠죠.

2. Bitmasking
문제를 풀기 위한 함수가 최대 2^25번 호출된다고 하면, 이 함수를 최대한 간소화해서 짜야 합니다. 이점을 간과하고 처음에는 5x5의 board에 block이 들어가는지 확인하기 위해 다음과 같은 코드를 짰습니다.
    def canPush(self, board, pushBoard):
        for x in range(len(board)):
            for y in range(len(board[0])):
                if 1 == pushBoard[x][y]:
                    if board[x][y] != 0:
                        return False
        return True

그냥 단순하게  5*5 번의 loop을 돌며 두 board (두번째 board는 block이라고 보시면 됩니다) 를 비교하는 code인데 이것이 상당한 cost가 되더군요.
이를 사용한 코드는 대략 는 이렇습니다.

        for block in totalBlock:
            if self.canPush(board, block):
               ...

별것 아닌 것 같은데 이 경우 시간이 너무 오래 걸렸습니다.
이를 해결하기 위해서 bitmask 를 사용합니다.
즉, 5x5 배열이 어짜피 1 혹은 0으로 표시 될 수 있으므로 이를 AND나 OR 연산으로 한번에 비교하는 것 입니다. 다음과 같습니다.
for block in totalBlock:
            if block & board == 0:
                if not self.isWin(board | block, totalBlock): 

이렇게 하면 획기적으로 속도가 빨라지더군요-



설명한 1과 2를 이용한 전체 코드는 다음과 같습니다.
    def isWin(self, board, totalBlock):
        self.callCnt = self.callCnt + 1

        ret = False
        if board in self.boardMap:
            self.hitCnt = self.hitCnt + 1
            ret = self.boardMap[board]
            return ret

        for block in totalBlock:
            if block & board == 0:
                if not self.isWin(board | block, totalBlock):
                    ret = True
                    break

        self.boardMap[board] = ret

        return ret

자 그럼 이 경우 문제가 풀렸느냐

그렇지 않습니다.

...ㅠㅠ

Python에서 제귀 자체에 대한 비용이 꽤 큰지 탐색 할 경우가 많으면, 제대로 풀리지 않았습니다.

일단 pypy를 사용해봤는데요, 정말 획기적으로 속도 향상이 있었지만 문제가 되는 케이스에서는 여전히 만족할 속도가 나오지 않았습니다.
대략적으로 python에서 50여초가 걸리던 케이스가 pypy에서는 9초가 걸리더군요-

우선 개선점을 좀 더 고민하다가, board가 저장되면 이를 90도, 180도, 270도 돌린 board도 결과가 같음을 알았습니다.

저장하는 부분 self.boardMap[board] = ret 에 위의 3가지 케이스가 더해지도록 합니다.
우선 회전을 위한 코드는 다음과 같습니다.

    def rotate5x5(self, board):
        while len(board) < 25:
            board = "0" + board
        rotated = [0 for i in range(25)]
        rotatedIdx = [[i+j*5 for i in range(5)] for j in range(5)]
        rotatedIdx = [list(tmp) for tmp in zip(*rotatedIdx[::-1])] # Rotate
        rotatedIdx = [item for sublist in rotatedIdx for item in sublist] # to Flat
        for idx in range(25):
            rotated[idx] = board[rotatedIdx[idx]]
        return rotated

이를 활용해서 저장부에 다음을 추가합니다.
        rotated = self.rotate5x5("{0:b}".format(board)) # 90
        self.boardMap[int("".join(rotated), 2)] = ret
        rotated = self.rotate5x5("".join(rotated)) # 180
        self.boardMap[int("".join(rotated), 2)] = ret
        rotated = self.rotate5x5("".join(rotated)) # 270
        self.boardMap[int("".join(rotated), 2)] = ret

자- 잘 풀렸을까요?

아닙니다. isWin의 cost가 rotate5x5로 인해 늘어나서 속도는 더 느려집니다. ㅠㅠ

rotate5x5의 연산 cost를 다음과 같이 좀 무식하게 해서 줄여봤습니다.
 while len(board) < 25:
            board = "0" + board
        rotated = [0 for i in range(25)]
        rotated[0] = board[20]
        rotated[1] = board[15]
        rotated[2] = board[10]
        rotated[3] = board[5]
        rotated[4] = board[0]
        rotated[5] = board[21]
        rotated[6] = board[16]
        rotated[7] = board[11]
        rotated[8] = board[6]
        rotated[9] = board[1]
        rotated[10] = board[22]
        rotated[11] = board[17]
        rotated[12] = board[12]
        rotated[13] = board[7]
        rotated[14] = board[2]
        rotated[15] = board[23]
        rotated[16] = board[18]
        rotated[17] = board[13]
        rotated[18] = board[8]
        rotated[19] = board[3]
        rotated[20] = board[24]
        rotated[21] = board[19]
        rotated[22] = board[14]
        rotated[23] = board[9]
        rotated[24] = board[4]
        return rotated
조금 줄어들긴 합니다만, 여전히 안쓰는 것 보다 더 높은 cost 를 요구합니다 ㅠㅠ

즉, bit 연산처럼 O(1) 의 cost로 배열을 돌릴 수 없다면, 5x5의 경우 그다지 성능 개선이 나오지 않습니다. (물론 더 커지면 의미가 있을 것 같네요)

아무래도 그냥 메모이제이션만 사용하는게 우선은 최선이라 할 수 있겠네요.

즉, 이 문제는 python으로는 무리가 있는 것 같습니다.
(해결하신 분은 저도 알려주세요. 계속 고민해봐야죠.)

여기서 궁금증이 생겨서 python(그리고 pypy)의 recursion cost를 측정해봤습니다.

재미있는 점은 변수도 cost에 큰 영향을 준 점인데요, 아래의 코드에서 callCnt가 그것입니다.(callCnt는 굳이 이렇게 측정하지 않아도 2^n이지만 비교를 위해 넣습니다) 일단 코드는 다음과 같습니다.
    callCnt = 0
    def cost(self, n):
        self.callCnt = self.callCnt + 1
        if n == 0:
            return 1
        else:
            return self.cost(n-1) + self.cost(n-1)

    def testRecursion(self):
        self.cost(20)
        print self.callCnt

이를 돌리면  python에서는 0.640529870987 의 시간이 걸립니다.
pypy에서는 어떨까요? 0.121232032776 가 걸리네요-

재미있는 점은 위에서 self.cost(20)이 아닌 5와 같이 작은 수로 한 경우인데요,
이 경우 pypy가 더 느립니다.

pypy: 0.000104904174805
python: 5.60283660889e-05

10정도 해도 pypy가 더 느리네요.

pypy: 0.016s
python: 0.001s

아마 pypy의 최적화 코드가 어느정도 까지는 오버해드로 작용하는 것 같습니다.

사용한 python은 Python 2.7.6 이며, pypy는 Python 2.7.3(PyPy 2.2.1 with GCC 4.8.4) 입니다.

2016년 1월 10일 일요일

PKCS#12

요즘 보안관련된 문서를 접하다보니 익숙한 것 같지만 또 잘 모르는 그런 용어들이 많이 나타납니다.

오늘은 PKCS#12 에 관해서 정리해보겠습니다.

우선, PKCS 자체의 의미는 공개 키 암호 표준(Public-Key Cryptography Standard) 를 의미하고, 공개 키 암호에 대한 사용 방식을 정의한 프로토콜입니다.

이중 #12 의 경우 "Personal Information Exchange Syntax Standard" 라는 이름을 가지고 있네요.

wikipedia를 참고하면, ( https://en.wikipedia.org/wiki/PKCS_12 )

In cryptographyPKCS #12 defines an archive file format for storing many cryptography objects as a single file. It is commonly used to bundle a private key with itsX.509 certificate or to bundle all the members of a chain of trust.[1] 

라고 합니다.

많은 경우에 흔히 말하는 인증서와 키가 합쳐진 형태로 이해하기도 합니다.

PKCS#12 형태의 파일을 만드는 절차에 인증서와 키가 필요합니다.

http://www-01.ibm.com/support/knowledgecenter/SSWHYP_4.0.0/com.ibm.apimgmt.cmc.doc/task_apionprem_generate_pkcs_certificate.html?lang=ko 를 참고로 설명하겠습니다.

PKCS#12 파일을 생성하기 위해서는

필요한 것: 개인키(ex> key.pem), 인증 기관에서 서명한 인증서(ex> certificate.pem), CA기관의 하나 이상의 인증서(중간 CA인증서라고도 함)

프로시저

  1. 중간 CA 인증서가 있는 경우 이를 pem파일과 연결시켜 caChain을 빌드
    • cat ca1.pem ca2.pem ca3.pem > caChain.pem
  2. 개인키, 서명된 인증서, 1단계에서 작성한 CA파일을 포함하는 PKCS#12 파일
    • openssl pkcs12 -inkey key.pem -in certificate.pem -export -out certificate.p12 -CAfile caChain.pem -chain
실제 자세한 내용은 복잡하다고 하고, 이정도만 우선 알아도 될 것 같습니다.

2016년 1월 6일 수요일

ELF (Executable and Linkable Format)

자주 접해온 키워드인 ELF. 이참에 뭔지 좀 자세하게 알아봤습니다.

다음을 참고하여 정리합니다.

https://ko.wikipedia.org/wiki/ELF_%ED%8C%8C%EC%9D%BC_%ED%98%95%EC%8B%9D
http://egloos.zum.com/recipes/v/5010841
http://devanix.tistory.com/182

ELF(Executable and Linkable Format)는 실행 파일, 목적 파일, 공유 라이브러리 그리고 코어 덤프를 위한 표준 파일 형식입니다.

하나의 ELf 헤더와 파일 데이터로 이루어지며 다음을 포함합니다.

  • 0개 또는 그 이상의 세그먼트들을 정의하는 프로그램 헤더 테이블
  • 0개 또는 그 이상의 섹션들을 정의하는 섹션 헤더 테이블
  • 프로그램 헤더 테이블 또는 섹션 헤더 테이블의 엔트리들에 의해 참조되는 데이터
여기서 섹션과 세그먼트는 다음을 포함합니다.
  • 섹션: 링킹과 재배치에 필요한 중요 정보를 포함
  • 세그먼트: 파일의 런타임 실행에 필요한 정보를 포함
전체 파일의 어떤 바이트도 최대 1개의 섹션에 의해 소유되며, 섹션에 소유되지 않는 바이트도 존재할 수 있습니다.

위에서 기술한 프로그램 헤더 테이블과 섹션 헤더 테이블에 따라 ELf 파일은 두가지 관점을 가집니다. 프로그램 헤더는 런타임시 사용되는 세그멘트를, 섹션 헤더는 바이너리 섹션들의 집합을 나열합니다.


실제 헤더의 구조는 위키를 참고하세요. 
이중 ELF header는 다음과 같은 형태이며 일부만 기술합니다.
(Offset, 길이, 의미 순서입니다)
  • 0x00, 4, "0x7F 0x45 0x4C 0x45" 라는 매직넘버로 기술됩니다. ".ELF" 라는 의미 입니다.
  • 0x04, 1, 비트 정보로, 64bit or 32bit 인지를 알려줍니다.
  • 0x05, 1 , Big/Little Endian 구별을 해줍니다.
 .text와 .rodata는
  • .text: 실제 코드들이 들어갑니다. push, mov 등의 명령이 있습니다.
  • .rodata: Read-only Data segment로 읽기만 가능한 데이터 영역입니다. static이나 const 같은 값이 들어갑니다.

헤더 이하의 해석은 Link 전(.o)과 후의 관점에 따라 아래와 같습니다.




자세한 설명은 http://egloos.zum.com/recipes/v/5010841 를 참고하면 되고, 대략적으로 저런 모양이구나 하는 것을 확실하게 알아두면 좋을 것 같습니다. 


2016년 1월 5일 화요일

ASN.1 (Abstract Syntax Notation One)

문서를 뒤적이다가 알듯 말듯한 형태로 적혀있는 코드가 있었습니다. 알아보니 ANS.1 Type에 대한 이해가 필요하기에 여기저기서 습득한 자료를 바탕으로 정리해봅니다.

참고한 사이트는 다음과 같습니다.
http://girlsy7.tistory.com/129
https://support.microsoft.com/ko-kr/kb/252648
http://fly32.net/446

ITU-T에서 정의한 네트워크상의 데이터 교환을 정의한 프로토콜로 데이터 구조를 기술하는데 사용되는 표현 기법을 의미합니다.
특정 장치, 데이터 표현방식, 언어, 플랫폼에 종속되지 않습니다.
인코딩/디코딩 규칙까지 규정합니다.

좀 더 구체적으로 다음을 정의합니다.

  • "형식" 정의
  • "모듈" 정의 및 표시 방법
  • INTEGER 정의
  • BOOLEAN 정의
  • "구조체 형식" 정의
  • 특정 키워드(예: BEGIN, END, IMPORT, EXPORT, EXTERNAL) 의 의미
  • 적절히 인코딩할 수 있도록 형식을 "태그"하는 방법


이를 이용하여 서로 다른 환경을 가진 시스템끼리 통신하기에 적합합니다.

이 부분에 대해서 좀 더 설명을 하자면, 동일한 Data라고 해도 Window와 Sun Application에서의 해석은 다를 수 있습니다.
32 bit integer를 기준으로 '100'이라는 값은 Little Endian 으로 [64 00 00 00] 으로 인식되지만, Big Endian으로는 [00 00 00 64] 로 인식됩니다.
때문에 어떻게 인코딩을 해야 하는지도 확실히 정해줘야하는데 이것을 정의한 것이라고 생각하면 됩니다. (기술한 것과 같이 인코딩 이외에도 정의하는게 많습니다)

Abstract Syntax와 Transfer Syntax의 개념에 대한 이해가 필요한데,
Abstract Syntax는 C에서의 Type으로 이해하면 쉽고, Transfer Syntax는 위에 기술한 예제와 같이 어떻게 인코딩을 하는지를 기술하는 것으로 이해하면 됩니다. 이런 Transfer Syntax에는 BER(Basic Encoding Rules), PER(Packing Ending Rules), CER(Canonical Encoding Rules)등이 있습니다.
ITU-T 권고안 X.209, 부록 I의 예제를 바탕으로 BER과 ASN.1을 비교하겠습니다.

다음의 '직원 데이터 레코드'가 있습니다.

   Name:             John P Smith
   Date of Birth:    17 July 1959
   (other data)

이를 ASN.1 설명으로 기술하면 이렇습니다.

   PersonnelRecord ::= [APPLICATION 0] IMPLICIT SET {
       Name,
       title [0]       VisibleString,
       dateOfBirth [1]          Date,
       (other types defined)          }

   Name ::= [APPLICATION 1] IMPLICIT SEQUENCE {
       givenName       VisibleString,
       initial         VisibleString, 
       familyName      VisibleString  }     

응용 프로그램은 직원 데이터를 직원 레코드 구조(ASN.1 데이터 형식)에 매핑한 다음 ASN.1 데이터에 BER(Basic Encoding Rules)를 적용합니다. 이름이 ASCII로 변환되는 것을 제외하면 다음과 같이 표시됩니다.

Personnel
  Record     Length   Contents
  60         8185
                      Name     Length  Contents
                      61       10
                                       VisibleString  Length  Contents
                                       1A             04      "John"
                                       VisibleString  Length  Contents
                                       1A             01      "P"
                                       VisibleString  Length  Contents
                                       1A             05      "Smith"

                       DateofBirth     Length  Contents
                       A0              0A
                                               Date    Length  Contents
                                               43      08      "19590717"
     

모든 것이 표현되고 완료되면 실제 전송되는 데이터 부분은 다음과 같습니다.


60 81 85 61 10 1A 04 ....
....  0A 43 08 19 59 07 17