"기초부터 차근 차근"

 

요즘 개발자들이 AI에 많이들 관심을 가지는것 같습니다만 "perceptron"을 물어보면 모르는 경우가 대부분입니다. AI = tensorflow라는 개념을 가지고 있지요. 사실 요즘은 오픈소스 생태계가 너무도 잘되어 있어서 SRE적으로 접근해도 됩니다. 그런데 개념을 잡지 못하면 오래가지 못해서 한계에 부딧칠겁니다. 근데 요즘 나오는 Deep Learning 기법들부터 이해하려하면 .... 또 역시 포기하게 되겠지요. Neural Network개념은 1960년대에 나온 개념입니다. 8비트 컴터도 없던 시절이지요. 이때 개념들부터 이해해 나가면 종국에는 Deep Learning의 영역까지 확장할수있을꺼라 확신합니다.

 

AI (Artificial Intelligence)의 역사

1980년대 부터 개발을 하고 AI에 관심을 가져온 사람으로써 경험과 느낌들을 기반으로 기술한 역사로 "역사학" 공부하시는분들하고는 의견이 다를 수 있음을 밝힙니다. 

perceptron은 1950년대에 "Frank Rosenblatt"이 수학적으로 제안한 AI의 초기 모델로 다들 기본적으로 알고 있는 뉴런이라는 인간의 뇌의 구조에 기반한 아이디어로 정립된 이론입니다. 8비트 컴퓨터가 나오기도 전에 이미 수학적으로 AI를 설계했다는 얘기지요.

 

이번 포스팅은 지난 포스팅 [AI] 과거를 알고 미래가 보인다 - Perceptron(1)에 이어집니다. 

 

다층 퍼셉트론의 등장

1980년대 컴퓨팅 파워의 비약적인(?) 발전에 힘입어 단층 퍼센트론의 "선형학습"의 문제를 해결하고자 더 많은 "컴퓨팅 파워"을 사용하는 쪽으로 "neural network"이 발전하였고 결국은 복잡한 곡선 학습이 가능한 다층 퍼셉트론이 등장합니다.

 

입력과 출력사이에 히든레이어를 두어서 출력값을 다수의 노드에서 연결강도를 조정함으로써 "선형학습"문제를 해결하고 "XOR"문제를 해결합니다. 

 

이때 BPNN ( Back Propagation Neural Nework ), SOM (Self-Organizing Map)등을 부각되고 "지도학습", "비지도학습", "강화학습"등의 연구와 실제 응용 세례들이 등장합니다. finally는 "전문가 시스템" 즉 특정분야에서는 인간의 판단을 일부 대신할수있는 시스템을 만들수있지 않을까 하는 개념들이 등장합니다.

 

이때 활발히 상용화 되었던 것들이 "음성인식", "얼굴인식", "번호판인식"등의 제품들이 마구마구 나왔던것이 기억이 납니다.

 

그런데 이때 나왔던 제품들 정말 쓰레기에 가까웠습니다. 사실 정확한 판단을 위해서는 수많은 히든레이어가 필요하고 더많은 계층의 히든레이어가 필요했는데 그당시 장비들의 컴퓨팅파워는 그 계산량을 감당할수없었지요. ㅠ.ㅠ

 

이러한 상용화의 실패는 "제2의 AI 암흑기"를 불러옵니다.

시장은 더이상 AI에 대한 기대를 버리고 "시스템"과 "알고리즘"에 의존한 상용제품들을 내 놓고 IT산업을 발전 시킴니다.

 

클라우드의 등장

그디어 클라우드의 시대가 열립니다. 지금 신세대 개발자들에게는 당연한 환경이겠지만 10년~20년전 클라우드, 클러스터같은 개념이 소개되었을때 정말 적응하는데 힘들었던 기억이 납니다. 

 

각설하고 클라우드의 등장은 "무한한" 컴퓨팅 파워을 제공했고 이 무한한 컴퓨팅 파워를 바탕으로 Neural Network는 심각하게 복잡한 계산을 가능하게 했고 두둥~~~ "알파고"가 등장합니다.

 

Deep Learning의 등장

"무제한"의 컴퓨팅파워를 바탕으로 NN은 NN자체를 시간에 따라 복제하기 시작하고 히든레이어를 복제하고 이전 레이어로 역전파하기 시작했습니다. 과거의 컴퓨팅 파워로는 상상도 못하지만 지금은 가능한 시대가 되었습니다.

 

DNN ( Deep Neural Network ) 입니다. 그냥 딱봐도 어마어마한 계산량이지요.. 단순히 그린거지.. 상용화를 위해서는 하나의 히든레이에 수백 수천개의 노드가 필요합니다.

 

RNN ( recurrent neural network ) 입니다. 아싸리 입력에 따른 히든레이어를 복사해 나가고 가중치도 전파해 나갑니다. 계산량을 감히 상상도 할 수 없습니다.

 

마치면서

1편 부터 2편까지 모두 읽었다면 딥러닝이란것이 갑자기 어디서 툭 튀어 나온것도 아니고 굉장히 획기적인 신기술도 아니라는것을 알 수 있을것입니다. 시대의 흐름을 잘 읽은 사람들의 작품이라 하겠습니다. 그런데 개인적으로 AI분야에 많은 개발자들이 진입하려하는것에 좀 우려감이 있습니다. 왜냐하면 거기에는 여러분의 intelligence가 필요하지 않기 때문입니다. 

 

개인적으로는 Ai와도 연관되겠지만

  • Big Data Minning
  • Big Data Aggregating

분야가 AI와 발맞출수있는 올바른 방향이라 생각됩니다.

"기초부터 차근 차근"

 

요즘 개발자들이 AI에 많이들 관심을 가지는것 같습니다만 "perceptron"을 물어보면 모르는 경우가 대부분입니다. AI = tensorflow라는 개념을 가지고 있지요. 사실 요즘은 오픈소스 생태계가 너무도 잘되어 있어서 SRE적으로 접근해도 됩니다. 그런데 개념을 잡지 못하면 오래가지 못해서 한계에 부딧칠겁니다. 근데 요즘 나오는 Deep Learning 기법들부터 이해하려하면 .... 또 역시 포기하게 되겠지요. Neural Network개념은 1960년대에 나온 개념입니다. 8비트 컴터도 없던 시절이지요. 이때 개념들부터 이해해 나가면 종국에는 Deep Learning의 영역까지 확장할수있을꺼라 확신합니다.

 

AI (Artificial Intelligence)의 역사

1980년대 부터 개발을 하고 AI에 관심을 가져온 사람으로써 경험과 느낌들을 기반으로 기술한 역사로 "역사학" 공부하시는분들하고는 의견이 다를 수 있음을 밝힙니다. 

 

perceptron은 1950년대에 "Frank Rosenblatt"이 수학적으로 제안한 AI의 초기 모델로 다들 기본적으로 알고 있는 뉴런이라는 인간의 뇌의 구조에 기반한 아이디어로 정립된 이론입니다. 8비트 컴퓨터가 나오기도 전에 이미 수학적으로 AI를 설계했다는 얘기지요.

 

Neural Network (단층 percentron)

Neural Network의 가장 단순한 단층 percent 의 원리부터 이해해 볼께요.

Neural Network는 몇개의 입력을 Node와 연결하고 원하는 결과가 나올때 까지 연결강도를 조정하면서 결과값을 찾아 갑니다.

여러가지 수학적인것들이 나오는데 우리는 SRE적으로 이해하고 가져다 씁시다. ( 수학적으로 이해 하려고 하면 ... 흥미가 떨어져요 )

 

초심풀한 2개의 입력과 1개의 출력을 가지는 percentron을 만들었습니다.

class neural{
private:
    const float lr = 0.1;
public:
    float wx;
    float wy;
    float bias;
public:
    neural(){
        bias = -1;;
        wx = 0.5;
        wy = 0.5;
    }
    void train(int x, int y, int result){
        auto output = recall(x, y);
        auto error = result - output;
        
        wx += error * x * lr;
        wy += error * y * lr;

        bias += lr * error;
    }
    int recall(int x, int y){
        double sum = 0.0;
        
        sum += wx * x;
        sum += wy * y;
        
        return 1 / (1 + std::exp(-(sum + bias)));
    }
};

자 방금 우리는 역사적인 "인공지능"을 만들었습니다.

 

이제 이 인공지능에게 AND연산을 가르쳐 봅시다.

void train(const string & title, vector<vector<int>> data){
    neural n;
    for(int i=0;i<10000;i++){
        for(int j=0;j<data.size();j++){
            n.train(data[j][0], data[j][1], data[j][2]);
        }
    }

    cout << "< " << title << " --->" << endl;
    for(int j=0;j<data.size();j++){
        auto output = n.recall(data[j][0], data[j][1]);
        cout << "[" << data[j][0] << "," << data[j][1] << "] = " << output;
        if(output == data[j][2])
            cout << " <-- OK";
        else
            cout << " <-- FAIL, expected " << data[j][2];
        cout << endl;
    }
    cout << "neural : wx = " << n.wx << ", wy  = " << n.wy << ", bias = " << n.bias << endl;
}

int main(int argc, const char * argv[]) {
    train("AND", {
        {0, 0, 0},
        {0, 1, 0},
        {1, 0, 0},
        {1, 1, 1}
    });

    return 0;
}

결과는

< AND --->
[0,0] = 0 <-- OK
[0,1] = 0 <-- OK
[1,0] = 0 <-- OK
[1,1] = 1 <-- OK
neural : wx = 12.8, wy  = 12.8, bias = 11.3

우리는 어떤 로직도 심지 않고 심지어 AND연산의 어떤 연관관계도 분석하지 않았지만 neural network이 연결강도와 bias를 조정해 가면서 답을 스스로 찾고 있습니다.

 

이 인공지능에 몇까지를 더 가르쳐 봅시다.

OR NOR NAND
    train("OR", {
        {0, 0, 0},
        {0, 1, 1},
        {1, 0, 1},
        {1, 1, 1}
    });
    train("NOR", {
        {0, 0, 1},
        {0, 1, 0},
        {1, 0, 0},
        {1, 1, 0}
    });
    train("NAND", {
        {0, 0, 1},
        {0, 1, 1},
        {1, 0, 1},
        {1, 1, 0}
    });
[0,0] = 0 <-- OK
[0,1] = 1 <-- OK
[1,0] = 1 <-- OK
[1,1] = 1 <-- OK
neural : wx = 14.7, wy  = 14.7, bias = 22.2
[0,0] = 1 <-- OK
[0,1] = 0 <-- OK
[1,0] = 0 <-- OK
[1,1] = 0 <-- OK
neural : wx = -0.1, wy  = -0.1, bias = 36.8
[0,0] = 1 <-- OK
[0,1] = 1 <-- OK
[1,0] = 1 <-- OK
[1,1] = 0 <-- OK
neural : wx = -0.2, wy  = -0.0999999, bias = 37

훌륭히 배워가고 있습니다.

어짜피 다 가르쳐주고 답을 주는거라고 생각할수있는데 입력값이 수천/수만이 될경우를 생각해 봅시다.

 

선형학습문제

퍼셉트론은 상당한 반향을 이끌었습니다만 "선형학습"이라는 한계를 드러내며 사람들의 기억속에서 사라집니다. 심지어 "민스키와 패퍼트"라는 "퍼셉트론"이 책을 발간하면서 수학적으로 한계를 증명해 버렸다.

결국은 "AND", "OR", "NOR", "NAND"등은 선으로 두분류로 나눌수있지만 마지막 그림처럼 "XOR"처럼 선으로 2분류로 나눌 수 없는 경우는 학습할 수가 없는 문제가 있다. 

 

이걸 한번 우리 "인공 지능"에게 학습시켜 봅시다.

XOR
    train("XOR", {
        {0, 0, 0},
        {0, 1, 1},
        {1, 0, 1},
        {1, 1, 0}
    });
[0,0] = 1 <-- FAIL, expected 0
[0,1] = 1 <-- OK
[1,0] = 0 <-- FAIL, expected 1
[1,1] = 0 <-- OK
neural : wx = -0.0999999, wy  = 1.3411e-07, bias = 36.8

아무리 학습을 시켜도 답을 못 찾는다. 

 

다중 퍼셉트론의 등장

당시 열악한 컴퓨팅환경에서 단층퍼셉트도 입력층이 많아지게되면 상당한 부담이었고 AI연구는 침체기에 빠집니다. 그런데 16비트가 PC가 나오고 서버시장은 더욱 더 빠르게 발전하고 가성비 높은 컴퓨팅 환경이 갖춰지게 되고 "다중 퍼센트론"이 주목받게 되고 속속 연구가 이루어 지며 "전문가시스템"이라는 개념이 등장하는등 제2의 AI이 붐이 일어 납니다.

 

너무 길어 기는 관계로 다음 포스트에서 이어 가겠습니다.

세계인의 타임킬링게임 "지뢰찾기"게임에서 특정 블럭을 눌렀을때 막혀있지 않은 최대 면접을 구하여 OPEN해줄때 사용할 수 있는 면적구하기 알고리즘 입니다. Graph, DFS에 관련해서는 미로찾기(graph traversal)을 먼저 읽고 오면 도움이 됩니다.

 

 

이전 포스트에서 언급한바와 같이 Graph - DFS ( Depth First Search ) 알고리즘은 완전 탐색 즉 모든 노드를 방문할 수 있는 알고리즘입니다. 이 알고리즘은 조금 응용해서 방문할 수 있는 모든 노드를 방문하면 결국은 방문수가 해당 node group의 넓이가 됩니다.

 

 문제

아래와 같은 지도가 존재할때 빈칸으로 이루어진 영역중 최대 크기 영역의 크기를 구해봅시다.

 

사전 정의

다음의 설명들에 사용된 코드들은 아래와 공통 코드들은 미로찾기(graph traversal)의 공통코드들을 재활용합니다.

 

최대 넓이 구하기

일단 DFS함수를 하나 만들고 각 노드별로 넓이를 구합니다 ( 방문노드의 합 ), 이때 방문한 노드는 넓이를 구하지 않습니다.

int dfs_area(vector<vector<int>> & map, vector<vector<int>> & visited, int x, int y){
    int theWays = 1;
    
    visited[y][x] = 1;
    
    if(moveable(map, visited, x-1, y))
        theWays += dfs_area(map, visited, x-1, y); // left
    if(moveable(map, visited, x+1, y))
        theWays += dfs_area(map, visited, x+1, y); // right
    if(moveable(map, visited, x, y-1))
        theWays += dfs_area(map, visited, x, y-1); // up
    if(moveable(map, visited, x, y+1))
        theWays += dfs_area(map, visited, x, y+1); // down
    
    return theWays;
}

int main(int argc, const char * argv[]) {
    
    vector<vector<int>> map = {
        {0, 0, 0, 1, 1, 0, 0},
        {0, 0, 0, 1, 1, 0, 0},
        {0, 1, 1, 1, 1, 1, 1},
        {1, 0, 0, 1, 0, 0, 0},
        {1, 0, 0, 1, 0, 0, 0},
    };
    
    print_map("MAP", map);
    cout << endl;
    
    vector<vector<int>> visited(map.size(), vector<int>(map[0].size()));
    
    int max_area = 0;
    // 모든 노드의 최대 넓이를 구합니다.
    for(int y=0;y<map.size();y++){
        for(int x=0;x<map[0].size();x++){
            if(map[y][x] == 1) continue;
            if(visited[y][x] == 1) continue;
            
            auto area = dfs_area(map, visited, x, y);
            if(max_area < area) max_area = area;
        }
    }
    
    cout << "Max Area Size : " << max_area << endl << endl;
    
    print_map("VISTED", visited);
    
    return 0;
}

 

출력값은

결과 방문지

지도상에 방문할수있는 모든 노드를 방문하였고 최대 크기 area도 찾아 냈습니다.

 

지뢰찾기

이 프로그램을 지뢰찾기에 응용해 봅시다.

사용자가 (1, 1) 좌표 를 클릭했다 하면 main program만 조금 수정합니다.

int main(int argc, const char * argv[]) {
    
    vector<vector<int>> map = {
        {0, 0, 0, 1, 1, 0, 0},
        {0, 0, 0, 1, 1, 0, 0},
        {0, 1, 1, 1, 1, 1, 1},
        {1, 0, 0, 1, 0, 0, 0},
        {1, 0, 0, 1, 0, 0, 0},
    };
    
    print_map("MAP", map);
    cout << endl;
    
    vector<vector<int>> visited(map.size(), vector<int>(map[0].size()));
    
    int x = 1, y = 1;
    auto area = dfs_area(map, visited, x, y);
    cout << "Area Size from (" << x << "," << y << ") is " << area << endl << endl;
    
    print_map("VISTED", visited);
    
    return 0;
}
결과 방문지

(1,1) 좌표를 클릭했을때 속해 있는 area의 넓이를 구했고 방문지 노드도 모두 구했다.

 

이제!!! 지뢰게임을 만들어 보시라...

 

아래에 full source를 올려둡니다. 여러가지 테스트 해봅시다.

더보기
//
//  main.cpp
//  area
//
//  Created by Jack Kim on 2023/06/09.
//

#include <iostream>
#include <vector>
using namespace std;

class location{
public:
    int x;
    int y;
    shared_ptr<location> prev;
public:
    location(int x, int y, shared_ptr<location> prev)
    : x(x), y(y), prev(prev) {}
};

bool moveable(vector<vector<int>> & map, vector<vector<int>> & visited, int x, int y){
    if(x < 0 || x > map[0].size()-1) return false;
    if(y < 0 || y > map.size()-1) return false;
    if(map[y][x] != 0) return false;
    if(visited[y][x] != 0) return false;
    
    return true;
}

void print_map(const string & title, vector<vector<int>> & map){
    int width = (int)map[0].size();
    int height = (int)map.size();
        
    cout << "[" << title << "]" << endl;
    for(int x=0;x<width*2+1;x++)cout << "─"; cout << endl;
    for(int y=0;y<height;y++){
        cout << "│";
        for(int x=0;x<width;x++){
            string ch = " ";
            switch(map[y][x]) {
                case 1: ch  = "▒"; break;
                case 0: ch  = " "; break;
                case 9: ch  = "+"; break;
            }
                        
            cout << ch ;
            if(x != width-1)
                cout << " ";
        }
        cout << "│" << endl;
    }
    for(int x=0;x<width*2+1;x++)cout << "─"; cout << endl;
}

int dfs_area(vector<vector<int>> & map, vector<vector<int>> & visited, int x, int y){
    int theWays = 1;
    
    visited[y][x] = 1;
    
    if(moveable(map, visited, x-1, y))
        theWays += dfs_area(map, visited, x-1, y); // left
    if(moveable(map, visited, x+1, y))
        theWays += dfs_area(map, visited, x+1, y); // right
    if(moveable(map, visited, x, y-1))
        theWays += dfs_area(map, visited, x, y-1); // up
    if(moveable(map, visited, x, y+1))
        theWays += dfs_area(map, visited, x, y+1); // down
    
    return theWays;
}

int main(int argc, const char * argv[]) {
    
    vector<vector<int>> map = {
        {0, 0, 0, 1, 1, 0, 0},
        {0, 0, 0, 1, 1, 0, 0},
        {0, 1, 1, 1, 1, 1, 1},
        {1, 0, 0, 1, 0, 0, 0},
        {1, 0, 0, 1, 0, 0, 0},
    };
    
    print_map("MAP", map);
    cout << endl;
        
    // 최대 넓이 영역 크기 구하기
    {
        vector<vector<int>> visited(map.size(), vector<int>(map[0].size()));
        
        int max_area = 0;
        // 모든 노드의 최대 넓이를 구합니다.
        for(int y=0;y<map.size();y++){
            for(int x=0;x<map[0].size();x++){
                if(map[y][x] == 1) continue;
                if(visited[y][x] == 1) continue;
                
                auto area = dfs_area(map, visited, x, y);
                if(max_area < area) max_area = area;
            }
        }
        
        cout << "Max Area Size : " << max_area << endl << endl;
        
        print_map("VISTED", visited);
    }
    
    // 특정지점에서 넓이 구하기 (1,1)
    {
        vector<vector<int>> visited(map.size(), vector<int>(map[0].size()));

        int x = 1, y = 1;
        auto area = dfs_area(map, visited, x, y);
        cout << "Area Size from (" << x << "," << y << ") is " << area << endl << endl;

        print_map("VISTED", visited);
    }

    
    return 0;
}

대부분 CLI나 Visual Studio Code를 Rust용 IDE로 추천하고 있는데 국민 IDE IntelliJ에서 Rust를 시작하는 방법을 포스팅합니다.

CLI로 해도 불편함이 없지만 우리는 IDE, 자동화를 사랑하니까 ~~~ Intellisence없이 개발하는건 이제는... 바~~보

 

일단 rust가 깔려있고 CLI 환경은 마친것으로 봅니다. ( 이것도 곧 포스팅 )

 

일단 Rust Plugin을 설치합니다.

 

[New Project]를 누르면 Rust항목이 추가됩니다.

 

Binary 를 선택하고 next를 누르고 프로젝트 이름을 설정하고 finish를 누룹니다.

 

오~~~ Rust 개발 환경이 다 만들어 졌습니다.

 

일단 실행해 봅시다.

CLI를 써도 되지만 우린 클릭질 좋아하니까~~~

우리 카카오 프랜즈가 손흔들고 있는 버튼을 누르자~~~

 

아무것도 안했는데 Hello World가 출력되었습니다.

 

Intelligence 기능만 소개하고 마치겠습니다.

Json 처리를 위하여 serde_json을 추가해 보겠습니다.

Cargo.toml를 얼고 [dependencies]에 serde라고 치자마자 추천 목록이 뜹니다. (엄지척!)

 

이름을 완성하고 "="을 누르자마자 버전도 추천해 줍니다. 

버전을 선택하면 자동으로 crate를 가져다가 설치해 줍니다. (그림에 에러는 무시하세요)

 

다시 main.rs를 열어봅니다.

se를 치자마자 serde_json이 추천됩니다.

 

json object를 만들고 돌려봅시다.

클릭 몇번으로 rust 개발환경을 마쳤습니다.

이제 Rust에 빠져봅시다.

 

다음번에는 rust cli환경을 구축해 볼께요.

C++에서 Rust로 넘어가기 위한 C++ 개발자들을 위한 개념들을 설명합니다.
항상 맨처음에 "개념" 하나씩 쓰고 시작하는데 C++에서 Rust로 넘어가는데 알아야할 명제 입니다.

C++의 class ( struct )와 비슷한 개념이 rust의 struct입니다. 이 두개를 비교하면서 Rust로 열심히 넘어가 보겠습니다.

 

개념 : "C++의 class (struct)의 거의 모든 개념은 Rust의 struct로 구현 가능하다."

 

일단 바로 예제 코드 갑니다.

 

아래 예제는 

  • static function에서 FileWriter를 create하여 instance를 생성합니다.
  • create() 함수에서 파일을 엽니다.
  • write() 함수로 내용을 씁니다.
  • 소멸자에서 파일을 닫습니다.
C++ Rust
#include <iostream>
using namespace std;


struct FileWriter{
    FILE * fp;
    
    static FileWriter create();
    void write();
    void read();
    ~FileWriter();
};


FileWriter FileWriter::create(){
    FILE * fp  = fopen("test.txt", "wt");
    cout << "file opened" << endl;
    return FileWriter{ fp = fp };
}


void FileWriter::write(){
    fprintf(fp, "test data");
    cout << "written data" << endl;
}


FileWriter::~FileWriter(){
    fclose(fp);
    cout << "file closed" << endl;
}




int main(int argc, const char * argv[]) {
    auto writer = FileWriter::create();
    writer.write();
    
    return 0;
}
use std::ffi::CString;


struct FileWriter{
    fd: *mut libc::FILE
}


impl FileWriter {
    fn create() -> FileWriter{
        let fd: *mut libc::FILE = unsafe {
            let file_name = CString::new("test.txt").unwrap();
            let file_attr = CString::new("w").unwrap();


            libc::fopen(file_name.as_ptr(), file_attr.as_ptr())
        };


        FileWriter{ fd }
    }
    fn write(&mut self){
        unsafe {
            let data = CString::new("test data").unwrap();
            libc::fprintf(self.fd, data.as_ptr())
        };
    }
}


impl Drop for FileWriter{
    fn drop(&mut self) {
        unsafe {
            libc::fclose(self.fd);
        }
    }
}


fn main() {
    let writer = FileWriter::create();
    writer.write();
}
file opened
written data
file closed

file opened
written data
file closed

rust예제의 경우 좀 억지감이 있지만 C++과 똑같이 만들기 위해 좀 억지를 부렸습니다.

머 여기서 좋은 점도 알게 되었습니다. "Rust 에서는 왼만한 C/C++ 함수들을 바로 가져다 쓸 수 있구나"

 

이 예제에서 알 수 있는것들은

  • rust struct는 method ( member function ) 이 없다.
  • impl를 통하여 method를 구현한다. ( c++의 class::method 패턴과 비슷하지요? )
  • rust는 생성자가 없어서 factory pattern을 사용한다.
  • rust을 Drop trait를 구현하여 소멸자 동작을 구현 할수있다.

 

다음번에는 Rust의 에러처리에 대해 알아보겠습니다.

C++에서 Rust로 넘어가기 위한 C++ 개발자들을 위한 개념들을 설명합니다.
항상 맨처음에 "개념" 하나씩 쓰고 시작하는데 C++에서 Rust로 넘어가는데 알아야할 명제 입니다.

 

Rust의 주요 특징중에 "소유권 이전"이 최대의 특징으로 꼽힐껍니다. 이걸 C++과 비교 해 보겠습니다.

 

개념 "C++은 Copy가 기본 동작이고 Rust는 Move가 기본동작이다." 

 

Primitive

C/C++, Java, Scala, Rust 거의 모든 지상의 언어들을 통털어서 copy가 기본이고 rust 역시도 copy가 기본입니다.

C++ Rust
int main(int argc, const char * argv[]) {
    auto a = 10;
    auto b = a;
    auto c = a;
    
    return 0;
}
fn main() {
    let a = 10;
    let b = a;
    let c = a;
}

a라는 변수에 10을 넣은 후 b,c라는 변수에 동시에 넣었지만 정상 동작한다.

 

Object 

C/C++은 Copy, Reference, Move가 가능하고 Java는 무조건 Reference지만 Rust는 Move기 기본입니다.

C++ Rust
int main(int argc, const char * argv[]) {
    auto a = 10;
    auto b = a;
    auto c = a;
    
    return 0;
}
fn main() {
    let a = 10;
    let b = a;
    let c = a;
}
[SUCESS] 3 |     let a = String::from("string");
  |         - move occurs because `a` has type `String`, which does not implement the `Copy` trait
4 |     let b = a;
  |             - value moved here
5 |     let c = a;
  |             ^ value used here after move

C++은 b, c에 a가 copy되어 문제없이 들어가지만 Rust는 a의 소유권이 b로 넘어간후 a를 다시 사용하였기 때문에 에러가 발생합니다.

 

여기까지는 다른 블로그에서도 많이 하는 얘기고.. C++을 Rust와 같이 바꿔 보겠습니다.

C++ Rust
int main(int argc, const char * argv[]) {
    auto a = std::unique_ptr<std::string>(new std::string("string"));
    auto b = std::move(a);
    auto c = a;
    
    return 0;
}
fn main() {
    let a = 10;
    let b = a;
    let c = a;
}
Call to implicitly-deleted copy constructor of 'std::unique_ptr<std::string>' 3 |     let a = String::from("string");
  |         - move occurs because `a` has type `String`, which does not implement the `Copy` trait
4 |     let b = a;
  |             - value moved here
5 |     let c = a;
  |             ^ value used here after move

아시는바와 같이 std::unique_ptr은 copy operator를 삭제하여 copy동작을 막은 템플릿입니다. 소유권이 하나의 인스턴스에만 유지하도록 하는 helper class입니다. 

 

Rust은 기본이 unique value이므로 위와 같이 하면 rust와 똑같이 동작합니다.

 

C++만 Rust형으로 바꿔보는건 재미가 없으니 Rust를 C++로도 바꿔 보겠습니다. 일단 rust에서만 에러나는 케이스입니다

C++ Rust
struct testStruct{
    int value;
};


int main(int argc, const char * argv[]) {
    testStruct a = { 10 };
    auto b = a;
    auto c = a;
    
    return 0;
}
struct TestStruct{
    a:u16
}


fn main() {
    let a = TestStruct{ a: 10 };
    let b = a;
    let c = a;
}
[SUCESS] 7 |     let a = testStruct{ a: 10 };
  |         - move occurs because `a` has type `TestStruct`, which does not implement the `Copy` trait
8 |     let b = a;
  |             - value moved here
9 |     let c = a;
  |             ^ value used here after move

당연하게도 struct도 rust는 move가 기본이기에 move된 변수를 사용하였다는 에러가 발생합니다.

testStruct가 move가 기본이기 때문입니다. testStruct의 기본동작을 copy로 바꿔봅시다.

C++ Rust
struct testStruct{
    int value;
};


int main(int argc, const char * argv[]) {
    testStruct a = { 10 };
    auto b = a;
    auto c = a;
    
    return 0;
}
#[derive(Copy, Clone)]
struct TestStruct{
    a:u16
}


fn main() {
    let a = TestStruct{ a: 10 };
    let b = a;
    let c = a;
}
[SUCESS] [SUCESS]

Rust도 Copy함수를 구현했더니 C++처럼 동작합니다.

 

정리

간단히 rust의 의도를 파악해 보자면

  • object의 기본동작으로 move이므로 memory deallocation 시점을 정확히 잡을수있다.
  • 의도하지 않는 copy가 일어나서 memory가 낭비되지 않도록한다 ( copy, clone이 필요할때는 의도적으로 coding 해라 )

사실 처음에 코드를 작성할때는 헷갈렸지만 자꾸 작성하다보면 변수의 scope이 심각하게 명확하고 function, thread등에서 공유되는 변수들의 범위와 scope가 정말 명확하게 보입니다( 그렇기 때문에 의도하지 않은 변수공유, 스코프가 벗어난 변수 참조등을 원천적으로 막아 안전한 프로그래밍이 가능합니다)

 

다음시간에는 C++ struct (class)와 Rust struct를 비교해 보겠습니다.

 

과거에 서해대고 계측관리에 사용했던 framework ( 물리엔진 )입니다. 사용하기 쉽고 수 많은 예제와 거의 모든 기능을 담고 있습니다. 요즘은 Unreal이 대세지만 수십년을 꾸준히 관리되어온 프로젝트인만큼 초심자에게는 좋은 자료가 될듯합니다.

 

https://irrlicht.sourceforge.io/

 

Irrlicht Engine - A free open source 3D engine

IMP – Irrlicht Music Player is a music player. Unique in the world, of its own kind. Probably the most expensive CPU music player too, but its all for our fun! Uses the latest Irrlicht 1.8.4. Mostly done with code and help came from irrlicht forums and c

irrlicht.sourceforge.io

 

실제 응용 프록젝트는 여기를 참조하세요

 https://dooli98.tistory.com/m/3

 

Direct 3D을 이용한 실시간 건축물 관제

서해대교 계측관제 (Visual C++, Direct3D) 3D로 관련 건축물을 모델링하고 각 부분에 설치된 관측 장비로 부터 수신된 데이타를 통합 표시하고 다리의 뒤틀림과 움직임을 보여준다. 서해대교의 전체

dooli98.tistory.com

 

SSL, TLS관련 글들을 검색해 보면 모두다 HTTP기반의 웹 보안기술이라고 이해 하는듯하다. 그렇다면 SSH? 이넘아도 TLS를 사용하는데? TCP, SOCKET통신에도 TLS를 사용하는데?

 

SSL이든 TLS든 OSI 7 Layer중 4 Layer 즉 Transport Layer에 해당하는 보안기술이다. 즉 TCP, UDP, HTTP등이 여기에 속한다.

 

그래서 SSL(Secure Sockets Layer)무었인가? 지금 개발자들은 모르겟지만 windows 95시절에는 전세계의 거의 모든 사람들이 Nascape라는 브라우저를 사용했었다. IE를 주로 사용하던 시절도 있었고...요즘은 크롬을 많이 사용하지요?

 

여튼 Nascape라는 회사가 1995년에는 지금의 구글같은 존재였기에 인터넷 기술도 선도했는데 그때 만든것이 SSL 이다.

그러나 갖가지 보안이슈들이 발현되면서 SSL2, SSL3가 나오면서 대세가 되었습니다. 

 

그런데 MS가 이 회사를 가만둘리가 없지요? MS가 IE를 Windows 에 끼워팔기 시작하면서 급격히 망하기 시작하여 더이상 SSL를 관리하는 사람이 없게 됩니다. 

 

그래서 국제표준기구에서 SSL3을 가져다가 TLS(Transport Layer Security)라는 이름으로 발표합니다.

현재는 v1.2, v1.3까지 나와서 사용되고 있지요... 

 

현재는 SSL은 사용을 권장하고 있지 않고 SSL사용시 보안이슈로 발현되고 있습니다.

 

그러나 아직도 많은 사람들이 SSL이라는 용어를 사용하고 있습니다. 이유는 SSL과 TLS가 사용상에 큰 차이점이 없고 SSL이라는 용어자체가 너무 널리 사용되다보니 헷갈리는듯합니다.

 

이제는 TLS라는 용어를 사용합시다.

 

TLS handshake나 관련 내용은 PKI ( Private Key Infrastructure ) + X.509 + SSL 이글에서 조금 언급해 놓았습니다.

'architecture' 카테고리의 다른 글

OSI 7 Layer Network Model  (0) 2023.06.10
CQRS ( Command Query Responsibility Segregation)  (0) 2023.06.04
PKI ( Private Key Infrastructure ) + X.509 + SSL  (0) 2023.05.31
Proxy ( Forward & Reverse )  (0) 2023.05.31
Agile(Scrum)에 대한 ...  (0) 2023.05.11

+ Recent posts