오브젝트 리뷰 - 4

기술/아키텍처 2021. 7. 25. 15:56 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

 

메시지와 인터페이스 

 

협력과 메시지

 

메시지는 객체 사이의 협력을 가능하게 하는 매개체다. 객체가 다른 객체에게 접근할 수 있는 유일한 방법은 메시지를 전송하는 것뿐이다. 전통적인 방법은 클라이언트-서버 모델이다. 메시지를 전송하는 객체를 클라이언트, 메시지를 수신하는 객체를 서버라 부르며 협력은 클라이언트가 서버의 서비스를 요청하는 단방향 상호작용이다. (p176)

 

객체가 독립적으로 수행할 수 있는 것보다 더 큰 책임을 수행하기 위해서는 다른 객체와 협력해야 한다. 두 객체 사이의 협력을 가능하게 해주는 매개체가 바로 메시지다. (p177)

 

메시지는 객체들이 협력하기 위해 사용할 수 있는 유일한 의사소통 수단이다. 한 객체가 다른 객체에게 도움을 요청하는 것을 메시지 전송 또는 메시지 패싱이라고 부른다. 이때 메시지를 전송하는 객체를 메시지 전송자라고 부르고 메시지를 수신하는 객체를 메시지 수신자라고 부른다. 메시지는 오퍼레이션명과 인자로 구성되며 메시지 전송은 여기에 메시지 수신자를 추가한 것이다. (p177)

 

메시지를 수신했을 때 실제로 실행되는 함수 또는 프로시저를 메서드라 부른다. 코드 상에서 동일한 이름의 변수에게 동일한 메시지를 전송하더라도 객체의 타입에 따라 실행되는 메서드가 달라질 수 있다. 객체는 메시지와 메서드라는 두 가지 서로 다른 개념을 실행 시점에 연결해야 하기 때문에 컴파일 시점과 실행 시점의 의미가 달라질 수 있다. 이런 메시지와 메서드의 구분은 전송자와 메시지 수신자가 느슨하게 결합될 수 있게 한다. (p178)

 

객체가 의사소통을 위해 외부에 공개하는 메시지의 집합을 퍼블릭 인터페이스라고 부른다. 프로그래밍 언어 관점에서 퍼블릭 인터페이스에 포함된 메시지를 오퍼레이션이라고 부른다. 그에 비해 메시지를 수신했을 때 실제로 실행되는 코드는 메서드라고 부른다. (p179)

 

오페레이션의 이름과 파라미터 목록을 합쳐 시그니처라고 부른다. 오퍼레이션은 실행 코드 없이 시그니처만을 정의한 것이고 메서드는 이 시그니처에 구현을 더한 것이다. (p180)

 

인터페이스와 설계 품질 

 

좋은 인터페이스는 최소한의 인터페이스와 추상적인 인터페이스라는 조건을 만족해야한다 (p181)

 

디미터 법칙은 객체 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한한다. 오직 하나의 도트만 사용하라는 말로 요약되기도 한다. 디미터 법칙을 따르기 위해서는 클래스가 특정한 조건을 만족하는 대상에게만 메시지를 전송하도록 프로그래밍 해야한다. 디미터 법칙을 따르면 부끄럼타는 코드를 작성할 수 있다. 부끄럼타는 코드란 불필요한 어떤 것도 다른 객체에게 보여주지 않으며 다른 객체의 구현에 의존하지 않는 코드를 의미한다. (p183, 184, 185)

 

디미터 법칙을 만족하지 않은 경우 내부 구조에 대해 물어보고 반환받은 요소에 대해 연쇄적으로 메세지를 전송하는 경우가 생기는데 이런 경우를 기차 충돌(train wreck)이라 한다. 기차 충돌은 클래스의 내부 구현이 외부로 노출됐을 때 나타나는 전형적인 형태다. (p186)

 

훌륭한 메시지는 객체의 상태에 관해 묻지 말고 원하는 것을 시켜야한다. 묻지 말고 시켜라는 이런 스타일의 메시지 작성을 장려하는 원칙이다. 객체의 정보를 이용하는 행동을 객체의 외부가 아닌 내부에 위치시키기 때문에 자연스럽게 정보와 행동을 동일한 클래스 안에 두게 되고 자연스럽게 정보 전문가에게 책임을 할당하게 되고 높은 응집도를 가진 클래스를 얻게 된다 (p186, 187)

 

메서드 이름을 짓는 두번째 방법은 '어떻게'가 아니라 '무엇'을 하는지를 드러내는 것이다. 무엇을 하는지를 드러내는 이름은 코드를 이해하기 쉽고 유연한 코드를 낳는 지름길이다. 무엇을 하는지 드러내도록 메서드의 이름을 짓기 위해서는 객체가 협력 안에서 수행해야 하는 책임에 관해 고민해야한다. 이처럼 무엇을 하느냐에 따라 메서드의 이름을 짓는 패턴을 의도를 드러내는 선택자라고 부른다. (p188, 189, 190)

 

명령 - 쿼리 분리 원칙 

 

어떤 절차를 묶어 호출하도록 이름을 부여한 기능 모듈을 루틴이라 부른다. 루틴은 프로시저와 함수로 구분 되는데 프로시저는 정해진 절차에 따라 내부 상태를 변경하는 루틴이며 함수는 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 종류이다. 명령은 객체의 인터페이스 측면에서 프로시저에 해당하고, 쿼리는 함수에 해당한다. (p202)

 

명령-쿼리 분리 원칙의 요지는 오퍼레이션은 부수효과를 발생시키는 명령이거나 부수효과를 발생시키지 않는 쿼리 중 하나여야 한다는 것이다. 어떤 오퍼레이션도 명령인 동시에 쿼리여서는 안된다. 명령은 상태를 변경할 수 있지만 상태를 반환해서는 안되며 쿼리는 객체의 상태를 반환할 수 있지만 상태를 변경해서는 안된다. (p203)

 

명령-쿼리 분리 원칙은 부수효과를 가지는 명령으로부터 부수효과를 가지지 않는 쿼리를 명백하게 분리함으로써 제한적이나마 참조 투명성의 혜택을 누릴 수 있게 해준다 (p213)

 

객체 분해

 

프로시저 추상화와 데이터 추상화 

 

현대적인 프로그래밍 언어를 특징 짓는 중요한 두 가지 추상화 메커니즘은 프로시저 추상화와 데이터 추상화다. 프로시저 추상화는 소프트웨어가 무엇을 해야하는지를 추상화하고 테이터 추상화는 소프트웨어가 무엇을 알아야 하는지를 추상화한다. (p218)

 

프로시저 추상화를 중심으로 시스템을 분해하기로 결정했다면 기능분해(functional decomposition)의 길로 들어서는 것이다. 기능 분해는 알고리즘 분해라고 부르기도 한다. 데이터 추상화를 중심으로 시스템을 분해하기로 결정했다면 데이터를 중심으로 타입을 추상화(type abstraction)하는 방향과 데이터를 중심으로 프로시저를 추상화 하는 방법(procedure abstraction)중 하나를 선택한다 (p218)

 

프로시저 추상화와 기능 분해

 

전통적인 기능 분해 방법은 하향식 접근법을 따른다. 하향식 접근법이란 시스템을 구성하는 가장 최상위 기능을 정의하고 좀 더 작은 단계의 하위 기능으로 분해해가는 방법을 말한다. (p219)

 

하향식 접근법과 기능분해는 근본적으로 변경에 취약한 설계를 갖는다. 메인 함수를 유일한 정상으로 간주하는 하향식 기능 분해의 경우에는 새로운 기능을 추가할 때마다 매번 메인 함수를 수정해야 한다. (p226, 227)

 

하향식 접근법은 비즈니스 로직을 설계하는 초기 단계부터 입력방법과 출력 양식을 함께 고민하도록 강요한다. 그러나 사용자 인터페이스는 시스템 내에서 가장 많은 변경이 발생하는 부분이므로 수정이 발생하면 상대적으로 변경이 적은 비즈니스 로직까지 영향을 미치게 된다. 근본적으로 변경에 불안정한 아키텍처가 만들어진다. (p229)

 

하향식 접근법의 경우 처음부터 구현을 염두에두기 때문에 자연스럽게 함수들의 실행 순서를 정의하는 시간 제약을 강조한다. 분해를 진행할 수 없어 기능 분해 방식은 중앙집중 제어스타일의 형태를 띨 수 밖에 없다. 이를 해결하기 위해선 시간 제약에 대한 미련을 버리고 좀 더 안정적인 논리적 제약을 설계 기준으로 삼아야 한다. 객체 지향은 삼수 간의 호출 순서가 아니라 객체 사이의 논리적인 관계를 중심으로 설계를 이끌어간다. (p230)

 

모듈 

 

시스템을 모듈 단위로 분해하기 위한 기본 원리로 시스템에서 자주 변경되는 부분을 상대적으로 덜 변경되는 안정적인 인터페이스 뒤로 감춰야 한다는 것이 핵심이다. 모듈 분해는 감춰야 하는 비밀을 선택하고 비밀 주변에 안정적인 보호막을 설치하는 보존의 과정이다. 비밀을 결정하고 모듈을 분해한 후에는 기능 분해를 이용해 모듈에 필요한 퍼블릭 인터페이스를 구현할 수 있다. 모듈은 복잡성과 변경 가능성을 감춰야한다. (p235, 236)

 

모듈은 기능이 아니라 변경의 정도에 따라 시스템을 분해한다. 각 모듈은 외부에 감춰야 하는 비밀과 관련성 높은 데이터와 함수의 집합이다. 따라서 모듈 내부는 높은 응집도를 유지한다. 또한 퍼블릭 인터페이스를 통해서만 통신해야 하므로 낮은 결합도를 유지한다. 모듈에게 있어 핵심은 데이터다. (p239)

 

클래스 

 

명확한 의미에서 클래스는 추상 데이터 타입과는 다르다. 핵심적인 차이는 클래스는 상속과 다형성을 지원하는데 비해 추상 데이터 타입은 지원하지 못한다는 것이다.  타입 추상화를 기반으로 하는 대표적인 기법이 바로 추상 데이터 타입이다. 추상 데이터 타입은 오퍼레이션을 기준으로 여러가지 타입을 나타낼 수 있다. 반면 객체 지향은 타입을 기준으로 오퍼레이션을 묵는다. (p245, 246)

 

실제 내부에서 수행되는 절차는 다르지만 클래스를 이용한 다형성은 절차에 대한 차이점을 감춘다. 다시 말해 객체 지향은 절차 추상화다.

 

 

 

 

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20

오브젝트 리뷰 - 3

기술/아키텍처 2021. 7. 21. 19:00 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

Chapter 5 리뷰 

 

책임 주도 설계를 향해 

 

데이터보다 행동을 먼저 결정하라. 객체에게 중요한 것은 데이터가 아니라 외부에 제공하는 행동이다. 클라이언트 관점에서 객체가 수행하는 행동이란 곧 객체의 책임을 의미한다. 

 

협력이라는 문맥 안에서 책임을 결정하라. 객체에게 할당된 책임의 품질은 협력에 적합한 정도로 결정된다. 객체에게 할당된 책임이 협력에 어울리지 않는다면 그 책임은 나쁜 것이다. 협력에 적합한 책임을 수확하기 위해선 객체를 결정한 후에 메시지를 선택하는 것이 아니라 메시지를 결정한 후에 객체를 선택해야 한다.

 

올바른 객체지향 설계는 클라이언트가 전송할 메시지를 결정한 후에 비로소 객체의 상태를 저장하는 데 필요한 내부 데이터에 관해 고민하기 시작한다. 결록적으로 협력이라는 문맥 안에서 객체가 수행할 책임에 초점을 맞춘다. 

 

책임 할당을 위한 GRASP 패턴 

 

INFORMATION EXPERT

 

객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이다. GRASP에서는 이를 INFORMATION EXPERT(정보 전문가) 패턴이라고 부른다. 정보 전문가 패턴은 객체가 자신이 소유하고 있는 정보와 관련된 작업을 수행한다는 일반적인 직관을 표현하는 것이며 여기서 말하는 정보는 데이터와는 다르다. 정보를 알고 있다고 해서 정보를 저장하고 있을 필요는 없다.

 

LOW COUPLING, HIGH COHESION

 

책임을 할당할 수 있는 다양한 대안들이 존재한다면 응집도와 결합도의 측면에서 더 나은 대안을 선택하는 것이 좋다. 다시 말해 두 협력 패턴 중에서 높은 응집도와 낮은 결합도를 얻을 수 있는 설계가 있다면 그 설계를 선택해야 한다는 것이다. GRASP에서는 이를 LOW COUPLING(낮은 결합도) HIGH COHESION(높은 응집도) 패턴이라고 부른다. 설계를 진행하면서 책임과 협력의 품질을 검토하는 데 사용할 수 있는 중요한 평가 기준이다.

 

응집도가 낮다는 것은 연관성이 없는 기능이나 데이터가 하나의 클래스 안에 뭉쳐있다는 것을 의미한다. 문제를 해결하기 위해선 변경의 이유에 따라서 클래스를 분리해야한다. 먼저 변경의 이유가 하나 이상인 클래스를 찾는 것으로부터 시작하는 것이 좋다. 변경의 이유를 파악할 수 있는 첫번째 방법은 인스턴스 변수가 초기화되는 시점을 살펴보는 것이다. 응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화 한다. 반면 응집도가 낮은 클래스는 객체의 속성 중 일부만 초기화하고 일부는 초기화되지 않은 상태로 남겨져 있다. 함께 초기화되는 속성을 기준으로 코드를 분리해야 한다. 

 

두번째 방법은 메서드들이 인스턴스 변수를 사용하는 방식을 살펴보는 것이다. 모든 메서드가 객체의 모든 속성을 사용하면 응집도가 높다고 볼 수 있으나 속성에따라 그룹별로 나뉜다면 클래스의 응집도가 낮다고 볼 수 있다. 응집도를 높이기 위해서는 속성 그룹과 해당 그룹에 접근하는 메서드 그룹을 기준으로 코드를 분리해야 한다.

 

POLYMORPHISM, PROTECTED VARIATION 

 

변화가 예상되는 불안정한 지점을 식별하고 그 주위에 안정된 인터페이스를 형성하도록 책임을 할당하라. PROTECTED VARIATION(변경 보호) 패턴은 책임 할당의 관점에서 캡슐화를 설명한 것이다. 클래스를 변경에 따라 분리하고 임터페이스를 이용해 변경을 캡슐화 하는 것은 설계의 결합도와 응집도를 향상시키는 매우 강력한 방법이다. 하나의 클래스가 여러 타입의 행동을 구현하고 있는 것처럼 보인다면 클래스를 분해하고 POLYMORPHISM 패턴에 따라 책임을 분산시켜라. 예측 가능한 변경으로 인해 여러 클래스들이 불안정해진다면 PROTECTED VARIATION 패턴에 따라 안정적인 인터페이스 뒤로 변경을 캡슐화하라. 

 

책임 주도 설계의 대안 

 

최대한 빠르게 목적한 기능을 수행하는 코드를 작성하고 리팩토링 한다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20

오브젝트 리뷰 - 2

기술/아키텍처 2021. 7. 12. 20:05 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

지난번 글에 이어 리뷰를 추가한다. 

 

협력, 책임, 역할 

 

객체지향 패러다임의 관점에서 핵심은 역할(role), 책임(responsibility), 협력(collaboration) 이다. 객체지향의 본질은 협력하는 객체들의 공동체를 창조하는 것이다. 협력을 구성하기위해 적절한 객체를 적절한 책임을 할당하는 과정이 필요하다 (p73)

 

협력 

 

- 협력이란 객체들이 애플리케이션의 기능을 구현하기 위해 수행하는 상호작용을 말한다. (p74)  

- 두 객체 사이의 협력은 하나의 객체가 다른 객체에게 도움을 요청할 때 시작되며 메시지 전송(message sending) 커뮤니케이션으로 이뤄진다. 메시지를 수신한 객체는 메서드를 실행해 요청에 응답한다. (p75)

- 객체는 자신에게 할당된 책임을 수행하던 중에 필요한 정보를 알지 못하거나 외부의 도움이 필요한 경우 적절한 객체에게 메시지를 전송해서 협력을 요청한다. (p76) 

- 객체가 참여하고있는 협력이 객체의 행동을 결정한다. 객체의 상태를 결정하는 것은 객체의 행동이다. 객체의 상태는 그 객체가 행동을 수행하는 데 필요한 정보가 무엇인지로 결정된다. 따라서 객체가 참여하는 협력이 객체를 구성하는 행동과 상태를 모두 결정한다. 협력은 객체를 설계하는데 필요한 일종의 문맥(context)를 제공한다 (p77)

 

책임 

 

- 협력에 참여하기 위해 객체가 수행하는 행동을 책임이라고 부른다. (p78)

- 협력을 설계하는 출발점은 시스템이 사용자에게 제공하는 기능을 시스템이 담당할 하나의 책임으로 보는 것이다. 객체지향 설계는 협력에 필요한 메시지를 찾고 메시지에 적절한 객체를 선택하는 반복적인 과정을 통해 이뤄진다 (p83)

- 객체에게 책임을 할당하는데 필요한 메시지를 먼저 식별하고 메시지를 처리할 객체를 나중에 선택하는것이 중요하다. 객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하게 한다. (p84) 

- 객체를 객체답게 만드는 것은 객체의 상태가 아니라 객체가 다른 객체에게 제공하는 행동이다. (p85) * 여기서 말하는 객체의 상태는 객체가 갖고있는 데이터를 의미하는 것 같다. 

- 책임을 찾고 책임을 담당할 객체를 찾아 책임을 할당하는 방식으로 협력을 설계하는 방식을 RDD(Responsibility DrivenDesign) 이라 부른다 (p83)

 

역할 

 

- 객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합을 역할이라고 부른다. 역할이 중요한 이유는 역할을 통해 유연하고 재사용 가능한 협력을 얻을 수 있기 때문이다. (p86, 87)

- 협력을 구체적인 객체가 아니라 추상적인 역할의 관점에서 설계하면 협력이 유연하고 재사용 가능해진다. 역할의 가장 큰 장점은 설계의 구성요소를 추상화할 수 있다는 것이다. (p92)

- 추상화의 첫번째 장점은 세부사항에 억눌리지 않고도 상위 수준의 정책을 쉽고 간단하게 표현할 수 있다는 것이고 두번째 장점은 설계를 유연하게 만들 수 있다는 것이다. 협력 안에서 역할이라는 추상화를 이용하면 기존코드를 수정하지 않고도 새로운 행동을 추가할 수 있다 (p94)

 

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20

오브젝트 리뷰 - 1

기술/아키텍처 2021. 7. 11. 15:53 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

 

객체지향 프로그래밍을 다룬 오브젝트를 읽으며 나의 잘못된 과거의 코딩이 생각나던 구절, 나도 모르게 밑줄을 치게 되던 주옥같은 문장, 기억해야할 용어를 정리해본다. 이 책의 모든 내용을 한 포스트에 정리하긴 어려울 것 같고 공부한 날짜별로 메모하고 싶은 내용만 담아본다. 기본적으로 책의 내용을 참조 했지만 공부한 내용을 요약해서 정리하고자 몇몇 문장과 단어를 추가하고 수정했다. 그리고 페이지 별로 주요 문장을 뽑아냈기 때문에 목차별로 전달하고자 하는 핵심 메시지가 누락돼 저자의 의도가 제대로 전달되지 않을 수 있다. 추상적이거나 애매해보이는 보이는 표현은 책을 통해서 확인 바란다.

 

캡슐화의 개념과 목적

 

- 개념적이나 물리적으로 객체 내부의 세부적인 사항을 감추는 것. 목적은 변경하기 쉬운 객체로 만드는 것이며, 캡슐화를 통해 객체 내부 접근을 제한하면 객체와 객체 사이의 결합도를 낮아지기 때문에 변경하기 쉬운 객체로 만드는게 가능하다. (p20)

 

응집도가 높은 객체란

 

- 밀접하게 연관된 작업만을 수행하고, 연관성 없는 작업은 다른 객체에게 위임하는 객체. 스스로 자신의 데이터에 책임을 지는 객체가 응집도가 높다고 말할 수 있다. (p26)

 

절차지향 프로그래밍과 객체지향 프로그래밍

 

- 프로세스와 데이터를 별도의 모듈에 위치시키는 방식을 절차적 프로그래밍(Procedural Programming)이라 부르며 반대로 데이터와 프로세스가 동일한 모듈 내부에 위치하도록 프로그래밍 하는 방식을 객체지향 프로그래밍(Object-Oriented Programming)이라 한다. (p26) 여기서 프로세스는 처리하는 작업을 담당하는 메서드에 해당하고 데이터는 작업을 처리할 때 필요한 정보를 말한다. 

 

- 절차지향 프로그래밍에서는 별도의 모듈에 프로세스와 데이터가 있기 때문에 의존관계가 높아진다. 반명 객체지향 프로그래밍에서는 연관관계가 있는 프로세스와 데이터가 하나의 모듈에 있기 대문에 의존관계가 낮아진다.

 

훌륭한 객체 지향 설계를 위한 원칙

 

- 캡슐화를 이용해 의존성을 적절히 관리함으로써 객체 사이의 결합도를 낮춘다 (p27)

- 객체지향 설계에서는 독재자가 존재하지 않고 각 객체에 책임이 적절하게 분배된다. 따라서 각 객체는 자신을 스스로 책임진다. (p28)

- 소프트웨어를 구성하는 모든 객체들이 자율적으로 행동하는 설계 (p34)

- 변경이 쉬우며 협력하는 객체 사이의 의존성을 적절하게 관리 (p36)

 

도메인 

 

- 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야를 도메인이라 부른다 (p41)

 

- 어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지를 고민한다. 어떤 객체들이 어떤 상태와 행동을 가지는지 우선 결정한다. 그리고 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야한다. (p41) 

 

자율적인 객체 

 

- 객체는 상태와 행동을 가지는 복합적인 존재이며 스스로 판단하고 행동하는 자율적인 존재.  (p44)

- 객체 내부에 대한 접근을 통제해 객체를 자율적인 존재로 만든다. 자율적으로 만드는 것은 외부의 의존성으로부터 자유로워지는 것을 의미한다. (p44) 

- 외부에서 접근가능한 부분을 퍼블릭 인터페이스(public interface)라고 하며 내부에서만 접근 가능한 부분을 구현(implementation)이라 부른다. (p44)

 

객체 간의 협력 

 

- 다른 객체의 인터페이스에 공개된 행동을 수행하도록 요청(Request) 할 수 있고 요청 받은 객체는 자율적인 방법에 따라 응답(Response)한다. (p49)

- 객체끼리 상호작용할 수 있는 유일한 방법은 메시지를 전송하는 것 뿐이다. 다른 객체에게 요청이 도착할 때 해당 객체가 메시지를 수신했다고 이야기한다. 수신된 메시지를 처리하기 위한 자신만의 방법을 메서드라고 한다 (p49)

 

상속과 다형성 

 

- 상속과 인터페이스를 사용하면 컴파일타임 의존성과 실행 시점의 의존성이 다를 수 있다. 클래스 사이의 의존성과 객체사이의 의존성이 동일하지 않을 수 있다. (p59)

- 상속과 인터페이스를 사용하면 코드가 유연해진다. 그러나 유연해진만큼 코드를 이해하고 디버깅하는 것은 어려워진다. 반대로 유연성을 억제하면 재사용과 확장가능성은 낮아진다 (p59) 

- 동일한 메시지를 전송하지만 실행되는 메서드는 수신되는 객체에 따라서 달라진다. 이를 다형성이라 부른다. 다형성은 컴파일 시간 의존성과 실행시간 의존성을 다를수 있는 사실을 기반으로 하며 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답하는 능력을 말한다. (p 63)

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09

응집도(Cohesion)와 결합도(Coupling)

기술/아키텍처 2021. 7. 10. 12:16 Posted by 아는 개발자

모듈

 

응집도와 결합도를 설명하기 위해선 모듈이라는 단어를 사용해야하는데 모듈은 문맥에 따라서 의미가 달라지는 경우가 많아서 이 글에서는 클래스나 패키지, 라이브러리 같은 프로그램을 이루는 임의의 요소로 정의한다.

 

응집도 

 

모듈을 이루는 요소들의 연관성 척도다. 클래스내의 함수와 변수, 더 큰 범위에선 패키지 내의 개별 클래스가 하나의 목적으로 연관관계가 이뤄질 경우 응집도가 높고 그렇지 않은 경우에는 응집도가 낮은 것으로 판단한다. 응집도가 높을 수록 소프트웨어를 수정 할 경우 변경하게될 범위가 명확해지기 때문에 좋은 설계로 본다. 반대로 응집도가 낮을 수록 소프트웨어를 변경해야하는 이유가 많아지기 때문에 좋은 설계로 보기 어렵다.

 

결합도 

 

소프트웨어를 구성한 여러 모듈은 서로를 호출하는 관계를 가지게 되는데, 각각의 모듈이 서로를 의존하는 정도를 결합도라고 한다. 모듈간의 의존 관계가 생기면 하나의 모듈을 수정할 때 다른 모듈이 영향을 받게 된다. 결합도가 높을 수록 다른 모듈의 변경에 영향을 많이 받게 되고 반대로 낮을 수록 다른 모듈의 변경에 영향이 적게된다. 일반적으로 결합도가 낮을 수록 좋은 설계로 본다.

 

그림 1

 

위 그림에서 각 모듈간 의존 관계가 많다. 특히 모듈E는 A,B,C,D 모듈이 의존하고 있는 모듈이기 때문에 E가 변경된다면 A, B, C, D 모두 영향을 받게 되므로 결합도가 높은 좋지 못한 설계에 해당한다. 이런 경우 유지 보수가 어렵기 때문에 도메인 요구사항에 맞춰서 재설계 하는 편이 좋다.

 

그림 2

 

그림 2는 그림 1의 결합도 문제를 해결한 설계다. E에 의존했던 A,B 는 구조를 변경해 E를 참조하지 않아도 되게끔 수정했다. 또한 A와 D 간의 의존관계도 수정했다. 이전보다 의존 관계가 많이 줄었으므로 결합도가 낮아진 것으로 볼 수 있다. 

 

* 그림 2는 설명을 위한 예시다. 실제로 소프트웨어 상에서 결합도 문제를 해결하기 위한 과정은 이렇게 단순하지 않다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09

멀티쓰레드 동시성

기술/컴퓨터사이언스 2021. 6. 5. 14:06 Posted by 아는 개발자

멀티 쓰레드 환경은 하나의 프로세스 내에 여러 개의 쓰레드가 동작할 수 있는 환경을 말한다. 쓰레드는 고유의 작업을 하면서 쓰레드 내에서 할당된 변수 뿐만 아니라 프로세스 내에 있는 변수에도 접근 할 수 있는데 이때 여러 개의 쓰레드가 같은 데이터에 접근하는 경우 경우에 따라 동시성 문제가 발생할 수 있다. 

 

 

위 그림은 스레드A, B가 프로세스 내에 상주한 정수형 변수 a에 접근하는 경우다. 둘다 read 명령으로 접근 하기 때문에 변수의 값이 변할 염려가 없다. 쓰레드 A, B 모두 변수의 값을 3으로 읽어오기 때문에 예상하지 못한 값을 읽어오게 되는 경우는 없다. 이 경우에는 동시성 문제가 발생하지 않는다.

 

 

그런데 위 그림에선 쓰레드 A가 a의 값을 1만큼 더해주는 작업을 하는데 이때는 접근 순서에 따라서 쓰레드 B가 읽어오게 되는 값이 달라질 소지가 생긴다. 쓰레드 B 가 접근하기 전에 쓰레드 A가 2번 접근했었다면 쓰레드 B는 3이 아닌 5를 값으로 읽어오게 되기도 하고 동시에 접근을 한다면 쓰레드 A가 값을 업데이트 하는 도중 쓰레드 B가 읽어서 변화된 값을 읽지 못하게 되는 우려도 생긴다. 위처럼 여러 개의 쓰레드가 공유된 자원에 접근 할 때 데이터의 신뢰성을 보장받을 수 없는 경우 쓰레드 동시성(Concurrency) 문제라한다.

 

728x90

'기술 > 컴퓨터사이언스' 카테고리의 다른 글

멀티쓰레드 동시성  (0) 2021.06.05
프로스세와 스레드  (0) 2021.06.05
스핀락, 뮤텍스, 세마포어  (0) 2018.11.07
RCU (Read-Copy Update)  (0) 2018.10.30
Cgroup (Control Group)  (0) 2018.09.15
CPU pinning과 taskset  (0) 2018.08.27

프로스세와 스레드

기술/컴퓨터사이언스 2021. 6. 5. 13:42 Posted by 아는 개발자

 

프로세스 (Process)

 

프로세스는 운영체제에서 프로그램을 구성하는 기본 단위다. 현재 글을 쓰고 있는 크롬 웹브라우저, 스마트폰에서 사용중인 넷플릭스, 카카오 앱도 모두 운영체제내에선 프로세스 단위로 이뤄진다. 특정 프로그램의 경우 두개 이상의 프로세스로 이뤄지는 경우도 있으나 하나의 프로세스로 이뤄진 경우가 일반적이다.

 

프로세스는 운영체제 고유 스케줄링에 관리를 받는다. 우리가 스마트폰에서 구글 뮤직 앱으로 음악을 들으면서 카톡을 할 수 있는 것도 구글 뮤직 앱 프로세스와 카톡 프로세스가 운영체제 스케줄링에 의해 관리되기 때문이다. 프로그램이 프로세스의 형태로 이뤄진 것도 어찌보면 운영체제 내에서 동작을 관리하기 위함으로 생각할 수도 있다.

 

쓰레드 (Thread)

 

프로세스내에서 실행되는 작업 단위다. 프로세스의 데이터에 접근 할 수 있고 특정한 작업을 맡길 때 사용한다. 안드로이드의 경우를 예로 들면 대표적으로 화면을 담당하는 UI 쓰레드와 네트워크나 디스크 작업에 쓰이는 I/O 쓰레드가 있다. 각 쓰레드 모두 하나의 프로세스내에서 실행되며 서로 데이터를 공유 할 수 있다.

 

하나의 프로세스에 여러개의 스레드를 두는 경우 멀티 스레드 환경이라고 부르며 동시에 이뤄져야 하는 작업이 있는 경우 이 방식으로 소프트웨어를 구성한다. 예로들면 화면을 업데이트 하면서 파일을 다운받는 경우 하나의 스레드에서 작업한다면 둘 중 하나가 끝나야 다음 작업이 가능한데 각각 별도의 쓰레드를 둔다면 동시에 작업이 가능하다. 그래서 대부분의 프로그램이 멀티 쓰레드 방식으로 구현된다.

 

728x90

'기술 > 컴퓨터사이언스' 카테고리의 다른 글

멀티쓰레드 동시성  (0) 2021.06.05
프로스세와 스레드  (0) 2021.06.05
스핀락, 뮤텍스, 세마포어  (0) 2018.11.07
RCU (Read-Copy Update)  (0) 2018.10.30
Cgroup (Control Group)  (0) 2018.09.15
CPU pinning과 taskset  (0) 2018.08.27

클린 아키텍처

기술/아키텍처 2021. 5. 20. 19:51 Posted by 아는 개발자

 

로버트 마틴의 클린 코드에선 코드를 깔끔하게 잘 짜는 방법을 배웠다면 클린 아키텍처에서는 소프트웨어를 더 잘 만드는 방법을 배운 것 같다. 책의 표현을 빌리자면 클린 코드에서는 좋은 벽돌을 구분하는 방법을 배웠다면 클린 아키텍처에서는 좋은 벽돌로 건물을 짓는 방법을 배운 느낌이랄까. 책에선 저자가 경험한 내용을 바탕으로 전달하려는 교훈이 많다. 저수준, 고수준, 프레임워크는 세부사항일 뿐이다 등등.. 그런데 나의 소프트웨어 깊이가 부족해 공감하기 어려운 부분도 있었고 이해되지 않던 부분도 있어서 모두 소화하진 못했다. 그래도 연차가 늘어나고 더 큰 규모의 소프트웨어를 경험하다보면 이 책에서 내가 캐치하지 못했던 새로운 면이 보일 것 같아 기대된다. 2-3년 후에 다시 이 책을 읽어봐야 겠다.

 

많은 전달 내용 중 내 머릿속을 관통하는 소프트웨어 원칙은 이 그림으로 표현 할 수 있다.

 

 

다이어그램 상에선 컴포넌트A 가 컴포넌트B를 가리키고 있는 그림인데 소프트웨어상에선 컴포넌트A 가 컴포넌트B 에 의존한다는 의미의 그림이다. 이 의존 관계는 소프트웨어 상에서 생길 수 있는 가장 중요한 관계고 이 관계를 어떻게 정의하느냐에 따라서 소프트웨어의 아키텍처가 결정된다. 컴포넌트A는 저수준으로, 컴포넌트B는 고수준으로 둬야한다고 그림상에선 표현 했는데 여기서 말하는 수준은 어떤 컴포넌트가 우월한지를 결정하는 기준이 아니라 얼마만큼 변동성이 크냐를 기준으로 결정한다.

 

위 그림처럼 의존관계가 성립되려면 고수준인 컴포넌트B는 수정할 일이 적어야 한다. 그래야 컴포넌트A에 미치는 영향을 최소화 할 수 있기 때문이다. 위 그림처럼 결정되는 대표적인 예가 애플리케이션에서 String, Math 같은 자바 고유 라이브러리 클래스를 사용하는 경우다. 자바 버전에 따라서 클래스가 변경될 소지가 있지만 그래도 우리가 개발하는 클래스보다 변경될 소지는 적다. 이런 경우 의존 관계는 적절한 것으로 볼 수 있다.

 

그래도 가끔은 이런 의존 관계를 성립하기 힘든 경우도 있다. 둘다 변경의 소지가 크지만 두 클래스를 연결해야할 때가 있다. 이럴때 사용하는 방식이 의존성 역전원칙이다. 좀더 고수준으로 보이는 클래스에 특정 인터페이스를 만들고 이것과 상속 관계로 만든다. 그리고 저수준 클래스를 인터페이스에 의존하는 관계로 만든다. 인터페이스는 변경될 소지가 적기 때문에 이 의존 관계도 적절한 관계로 볼 수 있다.

 

 

물론 매번 이렇게 코딩할 수는 없다. 개발하다보면 새로운 함수도 추가해야돼 인터페이스도 손될 일이 많아지니까. 모든 원칙을 지키다 보면 오버 엔지니어링이돼 개발 프로세스가 느려지는 부작용도 생길 수 있다. 항상 모든 원칙을 지키기는 어려울 것 같다. 하지만 원칙을 알고 생략하는 것과 모르고 넘어가는 것은 차이가 크다. 앞으로 일하면서 어떤 원칙을 넘기면서 개발하고 있는지 되새겨봐야 할 것 같다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09

DIP(Dependency Inversion Principle)

기술/아키텍처 2021. 5. 9. 08:57 Posted by 아는 개발자

좋은 아키텍처는 변동성이 큰 모듈에 의존하지 않는 것이다. 그런데 개발하다 보면 예상치 못한 버그도 종종 생기기 마련이기 때문에 어떤 클래스는 릴리즈마다 계속 수정을 할 수 밖에 없다. 그런데 이때마다 새로운 함수를 추가하고 새로운 변수가 등장한다면 이 클래스를 의존하는 다른 모듈에도 영향이 미친다. 이런 형태면 하나의 클래스를 수정하는데도 다른 클래스까지 영향을 주게 된다.

 

그림 1

위 그림에선 사용자가 버그가 많은 결제 시스템 클래스를 의존하고 있다. 지금까지 pay 함수에 버그가 많아서 3개의 레거시 함수가 있다. 이런 형태는 새로운 함수가 추가될 때 마다 사용자의 코드에 영향을 주게 되는 사례다.

 

해법은 인터페이스를 이용하는 것이다. 모듈은 안정화된 인터페이스에 의존하고 변동성이 큰 실제 구현체는 인터페이스를 바꾸지 않는 선에서 수정한다. 인터페이스가 바뀌지 않는것이 보장됐기 때문에 원래 실제 구현체에 의존하는 클래스는 수정이 있어도 코드를 수정하지 않아도 된다. 이런 철학으로 만든 원칙이 DIP(Dependency Inversion Principle) 의존성 역전 원칙이다.

 

그림 2

그림 2는 그림 1에서 DIP를 적용한 버전이다. 사용자는 안정화된 결제시스템 Interface를 참조하고 있기 때문에 수정할 일이 없다. 버그가 많은 결제시스템만 수정해도 소프트웨어의 안정성은 보장된다. 요즘에는 프레임워크 차원에서 이렇게 구현할 수 있도록 지원하고 있다. 안드로이드의 Hilt, Dagger가 DIP를 지원하는 대표적인 라이브러리니 아직 사용해보지 않은 분들은 한번 써보는게 좋을 것 같다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09
OCP (Open Closed Principle)  (0) 2021.05.04

ISP (Interface Segregation Principle)

기술/아키텍처 2021. 5. 9. 08:28 Posted by 아는 개발자

ISP는 필요 이상 많은 것을 포함하는 모듈에 의존하지 말자는 원칙에서 유래됐다. 이것도 사례를 먼저 보자.

 

위 그림을 보면 결제관리, 환불관리, 포인트 관리 객체가 시스템 클래스를 참조하고 있다. 그런데 결제관리 객체는 pay 함수만 호출 할 것이고, 환불 관리는 refund, 포인트관리는 addPoint함수만 사용할 것이다. 각 객체의 입장에서는 필요 이상으로 시스템 클래스에 의존하고 있는 형태다. 만약 객체에서 불필요하게 다른 함수를 호출한다면 에러가 발생할 위험도 있다.

 

이런 경우에는 시스템을 각각 쪼개주는 방법이 있다. 결제시스템, 환불시스템, 포인트 시스템을 각각 인터페이스로 만들어고 시스템 클래스가 인터페이스의 하위타입으로 구현한다. 그리고 각각의 객체는 관계가 있는 인터페이스에만 의존하도록 만들면 의존하는 모듈에 불필요하게 포함된 함수들을 제거할 수 있다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09
OCP (Open Closed Principle)  (0) 2021.05.04
SRP (Single Responsibility Principle)  (0) 2021.05.04

LSP (Liskov Substitution Principle)

기술/아키텍처 2021. 5. 9. 08:11 Posted by 아는 개발자

LSP는 1988년에 미국MIT 공과대학 교수였던 바버라 리스코브가 제안한 것으로, 상호 대체 가능한 구성요소를 이용해 소프트웨어 시스템을 만들 수 있으려면, 이들 구성요소는 반드시 서로 치환가능해야 한다는 원칙이다. 문장으로 보면 무슨뜻인지 정확히 파악하기 어려우나 원칙을 지킨 사례와 어긴 사례를 보면 어떤 의미인지 감을 잡을 수 있을 것이다.

 

원칙을 지킨 사례

위 그림은 원칙을 지킨 사례다. 구매자가 결제 시스템을 사용하는데 결제 시스템에선 네이버페이, 카카오페이 둘중 하나를 사용하고 있다. 구매자는 네이버페이를 사용하던, 카카오페이를 사용하던 동일한 결제시스템 인터페이스를 사용하게 될 것이고 영향 받지 않는다. 따라서 결제 시스템은 필요에 따라 하위타입을 치환 가능하다. 객체지향형 프로그램을 개발해본 사람이라면 이렇게 설계하는 건 매우 당연한 일이다.

 

원칙을 어긴 사례

위 그림은 원칙을 어긴 유명한 사례다. 사용자는 직사각형에 의존하고 있는데 직사각형의 하위 타입은 정사각형으로 선언돼있다. 수학적으로보면 정사각형은 직사각형에 포함되기 때문에 위 관계가 맞으나 위 설계상에선 위 관계가 적합하지 않다. 사용자가 의존하는 직사각형 클래스에는 setWidth, setHeight 함수가 있다. 너비와, 높이를 설정해주는 함수다. 그런데 정사각형은 높이가 너비가 같은 사각형이다. setWidth, setHeight 함수가 의도한 대로 동작하지 않게 되므로 정사각형은 하위타입으로서 적합하지 않다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09
OCP (Open Closed Principle)  (0) 2021.05.04
SRP (Single Responsibility Principle)  (0) 2021.05.04

OCP (Open Closed Principle)

기술/아키텍처 2021. 5. 4. 18:39 Posted by 아는 개발자

SOLID 원칙의 두번째 원칙인 OCP (Open-Closed Principle)은 소프트웨어가 기존 코드를 수정하기보단, 새로운 코드를 추가함으로써 시스템의 행위를 변경할 수 있는 설계 원칙이다. OCP 원칙을 들어보지 못했더라도 Spring이나 안드로이드 최신 개발 프레임워크에서 지향하는 구조 (Spring Repository-Service-Controller 구조, 안드로이드 MVVM)를 사용하고 있다면 암묵적으로 OCP 원칙에 따르고 있게 되는데 이 원칙의 핵심은 소프트웨어를 이루는 컴포넌트를 저수준에서 고수준으로 계층화하는 것이다.

 

위 그림은 웹페이지를 출력하는 클라이언트를 단순하게 표현한 것이다. Interactor는 서버에 있는 데이터를 가져 올 수 있는 인터페이스의 역할을 하고, Controller는 Interactor를 통해서 노출할 정보를 가져오고 Presenter는 Controller에서 가져온 정보를 일차적으로 처리한 후 WebView는 Presenter에서 일차적으로 처리한 정보를 이용해 화면에 노출한다. 데이터의 연산이 가장 적은 WebView는 가장 저수준의 컴포넌트고 서버와 맞닿아 있는 Interactor는 가장 고수준의 소프트웨어다. 

 

컴포넌트간의 의존 관계가 일방향이기 때문에 컴포넌트의 변화가 미치는 영향을 최소화 할 수 있어 소프트웨어의 수정이 쉬워진다. 화면상의 버튼의 위치를 수정하게 되는 경우에는 WebView만 수정하면 되게 되고 불러오는 일부 데이터 처리의 변경하고 싶다면 Presenter를, 불러오는 데이터를 변경한다면 Controller를 수정하면 된다. 변경하려는 구조가 저수준에서 고수준으로 갈수록 수정이 미치는 영향이 커지지만, 저수준의 컴포넌트의 변화에서 고수준 컴포넌트를 보호 할 수 있어 유지관리에 유리하다. 

 

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09
OCP (Open Closed Principle)  (0) 2021.05.04
SRP (Single Responsibility Principle)  (0) 2021.05.04

SRP (Single Responsibility Principle)

기술/아키텍처 2021. 5. 4. 17:30 Posted by 아는 개발자

소프트웨어 디자인 원칙으로 유명한 SOLID에서 처음으로 소개되는 항목 SRP는 Single Responsibility Principle 의 준말이다. 풀네임의 뜻을 통해 원칙의 의미를 추측한다면 "모든 모듈은 하나의 일만 해야 한다"로 오해하기 쉬운데 이건 함수인 경우에 적용되는 원칙이지 모듈 범위에서 SRP가 뜻하는 바는 이와 다르다. 디자인 원칙에서 SRP는 "하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다"는 것을 의미한다. 아마 이 문장의 느낌이 바로 와닿지가 않을 것 같기 때문에 다음 예제를 소개한다.

 

위 그림 상에의 Employee 클래스는 초과근무 시간(overtimeHours)을 속성으로 갖고 있고 재무팀에선 이 정보를 이용해 초과근무 수당을 계산하는 함수를(overtimeHours) 관리하고, 인사팀에서는 허위로 초과근무 시간을 채우는 사람이 없는지 감시하고자 초과근무 시간을 보고하는 함수(reportHours)를 관리하고 있다. 두개의 액터가 하나의 클래스를 변경 할 수 있는 요인이기 때문에 이미 SRP원칙을 어긴 구조다.

 

갑자기 인사팀에서 초과 근무 시간을 보고하는 함수를 수정해야하는 일이 생겼다. 할 일은 많은데 CEO는 계속 초과근무 시간을 줄이라고 하니 어쩔 수 없이 데이터를 조작하기로 했다(실제로 그러시면 안됩니다). 회사원 별로 근무한 초과 시간에서 30% 를 낮추고자 했는데 문제는 reportHours() 함수의 리턴 값을 수정한게 아니라 클래스의 속성값인 overtimeHours를 수정한 것이다. 이 속성값은 인사팀만 사용하는 것이 아니라 재무팀도 사용하고 있었기 때문에 calculatePay 함수가 영향을 받게 됐고 근로자는 실제로 초과 근무한 시간의 30%를 제외한 부분만 인정돼 수당의 70% 만 받게되는 사건이 벌어졌다. 그 결과 재무팀은 초과근무한 직원들로부터 끝없는 컴플레인을 받게 됐고 이 소식을 들은 CEO는 인사팀이 데이터를 조작했다는 사실을 알게돼 인사팀장이 아주 난처해졌다는 웃픈 가상의 이야기.

 

몇몇 분들은 바보 같이 overtimeHours를 수정한게 문제라고 할 것 같다. 개발자가 공통으로 사용하고 있는 속성 값을 건드린 건 아마추어적인 실수긴 하다. 그러나 실제 코딩을 해보면 이런 아마추어적인 실수를 종종 하게된다. 모든 코드를 분석할 시간이 없기 때문에 하나의 클래스를 참조하는 모든 연관관계를 보는 것은 불가능하다. 그래서 등장한 소프트웨어 원칙은 이런 실수를 미리 만들지 않게끔 구조를 세울 수 있도록 한다. 앞서 소개한 예제를 SRP원칙을 적용해 변형한 버전은 이렇게 바꿔볼 수 있다.

 

HoursRepoter, PayCalculator 두 개의 클래스가 새로 생겼고 각각의 클래스는 인사팀, 재무팀이 관리한다. Employee는 HoursRepoter클래스와 PayCalculator 클래스를 참조해 calculatePay 와 reportHour 함수의 리턴값을 처리한다. overtimeHours는 인사팀, 재무팀 모두 변경할 수 없게끔 분리했기 때문에 앞으로 초과근무 수당이 미지급되는 사건이 벌어진다면 모두 재무팀의 문제로(?) 돌릴 수 있게 됐다. 

 

SRP원칙을 처음 들어봤을 땐 쉽다고 생각했는데 예제를 공부하면서 깊이 생각해보게 되고 내가 짠 코드를 보면 볼수록 더 깊이가 있는 원칙인 것 같다. 클래스 구조를 단순하게만 바라보게 되는건 아닌지 다시 한번 생각해보게끔 하는 원칙이다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09
OCP (Open Closed Principle)  (0) 2021.05.04
SRP (Single Responsibility Principle)  (0) 2021.05.04

오픈소스 라이센스 정리

기술/오픈소스 2019. 6. 9. 11:04 Posted by 아는 개발자


오픈소스를 이용해서 개발하는 경우 코드를 무료로 보고 사용할 수 있어 개발하기 편리하다는 장점이 있으나 사용하고 있는 오픈소스가 어떤 라이센스를 가지느냐에 따라서 상업적인 이용이 제한될 수도 있고 내가 만든 코드를 공개해야 할 의무까지 생길 수 있다. 이런 경우를 예방하려면 개발하기 전부터 사용할 오픈소스 라이센스에 대해 검토해둘 필요가 있다. 이번 포스트에서는 유명한 오픈소스 라이센스들에 대해서 간단히 정리를 해보려고 한다.


0. 라이센스란? 


소프트웨어의 지적 재산권을 일컫는 말이다. 음악의 저작권과 비슷한 개념 정도로 생각하면 될 것 같다. 처음 코딩에 입문하는 사람들은 남들이 짠 코드랑 본인이 짠 것과 함수와 변수 명만 제외하면 거의 차이가 없어(헬로 월드 수준이니까) 딱히 저작권이라고 할만한 것이 있는지도 모르겠고 또 수만줄이 넘는 코드에서 카피 유무의 확인조차 불가능 할 것 같은 소프트웨어에 지적 재산권이 통용되고 있다는 것에 의문을 품기도 하지만 실제 소프트웨어 업계에선 엄연히 라이센스 규정에 따라서 운영되고 있으며 이미 몇가지 판례를 통해 법적인 효력까지 갖추고 있기 때문에 쉽게 무시해선 안된다



사용하고 있는 오픈소스가 어떤 라이센스를 쓰고 있는지 확인하려면 소스코드 파일의 맨위 주석을 보면 된다. 어떤 이유인지는 모르겠으나 거의 대부분의 라이센스가 파일 최상단에 명시돼 있다. 위 그림은 리눅스 커널 파일중 하나를 gedit으로 열어본 사진이다. 이 파일은 GNU 라이센스를 사용하고 있다.


1. GPL 라이센스


소스코드 공개 필요 O, 동일한 라이센스 적용 O, 상업적이용 O


GNU라고도 불리는 이 라이센스는 리눅스 커널에 기본 라이센스로 채택되면서 개발자 사이에선 유명한(악명높은) 라이센스가 됐다.. 이 라이센스는 오픈소스의 철학에 기초해서 만들어진 것이기 때문에 '자유를 누린 만큼 너의 코드도 공개해!' 원칙을 가지고 있다. 이 라이센스를 이용해서 만든 소프트웨어는 동일한 GPL 라이센스를 사용해야하고 모두 코드를 공개해야 한다. '내가 얘네 썼는지 어떻게 알겠어' 하면서 무시할 수도 있지만 이미 판례를 통해 법적인 효력까지 갖춘 라이센스이기 때문에 사용할 때는 신중해야 한다. 자세한 내용은 나무위키 전염성 조항 참고하면 좋다. 리눅스 커널 기반 안드로이드 폰 제조사들이 다른 코드는 몰라도 리눅스 커널 소스코드는 공개할 수 밖에 없는 것은 GPL 라이센스 덕분이다.


2. BSD 라이센스


소스코드 공개 필요 X, 동일한 라이센스 적용 X, 상업적 이용 O 


BSD는 자유 소프트웨어 저작권의 한가지로 BSD계열(미국 캘리포니아 대학 버클리에서 개발한 운영체제인 유닉스)에서 주로 채택하고 있는 라이센스다. 규정은 앞서 언급한 GPL과 확연하게 다른데 이 오픈소스 라이센스를 가진 코드는 자유롭게 사용할 수 있고 소스코드를 공개 하지도 않아도 되며 돈받고 팔아도 된다. BSD에서 눈여겨 볼 만한 것은 다루는 것은 누구나 자신의 용도로 사용할 수는 있지만 이 소프트웨어를 사용해서 발생 가능한 모든 리스크와 손해는 본인이 책임지도록 하고 있다. 책임 도피형 라이센스다.


3. MIT 라이센스


소스코드 공개 필요 X, 동일한 라이센스 적용 X, 상업적 이용 O


라이센스 이름에서 추측할 수 있듯이 매사추세스 공과대학에서 만든 소프트웨어 라이센스다. BSD를 기초해서 만든 라이센스라 거의 규정은 똑같다. 소스코드를 공개할 필요도 없고 동일한 라이센스를 적용할 필요도 없으며 이 소프트웨어를 사용해서 발생할 수 있는 손해에 대해서도 책임을 지지 않는다는 것 까지 동일하다. BSD 계열의 소프트웨어에서 일반 소프트웨어로 옮겨오기 위해 만든 라이센스 정도로 생각하면 될 것 같다.


4. Apache 라이센스 


소스코드 공개 필요 X, 동일한 라이센스 적용 X, 상업적 이용 O 


아파치(Apache) 소프트웨어 재단에서 자체적으로 만든 라이센스다. 안드로이드 프레임워크쪽의 대부분의 라이브러리가 이 라이센스 규정을 따르고 있다. BSD와 거의 규정이 동일해 소스코드를 공개할 필요가 없다. 차이점이 있다면 Apache 라이센스는 특허권 측면에서 좀더 완성도를 높여서 Apache 라이센스로 출원한 특허에 대해서는 소스코드 사용자에게 특허의 무제한적 사용을 허가한다는 규정을 담고 있다. 즉 어떤 소프트웨어가 아파치 라이센스를 채택해서 배포했다면 그 소프트웨어가 특허출원이 되어 있어도 사용자에게 특허 사용료를 요구할 수 없다는 뜻이다. 나무위키에 따르면 소스코드를 무료로 공개해놓고 그걸 빌미 삼아 특허권 소송을 제기하는 더티한 플레이를 막기 위한 규정이라 한다. 이런 법적인 안전장치까지 있어서 개발자들이 가져다가 쓸 때 가장 선호하는 라이센스 중에 하나인 것 같다.


5. 추가로 


상세한 규정을 알고 싶으신 분들은 블로그 사이트를 찾는 것보다 정보통신산업 진흥원에서 만든 책자를 보는 것이 도움이 될 것 같다. https://www.oss.kr/oss_license 사이트에 가면 라이센스 가이드를 다운로드 받을 수 있다. 법적인 문제와 연관 될 수 있으니 가능하면 전문가와 상담하는 것이 좋을 것 같다.


728x90

'기술 > 오픈소스' 카테고리의 다른 글

오픈소스 라이센스 정리  (0) 2019.06.09
FFmpeg  (0) 2018.10.31
자동차시장 오픈소스 - 2  (0) 2017.01.31
자동차 시장 오픈소스 - 1  (0) 2017.01.15
이런 오픈 소스도 있다!  (0) 2017.01.07
오픈소스 시작하기  (0) 2017.01.01

Virtio Block 성능 세부 분석

기술/가상화 2019. 5. 20. 20:28 Posted by 아는 개발자

예전 포스트에서는 iozone을 이용해 Virtio block 드라이버의 성능을 간단하게 측정해봤다면  이번 글에는 범용적으로 사용되는 스토리지 벤치마크인 fio를 이용해 Virtio Block의 성능을 좀더 디테일하게 분석해보려고 한다.


실험의 큰 단위를 Sequential, Random으로 나누고 각각의 I/O size를 바꿔봤을 때 Host와 VM의 성능 차이가 어느 정도 나오는지를 분석 해봤다.


1. Seq 512K, Rand 4K


 

 Host

VM 

Ratio 

Random Read 

7705MB/s 

5184MB/s 

67% 

Random Write 

120MB/s 

69.9MB/s 

58% 

Sequential Read 

13.5GB/s 

12.4GB/s 

91% 

Sequential Write 

501MB/s 

346MB/s 

69% 


Sequential에는 512K의 io size를, Random인 경우에는 4K 단위로 io size를 줬다. 이 값들은 실제 스토리지 벤치마크에서 자주 사용되는 단위다. 


결과 값의 편차가 크기는 하지만 Sequential의 경우가 Random의 경우보다 성능이 더 잘 나오는 것으로 보인다. 그런데 random이 sequential 대비 성능이 이렇게 떨어지는 것은 이상해 보인다. 읽는 과정은 크게 차이가 없어 보이는데 말이다.


2. Random 512K 


 

 Host

VM 

Ratio 

 Random Read

13.3GB/s 

12.5GB/s 

93% 

 Random Write

524MB/s 

366MB/s 

70% 


Random Access가 원인이기 보다는 I/O size의 크기에 따라 성능이 달라지는 것 같아서 Random의 경우에도 Sequential과 동일하게 I/O 사이즈를 512K로 줬다. 그러자 Sequential 때와 거의 비슷한 수준으로 성능이 나타났다. I/O 사이즈에 따라서 성능이 들쭉날쭉한다. 


3. Virtio Block Process Sequence




간략하게 표현하면 Virtio Block 드라이버의 처리 루틴은 위와 같은 방식으로 이뤄진다. 실제 스토리지 장치에 접근하는 Block Driver는 Native와 똑같은 장치를 사용하고 있으니 이 지점에서는 Native와 Virtual Machine 모두 똑같은 원리로 동작해 성능 차이가 나지 않는다. 오버헤드가 발생할 수 있는 부분은 이 지점 이외의 부분, VM에서 Block 드라이버까지 요청이 전달 되는 화살표로 표현한 과정에서 발생한다. 화살표 요청의 횟수가 적을수록 Native에 비슷한 성능을 내고 많을수록 저조한 성능을 보인다.


4. I/O operations between 4K random and 512K random 


QEMU의 trace 기능을 이용해 4K일 때와 512K일 때 Virtio Block 함수를 호출하는 횟수를 측정 해봤다.


 

 4K

512K 

 4K / 512K

 Random Read

 279727

25733 

 10.87 : 1

 Random Write 

 3920485

141694 

 27.6687


Read의 경우에는 4K의 경우가 512K보다 10배 정도 더 Virtio Block 관련 함수를 호출하고 Write의 경우에는 27배 더 호출하고 있다. 호출 횟수와 실제 성능과 비교해보면 Random Read의 Host 대비 성능(67%)이 Random Write의 결과값(58%)보다 높게 나온 것으로 보아 Virtio Block 함수 호출 횟수와 어느정도 비례하고 있는 것을 알 수 있다. 


728x90

'기술 > 가상화' 카테고리의 다른 글

Virtio Block 성능 세부 분석  (0) 2019.05.20
Virtio Block 성능 벤치마크  (0) 2018.12.23
QEMU를 이용해 커널 이미지 바꿔서 부팅해보기  (0) 2018.12.20
kvm ioeventfd  (0) 2018.08.11
kvm irqfd  (0) 2018.08.11
vhost  (0) 2018.07.08
TAG Block, FIO, virtio

SDN과 NFV

기술/클라우드컴퓨팅 2019. 4. 21. 13:28 Posted by 아는 개발자

SDN (Software Defined Network)

SDN은 "네트워크 자원을 소프트웨어적으로 가상화해서 운영하는 기술이다" 라고 한줄만 가지고 이해하기 모호하니 딱 정의하기에 앞서 이 기술이 나오게 된 배경부터 주목해보자. 지금과 같이 인터넷이 크게 발달하기 전에는 트래픽의 양이 많지 않았고 트래픽의 패턴도 서버와 클라이언트에게 주고받는 데이터 정도로 단순했다. 그런데 스마트폰의 시대가 시작되면서 트래픽의 양이 급격히 증가하기 시작했고 패턴도 단순히 클라이언트와 주고 받는 것이 아니라 다른 서버의 데이터베이스에 접근하는 경우가 생기기 시작했다. 이전보다 네트워크 관리 하는 것이 훨씬 복잡해졌다.

과거에는 네트워크 장비들이 모두 하드웨어로만 조작 할 수 있었다. 트래픽이 감당 할 수 없을 정도로 많아진 경우에는 서버를 더 사야했고 다른 서버로 연결 할 때는 연결시 필요한 방화벽, 스위치 등등도 추가로 구매해야 했다. 이런 경우들은 비용문제도 있을 뿐만 아니라 기존 서버에 새로운 하드웨어를 증설하는 일이 꽤나 시간도 오래 걸리고 번거롭다는 문제가 있었다. 가상화 기술이 나오면서 네트워크 장치들도 소프트웨어적으로 해결해 관리를 편하게 하려는 움직임이 있었는데 이런 요구사항으로 나오게 된 것이 SDN이다. 


출처: 주니퍼

SDN을 사용하면 서버를 여러대를 살 필요 없이 한번에 성능 좋은 서버를 산후 여러개의 VM을 돌려서 복수의 서비스를 돌릴 수 있다. 특정 서비스에 트래픽이 몰리면 이전처럼 서버를 추가로 증설할 필요 없이 트래픽이 몰리는 VM에 하드웨어 자원을 더 많이 할당해서 해결 할 수 있다. 예전처럼 하드웨어적으로 처리할 필요가 없어 관리하기가 쉬우며 놀고 있는 다른 VM의 자원들을 끌어다가 사용할 수 있기 때문에 비용적으로도 효율적이다.


NFV (Network Function Virtualization)

NFV는 앞서 설명한 SDN을 소프트웨어적으로 구현할 수 있는 기술이다. 구체적으로 설명하면 switch, firewall 같은 표준화된 네트워크 장비들을 소프트웨어적으로 만들수 있으며 서버에 설치된 네트워크, 스토리지, CPU 등 컴퓨팅 리소스의 orchestration을 담당하는 컴포넌트다. SDN이 NFV를 포함하는 기술이라고 보면 될 것 같다. 사실은 거의 전부라고 생각되긴 하지만.

출처: 주니퍼

세세하게 들어가면 끝도 없으니 위 그림의 컴포넌트들에 대해서 짤막하게만 알아두고 가자.

VNF (Virtualized Network Functions)

가상화 해서 만든 네트워크 장비들이다. 가상의 방화벽, 스위치, 로드밸런서 등등이 이에 해당한다. 필요에 따라서 여러 개를 만들 수 있다. 클라우드 환경에서 돌아가고 있는 서비스들이 바라보는 네트워크 장비이기도 하다.

NFVI (NFV Infrastructure)

앞서 설명한 VNF와 실제 하드웨어 장비간의 인터페이스를 담당한다. Virtualization Layer를 통해서 물리 장비에 매핑 할 수 있는 컴포넌트다. API라고 생각하면 좋을 것 같다.

NFV MANO(Management and Orchestration)

가상의 네트워크 장비(VNF)를 NFV 인프라스트럭처 (NFVI)를 이용해 실제 하드웨어에 매핑하고 NFV에서 자원 할당 문제로 번번히 언급되는 VNF 스케줄링, VNF 연결문제(Chain Composition) 등등을 다루는 핵심 컴포넌트다. 

728x90

'기술 > 클라우드컴퓨팅' 카테고리의 다른 글

SDN과 NFV  (0) 2019.04.21
Kubernetes 소개  (0) 2018.06.23
오픈스택 구조 분석  (0) 2017.03.07
오픈스택이란?  (0) 2017.03.07
클라우드 가상화와 Docker  (2) 2017.03.05
클라우드 컴퓨팅(Cloud Computing)  (0) 2017.02.25
TAG NFV, SDN

Virtio Block 성능 벤치마크

기술/가상화 2018. 12. 23. 14:08 Posted by 아는 개발자

QEMU와 KVM환경에서 띄운 Ubuntu VM의 스토리지 성능을 Virtio Block을 사용할 때와 아닐 때를 각각 나누어서 측정을 해보았고 최적화 옵션을 적용할 때 Host대비 얼마정도의 성능을 내는지도 실험 해봤다.


1. 실험 방법


벤치마크툴은 iozone을 사용했고 적용한 옵션은 다음과 같다.

iozone -e -I -a -s 100M -r 4k -r 4096k -r 16384k -i 0 -i 1 -i 2


여기서 주목해야할 옵션 항목만 짚고 넘어가자.

  • -r 은 record할 사이즈를 말하며 여러개의 인자를 전달하면 인자의 개수만큼 측정한다.
  • -i 는 측정할 실험 항목을 의미한다. (0=write/rewrite, 1=read/re-read, 2=random-read/write)
  • -s 는 읽고 쓸 데이터의 크기를 의미한다.

2. 실험환경


Host 환경


- CPU: Intel(R) Core(TM) i5-6600 CPU @ 3.30GHz

- Memory: 8G

- Storage: SSD


Guest VM 환경


- CPU: Intel(R) Core(TM) i5-6600 CPU @ 3.30GHz

- Memory: 4G

- Storage: VirtioBlock(1) / Qemu Storage(2)


3. 측정결과 및 분석


(1) Record Size: 4K


4K

write

rewrite

read

reread

R/Read

R/Reread

Host

108531

140209

130116

149202

41372

108057

VM(VirtioIO)

56260

76539

74808

76864

33816

73393

VM(QEMU Storage)

10231

13252

11558

15289

13324

17782


(2) Record Size: 4096K


4096K

write

rewrite

read

reread

R/Read

R/Reread

Host

475827

493225

500618

506168

506449

491150

VM(VirtioIO)

381458

385537

406131

403043

408653

387712

VM(QEMU Storage)

254328

260574

261297

268813

254842

259518


(3) Record Size: 16384K


16384K

write

rewrite

read

reread

R/Read

R/Reread

Host

495826

502281

524808

521351

520112

502445

VM(VirtioIO)

394283

431118

423213

417768

435611

418307

VM(QEMU Storage)

276397

269190

273982

288246

288831

268841


* 결과 단위는 KB/S이다.


- 측정 단위가(Record Size) 작을수록 VirtioBlock과 QEMU Storage의 성능차이가 많이 나며 커질 수록 어느정도 좁혀지나 40~60% 정도 Virtio Block의 성능이 더 우수한 것으로 수렴한다.


- QEMU Storage 일때는 Host의 절반 정도(53%~55%)이지만  Virtio Block 옵션을 적용하면 Host 대비 80% 정도의 성능을 낸다. Host의 Storage 성능이 워낙 빠르기 때문에 (SSD) 20% 정도의 성능을 손해보더라도 불편함없이 사용할 수 있을 것 같다.


- SSD로 띄운 VM은 최적화 옵션을 넣지 않아도 HDD로 띄운 Host보다 성능이 우수하게 나온다. 성능 구린 PC를 하나 더 사는 것 보다는 성능 좋은 PC에 Virtual Machine을 띄우는게 경제적으로나 성능적으로나 우수해보인다.


16384K

write

rewrite

read

reread

R/Read

R/Reread

Host(HDD)

135480

126629

155426

168672

136012

126976

VM (Virtio based SSD)

394283

431118

423213

417768

435611

418307

VM(QEMU Storage)

276397

269190

273982

288246

288831

268841


728x90

'기술 > 가상화' 카테고리의 다른 글

Virtio Block 성능 세부 분석  (0) 2019.05.20
Virtio Block 성능 벤치마크  (0) 2018.12.23
QEMU를 이용해 커널 이미지 바꿔서 부팅해보기  (0) 2018.12.20
kvm ioeventfd  (0) 2018.08.11
kvm irqfd  (0) 2018.08.11
vhost  (0) 2018.07.08

전가상화를 지원하는 QEMU는 게스트 커널을 수정하지 않고 띄울수 있다는 장점이 있지만 virtio 같은 최적화 옵션을 사용하려면 커널의 일부 수정이 필요하다. 기존에 게스트 이미지에 내장된 커널을 수정하는 방법으로 가장 쉽게 떠올릴 수 있는 것은 게스트를 띄운 다음 이 안에서 커널 코드를 수정하고 빌드하는 것인데 답답한 게스트의 성능때문에 오래 걸리고 답답하다.


QEMU에서는 이런 점을 고려해서 파라미터를 이용해 사용할 커널 이미지를 지정할 수 있도록 만들었다. 개발자는 호스트 PC 환경에서 게스트에서 사용할 커널 이미지를 빌드한 후 스크립트에 -kernel 파라미터로 지정하면 된다. 


#!/bin/bash
DISK_PATH=.

qemu-system-x86_64 \
        -cpu host \
        -smp 8 \
        -enable-kvm -m 2048 \
        -kernel ./bzImage \
        -append "root=/dev/vda1 console=ttyS0" \
        -drive file=${DISK_PATH}/ubuntu.qcow2,cache=none,if=virtio \
        -display gtk \


위 스크립트에서 -append라는 옵션이 보이는데 이건 커널의 커맨드라인(cmdline)으로 들어갈 스트링을 지정할 수 있는 파라미터다. 새로 빌드한 커널에는 CONFIG_VIRTIO_BLK 옵션을 넣었기 때문에 커널에서 마운트할 디바이스의 이름(/dev/vda1)를 이곳에 기입했고 console로 시리얼 값을 보기 위해 ttyS0 옵션을 넣었다. 이게 없으면 따로 커널 코드에 넣지 않는 이상 마운트하다가 죽는다.


위의 올린 스크립트를 이용해 실행해보니 부팅이 정상적으로 된다. VIRTIO_BLK 옵션이 들어가서 그런지 예전보다 부팅이 조금 빨라진 것 같았다. 그래도 커널이 정상적으로 바뀌었는지 'uname -r' 명령어로 확인해봤다. Makefile에 넣은 EXTRAVERSION 값이 내 아이디로 들어간 것으로 보아 커널이 바뀐 것을 확인할 수 있었다.




728x90

'기술 > 가상화' 카테고리의 다른 글

Virtio Block 성능 세부 분석  (0) 2019.05.20
Virtio Block 성능 벤치마크  (0) 2018.12.23
QEMU를 이용해 커널 이미지 바꿔서 부팅해보기  (0) 2018.12.20
kvm ioeventfd  (0) 2018.08.11
kvm irqfd  (0) 2018.08.11
vhost  (0) 2018.07.08

JAVA 파일 생성/읽기/쓰기

기술 2018. 11. 25. 13:11 Posted by 아는 개발자

JAVA는 객체지향관점에 따라 시스템에 존재하는 파일이나 폴더를 하나의 객체로 보고 관리한다. 시스템 내에서 특정 위치의 파일을 읽거나 삭제 또는 생성하고 싶다면 해당 파일에 대한 객체를 생성해주면 된다. 파일 객체는 아래의 예제 코드처럼 파일 경로와 파일 이름을 생성자의 인자로 넣어주면 간단히 생성할 수 있다.


File file = new File(filepath + File.separator + fileName);


1. 파일 유무 확인 및 생성


파일 객체 안에는 여러가지 함수가 있는데 가장 요긴하게 쓰이는 함수는 exists() 함수다. 주로 파일을 생성하거나 사용하기 전에 해당 위치에 파일 또는 폴더가 이미 존재하는지 아닌지를 확인할 때 사용한다. 아래의 코드는 파일의 유무를 확인하고 없는 경우 파일을 생성하는 예제다. 폴더를 생성 할 때는 createNewFile을 mkdir 함수로 변경하면 된다.


if (!file.exists()) {
    try {
        file.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
}


2. 파일 읽기


파일에 값을 읽을때는 FileInputStream, InputStreamReader, BufferedReader 객체를 사용한다. 예제코드를 따라가보면 세번의 객체 생성 작업이 있는 것을 볼 수 있는데 모두 방금 전에 생성한 객체를 인자로 사용한다. 이 방법은 객체가 가진 데이터 값을 변환하는과정이다.


FileInputStream은 시스템에 존재하는 파일의 byte를 획득하고 InputStreamReader는 InputStream 획득한 복수개의 byte 를 해석해 특정한 문자열 집합인 charset으로 만든다. 최종적으로 BufferedReader에서는 charset값을 읽어서 string으로 변환한다.


try {
    FileInputStream fIn = new FileInputStream(file);
    InputStreamReader isr = new InputStreamReader(fIn);
    BufferedReader inBuff = new BufferedReader(isr);

    while (true) {
        try {
            inputLine = inBuff.readLine();
        } catch(IOException e) {
            e.printStackTrace();
        }

        if (inputLine == null)
            break;

        outStringBuf.append(inputLine);
    }
} catch(FileNotFoundException e) {
    e.printStackTrace();
}


3. 파일 쓰기


파일에 값을 쓸 때는 FileOutputStream, OutputStreamWriter 객체를 사용한다. OutputStreamWriter는 Charater 형태의 stream을 byte 형태로 전환해 값을 입력 할 수 있는 객체다. OutputStreamWriter 생성시 변환한 값을 입력할 Stream 객체를 전달 받는데 이때 File 객체를 품은 FileOutputStream 객체를 사용하면 file에 값을 사용하도록 할 수 있다.


final String welcomeString = new String(initialValue);
FileOutputStream fOut = new FileOutputStream(file);
OutputStreamWriter osw = new OutputStreamWriter(fOut);

osw.write(welcomeString);
osw.flush();
osw.close();


728x90

'기술' 카테고리의 다른 글

JAVA 파일 생성/읽기/쓰기  (0) 2018.11.25
스택, 힙, 코드, 데이터영역  (6) 2018.11.10
VNC와 RDP  (2) 2018.09.12
jupyter notebook 소개  (0) 2018.08.04
URI (Uniform Resource Identifier)  (0) 2018.07.02
libgdx - Viewport  (0) 2018.06.22

스택, 힙, 코드, 데이터영역

기술 2018. 11. 10. 14:20 Posted by 아는 개발자


OS에서 프로세스가 바라보는 메모리 영역은 크게 코드(Code), 데이터(Data), 힙(Heap), 스택(stack) 영역으로 나뉘어진다. 




Code 영역


프로세스가 실행할 코드와 매크로 상수가 기계어의 형태로 저장된 공간이다. 컴파일 타임에 결정되고 중간에 코드를 바꿀 수 없게 Read-Only 로 지정돼있다.


Data 영역


코드에서 선언한 전역변수 또는 static 변수 등등이 저장된 공간이다. 전역변수/static 값을 참조한 코드는 컴파일 하고 나면 Data 영역의 주소값을 가르키도록 바뀐다. 실행 중도에 전역변수가 변경 될 수도 있으니 이 영역은 Read-Write로 지정돼있다.


단, 초기화 되지 않는 전역 변수는 BSS 영역에 할당된다.


Stack 영역 


자료구조로 많이 알려진 Stack은 프로세스의 메모리 공간을 관리하기 위한 알고리즘중 하나이다. 이 영역은 함수 안에서 선언된 지역변수, 매개변수, 리턴값, 돌아올 주소 등등이 저장되고 함수 호출시 기록하고 종료되면 제거한다. 기록하고 종료하는 메커니즘은 자료구조에서 배운 후위선출(LIFO) 방식을 따른다. 


컴파일 타임에 크기가 결정되기 때문에 무한히 할당 할 수 없다. 재귀함수가 너무 깊게 호출되거나 함수가 지역변수를 너무 많이 가지고 있어 stack 영역을 초과하면 stack overflow 에러가 발생한다.


Heap 영역


프로그래머가 필요할 때마다 사용하는 메모리 영역. Code, Data, Stack 영역과는 다르게 Heap은 런타임에 결정된다. 프로그래머는 malloc, calloc으로 Heap 영역의 메모리를 사용할 수 있다. 데이터 배열의 크기가 확실하지 않고 변동이 있을 때 Heap 영역을 활용해서 메모리를 할당한다. 단 사용하고 난 다음에는 반드시 해제를 해야 한다. 안그러면 memory leak이 발생한다.


스택보다 할당할 수 있는 메모리 공간이 많다는 것이 장점이지만 포인터로 메모리 영역을 접근해야 하기 때문에 다른 자료구조에 비해서 데이터를 읽고 쓰는게 느리다. 


참고자료


- 위키백과


728x90

'기술' 카테고리의 다른 글

JAVA 파일 생성/읽기/쓰기  (0) 2018.11.25
스택, 힙, 코드, 데이터영역  (6) 2018.11.10
VNC와 RDP  (2) 2018.09.12
jupyter notebook 소개  (0) 2018.08.04
URI (Uniform Resource Identifier)  (0) 2018.07.02
libgdx - Viewport  (0) 2018.06.22
  1. jiminpark 2020.04.02 16:17  댓글주소  수정/삭제  댓글쓰기

    c언어에서 String Literal은 data영역에 저장돼서 char* name = "jiminpark" 과 같은선언과 초기화에서 name을 통해 문자열 변경이 불가능하다고 하는데
    다른 여타 리터럴들은 위의 어디 영역에 저장될까요?

    • 아는 개발자 2020.04.02 18:13 신고  댓글주소  수정/삭제

      코드상에서 선언된 변수들은 데이터 영역에 저장이 되구요, 수정이 불가능한 변수들은 .rodata{} 영역, 수정이 가능한 변수는 .data{} 영역에 저장될 것 같습니다!

  2. 2021.01.26 15:34  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  3. 안녕하세요 2021.01.28 23:20  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 써주신 글이 도움이 많이 되었습니다. 혹시 제 벨로그에 출처를 밝히고 인용해도 될까요?

스핀락, 뮤텍스, 세마포어

기술/컴퓨터사이언스 2018. 11. 7. 20:40 Posted by 아는 개발자


여러 개의 프로세스가 동시에 실행 할 수 있는 멀티 코어 환경은 사용자의 시스템 전반의 성능을 향상 시켜 주었지만 개발자들에게는 '프로세스간 공유 자원 접근 관리'라는 골치아픈 숙제를 남겼다. 다수의 컴포넌트가 공유중인 자원을 동시에 읽거나 수정할 때 생기는 문제들을 포괄해서 '동기화 문제'라고 하며 대부분의 소프트웨어는 스핀락, 뮤텍스, 세마포어라는 자료구조들을 이용해 해결 한다. 이번 포스트에서는 각 자료구조들의 작동 원리와 차이점에 대해서 정리해보려고한다.


스핀락 (spinlock)


특정한 자료구조를 획득(lock) 또는 해제(unlock) 함으로서 공유 데이터에 대한 접근 권한을 관리하는 방법이다. 권한을 획득하기 전까지 CPU는 무의미한 코드를 수행하는 busy waiting 상태로 대기하고 있다가 접근 권한을 얻으면 내부 코드를 수행하고 종료후 권한을 포기한다. 상태가 획득/해제 밖에 없기 때문에 공유 영역에는 하나의 컴포넌트만 접근 할 수 있으며 획득과 해제의 주체는 동일해야한다.


// arch/arm64/kernel/debug-monitors.c
static DEFINE_SPINLOCK(step_hook_lock);

void register_step_hook(struct step_hook *hook)
{
    spin_lock(&step_hook_lock);
    list_add_rcu(&hook->node, &step_hook);
    spin_unlock(&step_hook_lock);
}

CPU를 선점하고 있는 busy waiting 상태로 대기하기 때문에 권한이 해제되는 대로 빨리 작업을 수행할 수 있는 장점이 있지만 선점 기간동안 다른 프로세스 작업이 지연될 수 있는 오버헤드도 존재한다. 그래서 짧게 수행할 수 있는 작업에 주로 사용된다.


뮤텍스 (mutex)


획득(lock), 해제(unlock) 두가지  상태가 존재해 하나의 컴포넌트만 공유영역에 접근 할 수 있고 획득과 해제의 주체가 동일해야한다는 점은 스핀락과 동일하나 권한을 획득 할 때 까지 busy waiting 상태에 머무르지 않고 sleep 상태로 들어가고 wakeup 되면 다시 권한 획득을 시도한다. 시스템 전반의 성능에 영향을 주고 싶지 않고 길게 처리해야하는 작업인 경우에 주로 사용한다. 주로 쓰레드 작업에서 많이 사용된다.


 
// arch/arm64/mm/dma-mapping.c
mutex_lock(&iommu_dma_notifier_lock);
list_for_each_entry_safe(master, tmp, &iommu_dma_masters, list) {
   if (data == master->dev && do_iommu_attach(master->dev,
        master->ops, master->dma_base, master->size)) {
    list_del(&master->list);
    kfree(master);
    break;
   }
}
mutex_unlock(&iommu_dma_notifier_lock);


세마포어 (Semaphore) 


스핀락, 뮤텍스와는 다르게 표현형이 정수형이며 이점을 살려 하나 이상의 컴포넌트가 공유자원에 접근하도록 허용할 수 있다. 예로 들면 뮤텍스와 스핀락은 옷가게에 한 개의 피팅룸만 있었던 반면 세마포어는 하나 이상의 피팅룸이존재하는 셈. 물론 세마포어를 이진수의 형태로 사용해 개념적으로 뮤텍스와 스핀락처럼 사용하는 것도 가능하다. 정수형인 만큼 획득과 해제 같은 명령이 아니라 값을 올리고 줄이는 방식으로 세마포어를 사용하다. 세마포어의 값이 0이면 기다려야 한다.


// fs/btrfs/disk-io.c
    down_read(&fs_info->cleanup_work_sem);
    if ((ret = btrfs_orphan_cleanup(fs_info->fs_root)) ||
        (ret = btrfs_orphan_cleanup(fs_info->tree_root))) {
        up_read(&fs_info->cleanup_work_sem);
        close_ctree(tree_root);
        return ret; 
    }    
    up_read(&fs_info->cleanup_work_sem);


스핀락과 뮤텍스와 달리 세마포어는 해제(Unlock)의 주체가 획득(Lock)과 같지 않아도 된다. 즉 어떤 프로세스가 세마포어의 값을 감소시켜도 다른 프로세스가 풀어줄 수 있다. 이 특징을 고려해 세마포어를 시그널(Signal) 원리로 사용 할 수도 있다.


아래의 그림처럼 TASK A가 먼저 스케줄링돼 나중에 해야할 작업(post_work)을 먼저 할 수도 있는 상황이라고 해보자. 초기 세마포어의 값을 0으로 초기화해 post_work를 호출하지 못하게 막는다. 그러고 난 후 TASK B의 pre_work가 끝난 후에 세마포어의 값을 올려서 TASK A가 post_work를 호출할 수 있도록 할 수 있다.




참고자료


- FEABAHAS 블로그


728x90

'기술 > 컴퓨터사이언스' 카테고리의 다른 글

멀티쓰레드 동시성  (0) 2021.06.05
프로스세와 스레드  (0) 2021.06.05
스핀락, 뮤텍스, 세마포어  (0) 2018.11.07
RCU (Read-Copy Update)  (0) 2018.10.30
Cgroup (Control Group)  (0) 2018.09.15
CPU pinning과 taskset  (0) 2018.08.27

FFmpeg

기술/오픈소스 2018. 10. 31. 20:40 Posted by 아는 개발자




FFmpeg는 동영상업계에 종사하는 사람들이라면 아주 유명하고 지대한 영향력을 쥐고 있는 오픈소스 프로젝트다. FFmpeg의 영향력이 얼마나 막강한지 나무위키에서는 FFmpeg를 '마이크로소프트+애플 정도의 영향력을 가지고 있는 멀티미디어계의 최종 보스이며 갑자기 유료화를 시전한다면 내일부터 전세계 동영상 시청이 불가할지도 모른다'고 표현할 정도이니 FFmpeg을 접해보지 못한 개발자라면 오픈소스계에서 리눅스 커널과 거의 맞먹는 위상을 가지고 있다고 짐작해볼 수 있다.


실제로 사용해보면 FFmpeg가 왜 이렇게 막강한 영향력을 가지고 있는지 어느정도 짐작해볼 수 있다. 동영상에 간단히 수정할 일이 생겼다고 해보자. 평소 같으면 다음 팟플레이어, 곰인코더를 설치해서 깔고 버벅거리는 로딩바를 보며 힘겹게 작업했을 것이다. 그런데 FFmpeg을 사용하면 리눅스 커맨드 한줄 만으로도 쉽고 빠르게 작업할 수 있다. 다음은 FFmpeg을 이용해서 동영상 확장자를 변경해본 커맨드다. 정말 직관적이고 심플하지 않은가. 


ffmpeg -i 20181031_194630.mp4 20181031_194630.avi


윗 명령어처럼 단순히 확장자를 변경하는 것 뿐만 아니라 동영상의 사이즈를 줄이고 자르고 비율을 조정하는 일까지 FFmpeg를 활용하면 한줄로 쉽게 할 수 있다. 기능이 많을 뿐만 아니라 속도도 최적화돼 있어 시간도 얼마 걸리지 않는다. 힘겹게 로딩바를 바라보며 프로그램이 죽을까봐 초조해했던 팟 인코더와는 최적화 수준의 차원이 다르다.


놀라운 사실은 지금까지 사용해왔던 다음 팟플레이어, 곰인코더 모두 FFmpeg을 사용한 소프트웨어라는 것. 그리고 이 둘뿐만 아니라 웬만한 사람들은 알만한 인코더들도 모두 FFmpeg 을 사용해서 만들어졌다. 그 예로 아래와 같은 소프트웨어들이 있다.


제트오디오VX, 유마일, 엔젤인코더, 샤나인코더, Freemake, Mencoder, 곰인코더, 팟인코더, Handbrake 등등


그렇다면 위의 나열한 소프트웨어들이 한 일이 무엇이냐고 묻는다면... 커맨드 환경에 익숙하지 않는 사용자을 위해 키보드와 마우스를 활용한 UX를 제공하고 설정 받은 옵션들을 조합해 FFmpeg 라이브러리를 호출한 정도? 자기네 입맛에 맞게 어느정도 수정은 했겠지만 소수에 지나지 않았을 것이다. 그럼 대체 얘네들은 왜 느렸던 걸까...?



출처


- 나무위키 FFmpeg


728x90

'기술 > 오픈소스' 카테고리의 다른 글

오픈소스 라이센스 정리  (0) 2019.06.09
FFmpeg  (0) 2018.10.31
자동차시장 오픈소스 - 2  (0) 2017.01.31
자동차 시장 오픈소스 - 1  (0) 2017.01.15
이런 오픈 소스도 있다!  (0) 2017.01.07
오픈소스 시작하기  (0) 2017.01.01

RCU (Read-Copy Update)

기술/컴퓨터사이언스 2018. 10. 30. 21:41 Posted by 아는 개발자

공유중인 값을 읽는 쓰레드와 업데이트하는 쓰레의 개수가 각각 하나인 경우에는 경우에는 spin_lock을 이용해도 별 어려움 없이 동기화 작업을 수행 할 수 있다. 그런데 값을 읽는 프로세스가 두개 이상이 될 때부터는 동기화 작업이 골치가 아파진다. 아래의 코드처럼 두개 이상의 쓰레드가 공유중인 val 값을 읽으려고 하고 한개의 쓰레드가 값을 업데이트 하고 있다고 해보자. 본래의 목적은 reader를 호출하는 thd[0]와 thd[1] 쓰레드가 writer를 호출하는 thd[2] 쓰레드가 업데이트 하는 값을 동일하게 읽는 것이었다.


reader 두개를 writer보다 앞서서 생성한 덕분에 초반에는 어느 정도 동기화가 유지가 되겠지만 시간이 지나면서 쓰레드의 priority 값이 달라지게되고 어떤 기점에서 thd[2] 쓰레드가 thd[0]와 thd[1] 사이에 실행될 수도 있다. 이런 경우 thd[0]와 thd[1]은 같은 값을 읽지못하게 된다.


pthread_spinlock_t lock;
int val;

void *reader() {
    int read;
    while (1) {
        pthread_spin_lock(&lock);
        read = val;
        printf("reader1: %d\n", val);
        pthread_spin_unlock(&lock);
    }
}

void *writer() {
    while(1) {
        pthread_spin_lock(&lock);
        val++;
        pthread_spin_unlock(&lock);
    }
}

int main() {
    pthread_t thd[3];

    pthread_spin_init(&lock, PTHREAD_PROCESS_SHARED);
    pthread_create(&thd[0], NULL, reader, NULL);
    pthread_create(&thd[1], NULL, reader, NULL);
    pthread_create(&thd[2], NULL, writer, NULL);

    pthread_join(thd[0], NULL);
    pthread_join(thd[1], NULL);
    pthread_join(thd[2], NULL);

    printf("DDD\n");

    return 1;
}


이런 경우를 위해 리눅스에서는 RCU(Read-Copy Update)라는 라이브러리를 제공한다. RCU를 사용하면 여러 개의 읽기 작업과 한 개의 쓰기 작업의 동기화를 구현 할 수 있다.  단, 여러 개의 쓰기 작업의 동기화는 지원이 안된다. LWN에서 소개한 예시코드로 간단히 사용 방법을 익혀보자.


 struct foo {
   struct list_head list;
   int a;
   int b;
   int c;
 };
 LIST_HEAD(head);

 /* . . . */

 p = kmalloc(sizeof(*p), GFP_KERNEL);
 p->a = 1;
 p->b = 2;
 p->c = 3;
 list_add_rcu(&p->list, &head);

// Thread1
 rcu_read_lock();
 list_for_each_entry_rcu(p, head, list) {
   do_something_with(p->a, p->b, p->c);
 }
 rcu_read_unlock();

// Thread2
 q = kmalloc(sizeof(*p), GFP_KERNEL);
 *q = *p;
 q->b = 2;
 q->c = 3;
 list_replace_rcu(&p->list, &q->list);
 synchronize_rcu();
 kfree(p);


11~15 라인에서는 `foo`구조체 포인터로 선언된 `p`변수에 메모리 공간을 할당하고 값을 대입한 후 `list_add_rcu`라는 함수를 이용해 리스트에 넣는 작업이다. `list_head` 변수가 확장성이 좋아 RCU에서도 list를 이용해 여러 개의 변수를 관리할 수 있도록 했다.


Thread1 은 읽는 작업을 수행하는 reader다. `p`값을 읽어오기 전에 `rcu_read_lock()` 함수를 호출해서 접근 권한을 얻고 작업을 수행한 다음에는 `rcu_read_unlock()`으로 lock을 풀어준다. 다른 쓰레드도 읽는 작업을 수행한다면 위와 동일한 코드를 탄다.


Thread2는 rcu 리스트의 값을 업데이트하는 쓰레드다. `list_replace_rcu()` 함수는 첫번째 인자의 값을 두번째 인자의 값으로 바꾸는 함수다. 그리고 바로 아래 `synchronize_rcu()`는 다른 쓰레드에서 rcu 리스트의 값을 읽는 작업이 모두 종료될 때 까지 기다린 후 수정한 정보를 업데이트 한다. 이 함수가 호출되지 않으면 RCU 리스트 수정 함수를 호출해도 reader쪽에서 읽는 값은 동일하다. 이 함수를 이용해 읽는 작업과 쓰는 작업의 동기화를 맞춰 줄 수 있다.


* 더 상세한 동작 메커니즘을 알고 싶으신 분은 LWN 을 참조하면 좋을 것 같다.

728x90

'기술 > 컴퓨터사이언스' 카테고리의 다른 글

프로스세와 스레드  (0) 2021.06.05
스핀락, 뮤텍스, 세마포어  (0) 2018.11.07
RCU (Read-Copy Update)  (0) 2018.10.30
Cgroup (Control Group)  (0) 2018.09.15
CPU pinning과 taskset  (0) 2018.08.27
스핀락  (0) 2018.07.23

Cgroup (Control Group)

기술/컴퓨터사이언스 2018. 9. 15. 13:37 Posted by 아는 개발자

Cgroup (Control Group)


Cgroup은 CPU, Network, Memory 등 하드웨어 자원을 그룹별로 관리 할 수 있는 리눅스의 모듈이다. 하나 또는 복수의 장치를 묶어서 하나의 그룹을 만들 수 있으며 개별 그룹은 시스템에서 설정한 값만큼 하드웨어를 사용할 수 있다. 설정하는 값은 예를 들면 이런 것들이다.

  • 어떤 그룹이 CPU를 더 많이 차지 할 것인지?
  • 그룹이 얼마만큼 메모리를 사용 할 수 있는지?
  • 네트워크 우선순위를 얼만큼 줄 것인가.
시스템에 생성된 프로세스들은 장치 별로 특정한 cgroup에 속하며 프로세스가 사용하는 하드웨어 자원의 총량은 속한 cgroup의 통제를 받게 된다. 이말은 곧 프로세스가 아무리 효율적으로 동작하도록 만들어져 있어도 cpu 점유율이 낮은 cgroup에 속해 있으면 속도가 느릴 수 밖에 없고 cgroup 자체가 CPU 점유율이 높아도 이 그룹에 속한 프로세스가 많으면 전반적인 속도가 저하 될 수 밖에없다. 리눅스에선 하드웨어와 프로세스 사이에 cgroup 계층을 두어서 자원을 관리할 수 있도록 만들었다.

하드웨어와 cgroup, 프로세스간의 관계는 아래의 그림처럼 표현 할 수 있다.



시스템에 어떤 cgroup이 있는지 그리고 프로세스가 어떤 cgroup에 매핑되어 있는지는 리눅스 파일 시스템에 모두 매핑 되어 있다. 간단히 cat과 ls 명령어로 조회가 가능하다.


// System에 설치된 cgroup 목록
kwony@kwony:~$ ls /sys/fs/cgroup/
blkio      cpuacct      devices  memory            net_prio    systemd
cgmanager  cpu,cpuacct  freezer  net_cls           perf_event
cpu        cpuset       hugetlb  net_cls,net_prio  pids

// 프로세스별 cgroup 정보. /proc/[pid]/cgroup
kwony@kwony:~$ cat /proc/8502/cgroup 
11:pids:/user.slice/user-1000.slice
10:cpuset:/
9:net_cls,net_prio:/
8:devices:/user.slice
7:freezer:/
6:perf_event:/
5:hugetlb:/
4:cpu,cpuacct:/
3:blkio:/
2:memory:/
1:name=systemd:/user.slice/user-1000.slice/session-c2.scope


728x90

'기술 > 컴퓨터사이언스' 카테고리의 다른 글

스핀락, 뮤텍스, 세마포어  (0) 2018.11.07
RCU (Read-Copy Update)  (0) 2018.10.30
Cgroup (Control Group)  (0) 2018.09.15
CPU pinning과 taskset  (0) 2018.08.27
스핀락  (0) 2018.07.23
eventfd  (0) 2018.07.18

VNC와 RDP

기술 2018. 9. 12. 22:10 Posted by 아는 개발자


원격 데스크톱을 연결하는 프로토콜은 여러 가지가 존재하는데 이중 대표적인 방법이 VNC와 RDP다. 두 프로토콜 모두 단순히 커맨드 라인 창만 띄우는 ssh를 넘어서서 원격 PC의 그래픽 인터페이스까지 연결해준다는 점은 동일하지만 둘의 세부 동작 방식과 주 사용 용도는 조금 다르다. 이번 포스트에서는 VNC와 RDP의 원리를 분석하고 각각의 차이점을 간단히 정리해보고자 한다.


VNC (Virtual Network Computing)


VNC는 RFB(Remote Frame Buffer)프로토콜 방식을 이용해 서버에서 보낸 화면 정보를 클라이언트에 설치된 그래픽 라이브러리를 이용해 그리는 방식이다. 사용하기 위해선 원격으로 접속하려는 PC에서는 지속적으로 화면 정보를 RFB의 형태로 변환해 전송하는 서버가 실행되고 있어야 하고 원격 PC를 조종하려는 PC에서는 받은 RFB 데이터를 변환하고 그래픽 드라이버를 이용해 화면에 뿌려줄 수 있는 클라이언트 소프트웨어가 설치되어 있어야 한다.




VNC는 주로 클라우드 상에서 동작하는 서버 VM을 동작하기 위해서 사용하는 프로토콜이며 QEMU 같은 가상화소프트웨어에서는 VNC를 디스플레이 옵션으로 기본으로 제공하고 있다. VM을 VNC로 연결하려는 경우 Hypervisor에서 VNC 서버만 실행해준다면 서버 VM에는 별도의 소프트웨어 설치가 필요 없으며 원격에서 접속하는 PC도 VNC Client 프로그램만 설치하면 Guest의 화면을 볼 수 있다.


하지만 장점 만큼이나 단점도 있다. VNC를 사용하면 직접 접속했을 때보다 화질이 좋지 않고 화면의 움직임도 더디고 자연스럽지 않다. 실제 원격 데스크톱은 더 빠르게 움직이고 있지만 데이터 전달 시간과 클라이언트 소프트웨어가 변환하는 시간이 갉아 먹으면서 실제 사용자에게는 느리게 보이게 되는 것이다. 아래 그림은 Ubuntu 18.04를 VM으로 띄우고 Host 컴퓨터에서 VNC로 접속한 것인데 보다시피 화면의 그래픽 화질이 낮다. 


화질 문제는 더 좋은 VNC software를 사용하면 어느정도 해결이 가능하다.


그리고 VNC는 원격에서 화면을 볼 수 있는 기능만 지원 할 뿐 클라이언트 별로 사용자 세션을 가질 수 있는 기능은 없다. 그래서 하나의 원격 PC에 접속한 다수의 클라이언트는 모두 동일한 화면과 마우스, 키보드 인터페이스를 공유한다. 즉 내가 마우스를 움직이면 다른 클라이언트의 화면에서도 똑같이 움직이고 다른 클라이언트가 키보드를 입력하면 내 화면에서도 입력이 되는 셈이다. 아래 그림처럼 Ubuntu 18.04 VM을 두 개의 VNC 클라이언트로 접속해보니 모두 동일한 Youtube 동영상을 실행하게 된다. VNC 프로토콜 본래 목적이 네트워크를 이용한 가상 컴퓨팅에 맞춰 설계됐기 때문에 그렇다.


화질이 매우 지저분하다


RDP (Remote Desktop Protocol)


RDP는 VNC의 한계점들을 개선하기 위해 새로 만든 프로토콜은 아니고, 마이크로소프트에서 자체적으로 만든 그래픽 공유 프로토콜이며 VNC와 동일하게 인터넷 연결을 통해 원격 데스크탑 PC를 사용할 수 있다. 마이크로스프트에서 개발한 만큼 초창기에는 Windows에서만 사용할 수 있는 소프트웨어 였으나 지금은 Mac, Linux, Android 등 다양한 운영체제에서도 서버 및 클라이언트로 이용이 가능하다. 


RDP와 VNC의 차이점은 사용자 세션 관리다. VNC는 여러 개로 접속한 Client가 모두 동일한 화면을 공유하게 되지만 RDP는 사전 설정에 따라서 Client 세션을 여러개 생성 할 수 있고 접속한 Client는 각자의 화면을 가지고 동작하게 된다. VNC보다 보안적으로 안전하고 사용성도 좋다. RDP 이름 대로 원격 데스크톱으로 사용하는데 초점을 맞춘 소프트웨어 인 것 같다. 만약 집에 있는 컴퓨터를 원격으로 사용하고 싶다면 RDP를 사용하는 것을 추천한다. VNC를 사용한다면 내가 하는 작업을 다른사람이 모니터로 볼 수도 있다.


freerdp 공식 홈페이지 소개 자료. 우분투에서 원격 윈도우 PC에 접속했다


프로토콜 방식이 다른 만큼 둘의 성능차이도 나는데 다른건 모르겠고 그래픽 쪽에서는 VNC보다 RDP가 조금더 우수한 것 같다. RDP에서 VNC의 RFB에 해당하는 전송 포맷이 알고리즘 적으로 조금 더 우수해서 그런것 같은데 정확한 원리는 잘 모르겠다. 나중에 자료조사해서 나오면 좀더 읽어봐야지. 아무튼 VNC처럼 못 쓸 정도는 아니어서 나처럼 우분투랑 윈도우를 동시에 사용하는 개발자들이 많이 사용하는 툴이다.




728x90

'기술' 카테고리의 다른 글

JAVA 파일 생성/읽기/쓰기  (0) 2018.11.25
스택, 힙, 코드, 데이터영역  (6) 2018.11.10
VNC와 RDP  (2) 2018.09.12
jupyter notebook 소개  (0) 2018.08.04
URI (Uniform Resource Identifier)  (0) 2018.07.02
libgdx - Viewport  (0) 2018.06.22
  1. 양정호 2019.10.14 11:03  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 글 잘 봤습니다.
    다름이 아니라 궁금한게 있어서 글 남깁니다.

    VNC서버에서 보내는 그래픽데이터를 간단한 옵션같은 것을 통해서 제가 지정한 이미지 파일로 보내는게 가능한지 궁금합니다.
    VNC가 Xserver에서 출력?하는 이미지를 땡겨서 클라이언트 쪽으로 보내는 걸로 알고 있는데
    Xserver소스를 고치는건 일이 너무 커지는 것 같아서 혹시나 더 간단한 방법이 있는지 알고 싶어서 글 남깁니다.

    혹시나 알고 계시다면 꼭 도움주시면 감사하겠습니다.

  2. 이응이응 2021.01.11 15:05  댓글주소  수정/삭제  댓글쓰기

    배우고 갑니다 ~_~

Q-Network

기술/인공지능 2018. 9. 12. 22:10 Posted by 아는 개발자


Q-Network


앞선 포스트에서 다룬 Q-Learning은 초기 학습 데이터 없이 주변을 탐사해가며 보상값을 최대화 하는 방향으로 환경 데이터의 파라미터의 값을 지속적으로 업데이트 하는 알고리즘이다. 그런데 만약 4x4처럼 작고 단순한 환경이 아니라 5000x5000이고 각 환경별로 선택 할 수 있는 경우의 수가 수십가지가 된다면 기존 알고리즘으로는 시간도 오래 걸릴 뿐만 아니라 모든 환경 데이터 값을 관리하는 것도 불가능하다.


이 문제는 Linear/Logical Regression에서도 겪어보았다. 학습에 사용되는 이미지의 크기가 커질수록 Feature의 값이 많아지게돼 기존의 방식 대로는 학습이 불가능했었다. 당시엔 이 문제를 해결하기 위해 Feature단위로 값을 분석하지 않고 인간의 뇌 구조를 본뜬 뉴럴 네트워크에 데이터 값을 몽땅 넣은 다음에 추이를 살펴보는 방법을 택했었는데 다행이도 Q-Learning도 이와 동일한 접근으로 해결 할 수 있다. Network를 사용한 Q-Learning은 Q-Network라고 부른다.


그런데 Linear/Logical Regression과 달리 지금까지 Q-Learning에서는 Cost Function이 없었다. 왜 없었을까? 그런데 조금만 생각해보면 있는 것이 오히려 이상하다. Q-Learning에서는 학습에 사용하는 실제 데이터가 없기 때문에 학습 결과물이 예측한 값과 라벨 값을 비교할 일이 없었다. 그래서 다소 비효율적이지만 모든 값을 돌아다니며 당시에 주어진 보상 값을 업데이트하는 방식을 사용해야 했다. 하지만 뉴럴 네트워크에 적용하려면 죽이되든 밥이되든 Cost Function이 필요하다. 그래서 Q-network에서는 학습데이터 없이 기존과 동일하게 보상 값을 할 수 있는 Cost Function을 새로 만들었다. 


별도로 논문에 첨부된 수식 보다는 코드로 보는것이 더 직관적일 것 같다. tensorflow를 해본 사람은 40, 41번째 줄을 제외하면 술술 넘어갈 것이다.

# Deep Q-Network lecture slide from http://hunkim.github.io/ml/RL/rl07-l1.pdf
# Original source code repository is https://github.com/awjuliani/DeepRL-Agents

import numpy as np
import tensorflow as tf
import random
from collections import deque
import gym 

class DQN:
    def __init__(self, session, input_size, output_size, name="main"):
        self.session = session
        self.input_size = input_size
        self.output_size = output_size
        self.net_name = name
     
        self._build_network()
     
    def _build_network(self, h_size = 10, l_rate = 1e-1):
        with tf.variable_scope(self.net_name):
            # Input data type
            self._X = tf.placeholder(
                tf.float32, [None, self.input_size], name="input_x")
     
            W1 = tf.get_variable("W1", shape=[self.input_size, h_size],
                             initializer=tf.contrib.layers.xavier_initializer())
            layer = tf.nn.tanh(tf.matmul(self._X, W1))
     
            W2 = tf.get_variable("W2", shape=[h_size, self.output_size],
                                initializer=tf.contrib.layers.xavier_initializer())
     
            # Predicted reward value
            self._Qpred = tf.matmul(layer, W2) 
     
        # Label data type
        self._Y = tf.placeholder(
            shape=[None, self.output_size], dtype=tf.float32)

        # Cost function
        self._loss = tf.reduce_mean(tf.square(self._Y - self._Qpred))
        self.train = tf.train.AdadeltaOptimizer(learning_rate=l_rate).minimize(self._loss)

    def predict(self, state):
        x = np.reshape(state, [1, self.input_size])
        return self.session.run(self._Qpred, feed_dict = {self._X: x}) 

    def update(self, x_stack, y_stack):
        return self.session.run([self._loss, self.train], 
                feed_dict={self._X: x_stack, self._Y: y_stack})

위 코드의 40번째 줄에 해당하는 부분이 Q-Network에서 사용하는 Cost Function이다. 여기서 self._Qpred는 현재 위치에서 예측했을 때 각 행동별 reward 값이다. 그리고 그것과 비교하는 self._Y는 아래 update 함수에서 y_stack으로 대입해주는 값인데 여기서 대입 해주는 값은 학습하면서 얻게된 실제 reward 값이다. 이 말은 즉슨 지금 내가 학습한 결과물과 동시에 시도하면서 얻어낸 reward 값을 비교해보면서 맞춰 나가겠다는 뜻이다. 발상의 전환이다. 방금 Learning중에 얻어낸 유의미한 데이터 값을 바로 학습에 적용했다. 특별한 사전 학습 데이터가 없이도 이런 방식으로 Cost Function을 만들어 낼 수 있다니 신기하다.


728x90

'기술 > 인공지능' 카테고리의 다른 글

Q-Network  (0) 2018.09.12
Q-Learning in Non-Deterministic World  (0) 2018.08.19
Q-Learning  (0) 2018.08.19
파이썬으로 AI 개발할 때 유용한 패키지 모음  (0) 2018.08.07
뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10

CPU pinning과 taskset

기술/컴퓨터사이언스 2018. 8. 27. 22:49 Posted by 아는 개발자


Big/Little로 이루어진 멀티코어 하드웨어 환경에서는 소프트웨어의 성능을 높이기 위해 하드웨어적인 트릭을 사용하는데 이중 가장 대표적인 것이 프로세스를 처리할 수 있는 CPU 종류를 설정하는 CPU pinning 방법이다. CPU affinity라고도 부른다.


기본적으로 스케줄러는 큐에 대기중인 프로세스를 하드웨어상에서 idle 상태에 있는 CPU에 최우선적으로 작업을 할당한다. 그런데 idle 상태에 있는 CPU가 Little라면 처리하는 속도가 Big Core에 비해서 확연히 차이가 날 수 있다. 칩에 따라서 다르지만 일반적으로 Big Core와 Little Core의 성능은 1.5배정도 차이가 난다. 만약 오래 걸리는 작업이라면 Little Core에 할당하는 것보다 Big Core가 idle이 되길 기다렸다가 여기서 처리하도록 하는 것이 성능적으로 우수 할 수 있다.


물론 시스템 내에 존재하는 모든 프로세스가 Big Core에서만 동작하려고 한다면 성능의 효과를 보지 못한다. CPU pinning은 시스템 전반의 효율화를 위해 만들어진 스케줄러의 빈틈을 노리는 것이기 때문에 다른 프로세스들은 모든 코어에서 정상적으로 돌고 있는 환경에서 효과를 극대화 할 수 있다. 그래서 일반적인 소프트웨어에서는 사용하지 않고 여러 개의 Virtual Machine이 동작하는 환경에서 VM의 성능을 조정할 때 주로 이용된다.



taskset은 Linux 환경에서 CPU pinning을 지원하는 커맨드다. 현재 프로세스에 pinning된 CPU 정보를 볼 수 있고 역으로 설정 할 수도 있다. 사용방법은 아래의 코드로만 봐도 될 정도로 간단하다.

// taskset -pc {pid} : pinning 정보 보기
// taskset -pc {Masking ex) 0-3 or 1,2,3,4} {pid} : pinning 설정하기
kwony@kwony:/proc/4007/task/4007$ taskset -pc 4007
pid 4007's current affinity list: 0-3
kwony@kwony:/proc/4007/task/4007$ taskset -pc 1,2,3 4007
pid 4007's current affinity list: 0-3
pid 4007's new affinity list: 1-3


728x90

'기술 > 컴퓨터사이언스' 카테고리의 다른 글

RCU (Read-Copy Update)  (0) 2018.10.30
Cgroup (Control Group)  (0) 2018.09.15
CPU pinning과 taskset  (0) 2018.08.27
스핀락  (0) 2018.07.23
eventfd  (0) 2018.07.18
workqueue 사용법  (0) 2018.07.16

Q-Learning in Non-Deterministic World

기술/인공지능 2018. 8. 19. 13:11 Posted by 아는 개발자


그림 1.


Non Deterministic은 한가지 행동이 여러가지 결과를 만들 수 있는 환경을 의미한다. 그림1에서 빨간선은 Agent가 이동하기로 의도한 방향이고 파란선은 에이전트가 실제로 이동한 방향이다. Deterministic한 상황에서는 에이전트가 의도한 대로 항상 오른쪽으로만 이동한다. 그런데 그림 1과 같은 상황에서는 에이전트가 그림처럼 오른쪽으로도 이동할 수도 있고 위로도 이동할 수도 있다. 이처럼 한가지의 행동(Action)이 여러가지 결과를 내는 환경을 Non-Deterministic이라 부른다.


앞선 포스트에서 설명한 Q-Learning은 예전에 알고리즘 시간에 배웠던 Dynamic Programming(이하 DP)과 여러모로 유사한데 DP는 에이전트가 취한 행동이 항상 고정된 결과를 가져올 때는(Deterministic) 최적의 해를 빠르게 낼 수 있는 알고리즘이지만 여러 가지 결과를 낼 수 있는 상황(Non-Deterministic)에서는 적용할 수 없는 문제점이 있다. 마찬가지로 Q-Learning도 환경이 고정될 때는 100%에 가까운 확률로 예측을 하지만 여러가지 결과를 내는 환경에서는 학습률이 1~2% 이하로 급격하게 저하된다. 


다행이도 DP와 달리 Q-Learning 수식을 조금만 수정하면 Non-Deterministic 환경에서도 어느 정도 괜찮은 성능을 볼 수 있다. 알고리즘 설명에 앞서 우리의 현실 세계를 바라보자. 회사에 이제 막 입사한 신입 사원은 자신의 직속 상사뿐만 아니라 고과권자, 임원 그리고 다른 회사에서 일하는 학교 선배 등등 무수히 많은 멘토들로부터 조언을 듣게 된다. 이중 몇몇은 지금 당장 실행에 옮길 수 있는 조언도 있고 지금은 아니어도 미래에 해야할 조언도 있으며 감동적인 얘기지만 요즘 시대의 정서와는 맞지 않는 (꼰대 소리) 이야기도 있다. 그래서 현명한 신입 사원이라면 멘토의 조언을 곧이곧이 받아들이지 않고 필요한 것들만 적절히 취사 선택 한다.


수정된 Q-Learning에서도 동일한 방식이 적용된다. 에이전트는 행동의 보상을 그대로 업데이트 하지 말고 여러가지 결과물의 보상값을 볼 수 있도록 기존에 학습한 정보를 고집할 필요가 있다. 수학자들은 고집하는 정도를 수학 수식의 형태로 간단하지만 예술적으로 표현 해뒀다. 이미 인공지능을 공부해본 사람들은 아마 예측 했을 수도 있겠다. Linear Regression을 공부 할 때 귀에 박히도록 배운 학습률(learning rate)을 적용하면 된다. 학습률이 높을 수록 방금 취한 action의 보상 값을 많이 반영하고 낮을 수록 보상값을 무시한다. 


1. 모든 환경 데이터 값, Q(s, a)을 초기화 한다.

2. 현재 상태를 확인한다.

3. 그리고 다음의 작업을 반복한다.

- 동작을 선택할 때 랜덤 값을 포함한다.

= argmax(Q(s, a) + ramdom_value)

- 보상 값을 받는다.

- 새로운 상태 값(s')을 확인한다.

- 환경 데이터 값을 업데이트 한다

  Q(s, a) = (1 - learning_rate) * Q(s, a) + learning_rate * (gamma * reward + Max Q(s', a'),)

# 0 < learning_rate < 1


* 이 알고리즘을 사용하면 예전처럼 100%까지는 아니어도 50-60% 정도의 정확도는 보인다. 여전히 아직은 아쉬운 결과 값이다.


* 어찌보면 단순한 수식의 변형이 학습률을 이정도로 끌어올리니 신기하다. 인간의 두뇌도 생각보다 단순하게 이뤄져 있을 것 같다는 생각이 든다.

728x90

'기술 > 인공지능' 카테고리의 다른 글

Q-Network  (0) 2018.09.12
Q-Learning in Non-Deterministic World  (0) 2018.08.19
Q-Learning  (0) 2018.08.19
파이썬으로 AI 개발할 때 유용한 패키지 모음  (0) 2018.08.07
뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10

Q-Learning

기술/인공지능 2018. 8. 19. 09:50 Posted by 아는 개발자

강화학습은 주변의 상태를 지속적으로 탐사해가며 미래에 주어지는 '보상' 값을 활용해 최적의 알고리즘을 찾는 학습법이다. 초기 학습데이터가 필요한 딥러닝과는 달리 초기의 학습데이터가 필요 없으며(물론 학습 데이터를 활용해서 성능을 높이기도 한다) 대신 어떤 조건에서 보상을 줄 것인지에 대한 정책과 탐사할 환경이 필요한 알고리즘이다. 국내에선 이세돌과 커제를 무참히 무찌른 알파고의 학습 알고리즘으로 알려져있다.


Q-Learning은 대표적인 강화 학습 알고리즘으로 '특정 상태에서 어떤 결정을 내리는 것이 미래 보상을 가장 높여줄 것'인지에 대한 정책 데이터를 지속적으로 업데이트 하는 알고리즘이다. 강화학습을 배울 때 가장 먼저 접하는 알고리즘이며 수학 수식들이 난무해 이해하고 싶지 않고 할 수도 없을 것 같은 위키피디아 페이지와 달리 예상외로 쉽고 차근차근 학습해나가면 누구나 배울 수 있는 알고리즘이다. 유튜브에 쉽게 풀어 쓴 강의도 많으니 심도 있게 공부하고 싶은 분들은 여기서 공부하면 좋을 것 같다.


이번 포스트에서는 Q-Learning을 아주 쉽게 풀어 쓰지는 않고 대략적인 동작 방법과 의사 코드를 정리해 수강한 강의 내용들을 정리하는데 초점을 맞추고자 한다.


그림 1.


Q-Learning의 현재 상태를 표현하면 위의 그림 처럼 테이블의 형태로 표현 할 수 있다. 현재 에이전트(학습하는 주체를 에이전트라고 부른다)가 있는 곳은 S로 표시된 곳이고 가야할 곳은 D이며 이곳에 도달 했을 때 보상을 받게 된다. H는 진입하면 죽는 곳이다.


그림 2.


처음 환경에 진입했을 때는 아무런 정보가 없기 때문에 이곳 저곳 다 다녀본다. 에이전트가 선택할 수 있는 방향은 동서남북중 하나인데 어떠한 정보가 없기 때문에 에이전트는 어느 곳을 선택해도 돌아오는 보상 값은 동일하다고 보고 랜덤하게 선택한 방향으로 이동하게 된다. 이렇게 다니면서 다니면서 홀에도 빠져 보고 이전에 지나친 경로들을 다시 다녀보기도 한다. 한마디로 삽질의 연속이다.


그림 3.


이렇게 우왕좌왕하면서 우연히 D 값에 도달 하기도 하는데 이때 에이전트는 처음으로 보상(reward)값을 받는다. 그리고 앞으로 움직이는 행동들은 앞서 초기화한 보상값을 활용해서 업데이트가 이뤄진다. 즉 이 상태에서는 특정 방향이 목적지에 도달할 확률이 높다고 가중치를 주는 작업이다. 지금까지를 의사코드로 정리하면 다음과 같다.

1. 모든 환경 데이터 값, Q(s, a)을 초기화 한다.

2. 현재 상태를 확인한다.

3. 그리고 다음의 작업을 반복한다.

- 동작을 선택하고 실행한다.

- 행동에 따른 보상 값을 받는다. reward

- 새로운 상태 값(s')을 확인한다.

- 아래의 수식으로 환경 데이터 값을 업데이트 한다

  Q(s, a) = reward + Max Q(s', a')


Q(s, a)의 의미는 상태 현재 상태가 's'이고 'a' 행동을 취했을 때 얻게 될 보상 값이다. 위 그림에서는 총 25개의 상태가 있고 각 상태별로 가능한 행동이 4개가 있으니 총 100개의 환경 데이터 값이 필요하다.


그림 4. 


위의 알고리즘은 최적의 값은 찾기는 하지만 보상값을 업데이트한 이후에는 똑같은 경로로 이동하게 되는 단점있다. 현재 상태의 보상 값을 바로 다음 상태의 보상 값으로 입력시키기 때문에 파란선으로 지나온 상태에서 최적의 보상을 주는 값이 이미 정해져 버리고 또 각 상태별 행동은 가장 높은 보상 값을 주는 것으로 선택하기 때문에 매번 동일한 경로로 이동하게 된다. 그래서 그림 4의 빨간선을 이용하면 더 빠르게 갈 수 있지만 이 경로는 보지 못하고 이전에 학습한 경로대로만 다니게 되는 단점이 있다.


이런 오류를 막기위해서 알고리즘에 두가지 노이즈를 추가한다. 미래의 보상은 시간이 지날 수록 낮춰지는 것과 행동을 선택할 때 보상값과 랜덤값을 더하는 방식이다. 두 방법 모두 에이전트에게 돌출 행동을 유도하는 일종의 트릭이다.


1. 모든 환경 데이터 값, Q(s, a)을 초기화 한다.

2. 현재 상태를 확인한다.

3. 그리고 다음의 작업을 반복한다.

- 동작을 선택할 때 랜덤 값을 포함한다.

a= argmax(Q(s, a) + ramdom_value)

- 보상 값을 받는다.

- 새로운 상태 값(s')을 확인한다.

- 환경 데이터 값을 업데이트 한다

  Q(s, a) = gamma * reward + Max Q(s', a'), # 0 < gamma < 1


노이즈를 준 부분을 굵은 글씨로 표시해뒀다. 행동을 선택 할 때 최적의 보상값 + random 값으로 선택하도록 했는데 이는 동서남북을 선택 할 때 각각의 보상값과 ramdom값을 더한 값중에서 가장 높은 값을 선택하도록 하는 것이다. 초기 알고리즘은 그림 4에서 빨간선과 파란선이 갈라지는 지점(2행 3열)에서 가장 높은 보상값을 주는 파란선을 무조건 선택하게 되는데 수정한 알고리즘은 빨간선을 선택 했을 때의 랜덤값이 더 높으면 빨간선을 선택하게 된다.


그리고 미래에 주어지는 보상은 지속적으로 감소하게 만들었다. 처음으로 받게되는 보상은 D에 도달하는 지점이었으니 빨간선과 파란선이 갈라지는 지점에서는 보상값이 꽤 많이 감가상각돼 빨간선을 택하는 것과 파란색을 택할 때의 보상값 차이가 얼마 나지 않을 것이다. 이 두가지 방법으로 에이전트는 기존 학습 데이터 값을 크게 벗어나지 않는 선에서 돌출된 행동을 유도 할 수 있다.


참고문헌


- 홍콩과기대 교수님 유튜브 강의: https://www.youtube.com/channel/UCML9R2ol-l0Ab9OXoNnr7Lw

728x90

'기술 > 인공지능' 카테고리의 다른 글

Q-Network  (0) 2018.09.12
Q-Learning in Non-Deterministic World  (0) 2018.08.19
Q-Learning  (0) 2018.08.19
파이썬으로 AI 개발할 때 유용한 패키지 모음  (0) 2018.08.07
뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10

kvm ioeventfd

기술/가상화 2018. 8. 11. 23:10 Posted by 아는 개발자

* 개인 공부용으로 정리한 것이라 부정확한 내용이 있을 수 있으니 참고용으로만 사용하길 바랍니다.


IOEVENTFD


eventfd를 응용해서 guest에 interrupt를 보낼 수 있는 기능을 만든 irqfd것과 비슷하게 ioeventfd도 eventfd를 이용해서 guest에게 mmio 기능을 전달 할 수 있는 매커니즘을 만들었다. 초기화 작업도 irqfd와 거의 비슷하다.


1. QEMU에서 kvm으로 mmio 값 전달.


QEMU에서 장치가 사용할 주소 값과 flag, eventfd 값을 세팅하고 kvm에 ioctl 을 날린다. 이때 전달인자 fd는 MemoryRegionIoeventfd 라는 구조체의 EventNotifier 값을 뽑아낸 것이다. 구조체 이름이 매우 와닿지 않는다.

// qemu/accel/kvm-all.c
static int kvm_set_ioeventfd_pio(int fd, uint16_t addr, uint16_t val, 
                      bool assign, uint32_t size, bool datamatch)
{
    struct kvm_ioeventfd kick = {
        .datamatch = datamatch 
                       ? adjust_ioeventfd_endianness(val, size) : 0, 
        .addr = addr,
        .flags = KVM_IOEVENTFD_FLAG_PIO,
        .len = size,
        .fd = fd,
    };   
    r = kvm_vm_ioctl(kvm_state, KVM_IOEVENTFD, &kick);

2. 받은 값을 이용해 _ioeventfd 자료구조 생성 및 초기화


_ioevent라는 자료구조를 선언하고 qemu 에서 전달한 값을 세팅한다. args는 QEMU에서 전달받은 인자 값이고 eventfd는 MemoryRegionIoeventfd 의 EventNotifer의 파일 디스크립터 값이다. VirtQueue와 비슷하게 선언된 것 같다.

// virt/kvm/eventfd.c
static int kvm_assign_ioeventfd_idx(struct kvm *kvm,
                enum kvm_bus bus_idx,
                struct kvm_ioeventfd *args)
{
    // HK: Define ioeventfd data structure
    INIT_LIST_HEAD(&p->list);
    p->addr    = args->addr;
    p->bus_idx = bus_idx; // HK: bus_idx is defined by ioeventfd flag
    p->length  = args->len;
    p->eventfd = eventfd;
}

3. IO Device 초기화


초기화한 _ioeventfd에 ioeventfd 작업인 write와 destructor 함수를 세팅한다.. kvm ioeventfd 기반에서 사용할 수 있는 함수들을 설정하는 작업으로 보면 될 것 같다. ioeventfd_write 함수는 쓰려는 값이 range 안에 있는지 확인하고 있으면 eventfd_signal을 보낸다. 값을 업데이트해서 QEMU에 알림을주려는 용도다. 

// virt/kvm/eventfd.c
static int kvm_assign_ioeventfd_idx(struct kvm *kvm,
                enum kvm_bus bus_idx,
                struct kvm_ioeventfd *args)
{
    // HK: Set ioeventfd operation for this device
    kvm_iodevice_init(&p->dev, &ioeventfd_ops);

static const struct kvm_io_device_ops ioeventfd_ops = {
    .write      = ioeventfd_write,
    .destructor = ioeventfd_destructor,
};

/* MMIO/PIO writes trigger an event if the addr/val match */
static int
ioeventfd_write(struct kvm_vcpu *vcpu, struct kvm_io_device *this,
      gpa_t addr, int len, const void *val)
{
    struct _ioeventfd *p = to_ioeventfd(this);

    if (!ioeventfd_in_range(p, addr, len, val))
        return -EOPNOTSUPP;
            
    eventfd_signal(p->eventfd, 1);
    return 0;
}

4. KVM IO bus 등록


mmio로 사용할 주소와 bus index, mmio 동작 작업을 kvm io bus에 등록한다. 버스에서는 등록된 mmio 장치의 주소 값을 보고 event를 날려주는것 같다. bus쪽은 공부가 좀더 필요한 부분이다.

    ret = kvm_io_bus_register_dev(kvm, bus_idx, p->addr, p->length,
                      &p->dev);
    if (ret < 0) 
        goto unlock_fail;
    
    kvm->buses[bus_idx]->ioeventfd_count++;


728x90

'기술 > 가상화' 카테고리의 다른 글

Virtio Block 성능 벤치마크  (0) 2018.12.23
QEMU를 이용해 커널 이미지 바꿔서 부팅해보기  (0) 2018.12.20
kvm ioeventfd  (0) 2018.08.11
kvm irqfd  (0) 2018.08.11
vhost  (0) 2018.07.08
virtio  (0) 2018.07.08