<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>leafbird/devnote</title>
  
  
  <link href="http://leafbird.github.io/devnote/atom.xml" rel="self"/>
  
  <link href="http://leafbird.github.io/devnote/"/>
  <updated>2021-01-02T16:50:33.936Z</updated>
  <id>http://leafbird.github.io/devnote/</id>
  
  <author>
    <name>leafbird</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>C# 고성능 서버 - Thread Local Storage</title>
    <link href="http://leafbird.github.io/devnote/2021/01/01/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-Thread-Local-Storage/"/>
    <id>http://leafbird.github.io/devnote/2021/01/01/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-Thread-Local-Storage/</id>
    <published>2021-01-01T07:00:49.000Z</published>
    <updated>2021-01-02T16:50:33.936Z</updated>
    
    <content type="html"><![CDATA[<p>프로그래밍에서 각 스레드별로 고유한 상태를 설정할 수 있는 공간을 <a href="https://en.wikipedia.org/wiki/Thread-local_storage">Thread Local Storage</a> (이하 TLS. transport layer security 아님) 라고 한다. VC++에서는 <code>__declspec(thread)</code> 키워드를 이용해서 tls 변수를 선언할 수 있다. </p><p>C#에도 <code>ThreadLocal&lt;T&gt;</code> 라는 클래스를 이용해 tls를 사용할 수 있지만, 막상 실제로 사용해보면 C++에서 했던 것처럼 쉽게 해결되지 않을것이다. C# 5.0부터 들어온 async / await 문법을 이용해 비동기 프로그래밍을 구현했다면, await 대기 시점 이전과 이후에 스레드가 달라지기 때문이다. </p><p>이를 해결하는 방법과 주의해야 할 사항을 정리해본다. </p><a id="more"></a><h2 id="async-await-을-절대-가볍게-접근하면-안된다"><a href="#async-await-을-절대-가볍게-접근하면-안된다" class="headerlink" title="async / await 을 절대 가볍게 접근하면 안된다"></a>async / await 을 절대 가볍게 접근하면 안된다</h2><p>주제와 약간 벗어날 수 있지만 서두에 미리 한 번 짚고 넘어갈 부분이 있다. <strong>절대로 async / await를 이용한 비동기 프로그래밍을 만만하게 보아서는 안된다</strong>는 것이다.</p><p>나도 제일 처음 비동기 메서드를 접했을 땐 아주 쉽고 간단한 기능이라고 착각했었다. 개인적으로는 비동기 메서드를 적용하고 난 후의 코드가 동기 프로그래밍과 너무 비슷해져 버리는 점이 착각을 유발하는 큰 원인이라고 생각한다 (MS: 얘는 뭐 좋게 해줘도 불만이 많네..) </p><p>이전에 DB 쿼리나 네트워크 통신같은 IO 작업에서 비동기로 받는 결과값을 처리하기 위해서는 하나의 동일한 주제(single concern)를 위한 로직임에도 불구하고 비동기 요청 이전과 이후의 코드가 분절되어야 했다. 이를테면 비동기 요청 전의 코드와 응답 후의 코드를 서로 다른 메서드로 나누어서 짜야 했다는 뜻이다. 코드의 가독성에 대해 고민을 좀 해봤던 개발자라면 람다를 써서 어떻게든 읽기 좋고 관리하기 좋도록 애써 보았을 수도 있으나, 가독성에서 정도의 차이가 있을 뿐 명백하게 존재하는 코드상의 분절을 피할 수 없었다. </p><p>비동기 메서드의 등장으로 이런 상황은 옛날 이야기가 되었다. 안간힘을 써보아도 완전하게 붙이기 힘들었던 분절된 코드들은 이제 하나의 async 함수 안에서 seamless하게 구현할 수 있게 되었다. 작성한 코드를 읽을 때에도 (신경써서 읽지 않는다면) 어디가 동기 처리이고, 어디가 비동기 처리인지도 잘 모르고 넘어갈만큼 술술 읽어내려가게 되었다. 좋게 해석하자면 어플리케이션 개발자가 좀 더 로직에만 집중 할 수 있는 환경이 되었다.</p><p>이것은 호수에 떠있는 백조와 같다. 일단 겉으로 보기에는 아주 우아하게 비동기 코드를 표현했으나, 조금만 안을 들여다보면 비동기 요청을 기준으로 발생하는 여전한 로직의 분절, 그에 따른 <strong>실행 시점 시간차 및 실행 환경상의 차이</strong> 등은 당연하게 존재하고 있기 때문이다. 이로 인한 이슈들은 동시성(concurrency)이 있는 멀티스레드 환경에서 더 잘 드러난다. MS는 실제로 프로그래머들이 하부의 복잡한 메커니즘을 잘 모르더라도 쉽고 편하게 비동기 로직을 다룰 수 있는 유토피아를 꿈꾸었을지 모르겠다. 하지만 간단한 툴 한두개 짜는거면 몰라도… C#이란 언어로 고성능 서버를 만들겠다고 한다면, 이에 대한 충분한 이해가 없이는 런타임에서 예상못한 오작동을 피할 수 없을 것이다.</p><p>이후 글에서 언급할 내용도 비동기 함수의 실행 시점차와 관련되어 있으므로, 비동기 메서드에 대한 어느 정도의 이해가 필요하다.</p><h2 id="ThreadLocal"><a href="#ThreadLocal" class="headerlink" title="ThreadLocal"></a>ThreadLocal</h2><p>우선 잠깐 언급했던 <code>ThreadLocal&lt;T&gt;</code> 클래스를 간단히 알아보자. 이를 이용해 일반적인 tls 변수를 선언하고 사용할 수 있다. 이보다 전부터 있었던 <code>[ThreadStatic]</code> 어트리뷰트로도 똑같이 tls를 선언할 수 있지만, 변수의 초기화 처리에서 <code>ThreadLocal&lt;T&gt;</code> 가 좀 더 매끄러운 처리를 지원한다. 일반적인 tls가 필요할 때는 좀 더 최신의 방식인 <code>ThreadLocal&lt;T&gt;</code> 를 사용하면 된다.</p><p>모든 tls 변수에 동일한 값을 저장해 두려는 경우가 있다. 예를들어 스레드가 3개 있으면, 메모리 공간상에 각 스레드를 위한 변수 3개가 있고, 이들 모두가 같은 값을 같는 경우를 말한다. <strong>서로 다른 스레드끼리 공유해야 할 자원이 있을 때, 해당 자원에 lock이 없이 접근하고 싶다면</strong> tls를 이용해 각 스레드마다 자원을 따로 만들어 각자 자기 리소스를 쓰게 하면 된다.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">Cs.Math</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">RandomGenerator</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">int</span> <span class="title">Next</span>(<span class="params"><span class="built_in">int</span> maxValue</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">      <span class="keyword">return</span> PerThreadRandom.Instance.Next(maxValue);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ... 중략    </span></span><br><span class="line">    <span class="comment">// 사용 계층에 노출할 인터페이스를 이곳에 정의. 사용자는 tls에 대해 알지 못한다.</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// System.Random 객체는 멀티스레드 사용에 안전하지 않으므로 각 스레드마다 개별 생성.</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">PerThreadRandom</span></span><br><span class="line">    &#123;</span><br><span class="line">      <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> <span class="title">ThreadLocal</span>&lt;<span class="title">Random</span>&gt; Random</span> = <span class="keyword">new</span> ThreadLocal&lt;Random&gt;(() =&gt; <span class="keyword">new</span> Random());</span><br><span class="line"></span><br><span class="line">      <span class="keyword">internal</span> <span class="keyword">static</span> Random Instance =&gt; Random.Value;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>이런 경우는 비동기 메서드의 실행중 스레드의 교체가 발생하더라도 아무 문제가 되지 않는다. 어차피 어떤 스레드로 바뀌더라도 tls 변수의 상태는 동일하기 때문이다. 0번 스레드가 불러다 쓰는 <code>Random</code> 객체가 어느순간 2번 스레드의 <code>Random</code> 객체로 바뀐다 해도 동작에 큰 영향이 없다.</p><h2 id="AsyncLocal"><a href="#AsyncLocal" class="headerlink" title="AsyncLocal"></a>AsyncLocal</h2><p>문제는 스레드별로 tls의 상태가 서로 달라야 할 때 발생한다. 0번 스레드에는 tls에 “철수”가, 2번 스레드에는 “영희”가 적혀있어야 하고, 이를 사용해 스레드마다 다른 동작을 해야 하는 경우. 그런데 거기다 async/await를 이용한 비동기 프로그래밍을 함께 사용한 경우. 0번 철수 스레드가 코드 수행 도중 await 구문을 만나 task의 완료를 기다리고 있었지만, 대기가 풀렸을 때는 2번 스레드로 갈아타게 되면서 철수가 영희가 되버리는 경우다.</p><img src="/devnote/2021/01/01/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-Thread-Local-Storage/00.png" class=""><p>스레드별로 서로 다른 상태값을 사용해야 하는 예를 구승모 교수님의 <a href="https://github.com/zeliard/Dispatcher">Dispatcher</a> 구현에서 찾아볼 수 있다. (<a href="https://github.com/zeliard/Dispatcher/blob/master/JobDispatcher/ThreadLocal.h">ThreadLocal.h</a>) Dispatcher는 고성능 멀티스레드 로직 수행을 위한 Actor 패턴 구현체다. 스레드에 lock을 걸지 않으면서도 서로 다른 스레드간 간섭 없이 순차실행을 가능하게 하기 위해, 스레드는 현재 자신의 수행상태 일부를 tls에 기록해 두어야 한다. </p><p>친절한 ms 형들이 이런 경우를 위해 <a href="https://docs.microsoft.com/ko-kr/dotnet/api/system.threading.asynclocal-1?view=net-5.0">AsyncLocal</a> 클래스도 미리 만들어 두었다. 생긴것도 서로 비슷해서  <code>ThreadLocal&lt;T&gt;</code> 를 사용했던 변수에 대신 <code>AsyncLocal&lt;T&gt;</code> 로 바꿔주면 위에서 말한 문제를 해결할 수 있다. 0번 스레드가 먼저 코드를 수행하다가 await 구문을 만나서 대기하고, 대기가 풀려날 때 2번 스레드로 변경이 되었더라도 <code>AsyncLocal&lt;T&gt;</code> 가 2번 스레드의 tls 값을 알아서 “영희” -&gt; “철수”로 바꿔주는 것이다. </p><p>이러면 문제는 해결된 것 같지만, 또다른 문제가 있다. 여기가 이 글의 핵심이다 집중해주기 바란다. <code>AsyncLocal&lt;T&gt;</code> 는 <strong>비동기 메서드 수행 도중 스레드가 바뀌면 새로 바톤을 이어받은 스레드에게 tls의 상태를 자동으로 동기화 시켜 주기는 하지만, 바톤을 넘겨주고 떠나는 원래 스레드의 tls 상태를 초기화 시켜주지는 않는다.</strong> 중요하니까 그림까지 그려서 한 번 더 말한다. 0번 “철수” 스레드가 await 구문 전까지 수행을 하고, 대기가 끝난 후 2번 “영희” 스레드로 변경되어 수행이 재개 될 때 <code>AsyncLocal&lt;T&gt;</code>를 사용하면 2번 스레드의 tls 상태가 “철수”가 되긴 하지만, 여전히 0번 스레드의 tls에도 “철수”가 남아있는 것이다. </p><img src="/devnote/2021/01/01/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-Thread-Local-Storage/01.png" class=""><p>실행을 재개하는 2번 스레드에서는 AsyncLocal 덕분에 큰 문제가 없지만, 0번 스레드는 이후  ThreadPool로 들어온 새로운 요청을 수행하러 나가게 될텐데, 그곳에서는 전혀 관련없는 과거의 tls 변수값을 가진 채로 수행될 가능성이 있으므로 주의해야 한다. </p><p>TPL에 관심을 갖고 공부해둔 개발자라면 혹시 <a href="https://docs.microsoft.com/ko-kr/dotnet/api/system.threading.synchronizationcontext?view=net-5.0">SynchronizationContext</a> 를 떠올릴 지도 모르겠다. 이를 이용해 await 대기가 풀려날 때 어떤 스레드로 재개할 것인지를 직접 컨트롤 할 수 있기 때문이다. 0번 스레드가 await 대기를 시작했다면, 대기가 풀려날 때도 0번 스레드가 다시 수행할 수 있게 스레드 스케줄링을 직접 해줄 수 있다.</p><p>하지만 웬만해서는 SynchromizationContext까지 이용해 스레드의 스케줄링을 제어할 생각은 하지 않을 것을 권하고 싶다. 성능, 사용성, 생산성 어느 방향으로든 기존보다 큰 개선을 이루기 어려울 것이다. 스레드 스케줄링은 프로그램의 가장 코어한 부분이기에 정말 꼭 필요한 경우에 한해 혹독한 테스트를 거쳐 변경해야 할 것이다.</p><h2 id="해결방안-직접-AsyncLocal-뒷정리-해주기"><a href="#해결방안-직접-AsyncLocal-뒷정리-해주기" class="headerlink" title="해결방안 : 직접 AsyncLocal 뒷정리 해주기"></a>해결방안 : 직접 AsyncLocal 뒷정리 해주기</h2><p>비동기 메서드가 실행되는 도중 스레드 교체가 발생했을 때, 바톤을 넘겨 주고 난 이전 스레드는 tls 값이 알아서 모두 초기화 되기를 기대했지만 그렇게 동작하지 않는다. 초기화를 하고 싶다면 내가 직접 해주어야 한다.</p><p>ThreadPool에서 스레드가 새로운 작업 요청을 수행하는 모든 시작점에서 tls 상태 초기화를 해주면 된다. 이 <code>모든 시작점</code>이라는 부분은 어떤 프로그램이냐에 따라 다를텐데, 현재 프로젝트에서 쓰고있는 게임서버의 경우 크게 2종류의 시작점으로 나눌 수 있다. </p><ol><li>소켓 API 에서 발생하는 각종 이벤트들의 핸들링 메서드</li><li>코드상에서 명시적으로 background job이 필요해 ThreadPool에 요청하는 경우. </li></ol><p>2번 명시적인 작업 요청의 경우는 추가적으로 설명해야 할 점도 있고, 쉬운 해결 방법도 있기에 다음의 별도 섹션에서 추가적으로 다룰 것이다. 1번 네트워크 이벤트 핸들링 메서드들에서는 별다른 뾰족한 수가 없어서 직접 tls 변수를 정리해 주는 식으로 해결했다. 네트워크 이벤트란 구체적으로 아래의 5가지 경우를 말한다.</p><ul><li>Socket.ConnectAsync 이후 호출되는 콜백</li><li>Socket.DisconnectAsync 이후 호출되는 콜백</li><li>Socket.AcceptAsync 이후 호출되는 콜백</li><li>Socket.ReceiveAsync 이후 호출되는 콜백</li><li>Socket.SendAsync 이후 호출되는 콜백</li></ul><p>이 곳에서 수동으로 tls를 정리해주면 된다. 코드로 표현해보면 이렇게 된다. </p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">sealed</span> <span class="keyword">class</span> <span class="title">JobDispatcher</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="title">AsyncLocal</span>&lt;<span class="title">JobDispatcher</span>&gt; asynclocalDispatcher</span> = <span class="keyword">new</span> AsyncLocal&lt;JobDispatcher&gt;();</span><br><span class="line">  </span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">ClearAsyncLocal</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>  &#123;</span><br><span class="line">    asyncLocalDispatcher.Value = <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// ... Actor 구현...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">sealed</span> <span class="keyword">class</span> <span class="title">TcpConnection</span> <span class="comment">// 소켓 구현체</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">private</span> Socket socket;</span><br><span class="line">  </span><br><span class="line">  <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">OnRecvCompleted</span>(<span class="params"><span class="built_in">object</span> sender, SocketAsyncEventArgs args</span>)</span></span><br><span class="line"><span class="function"></span>  &#123;</span><br><span class="line">    JobDispatcher.ClearAsyncLocal();</span><br><span class="line">    <span class="comment">// ... 소켓 수신 처리</span></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">OnSendCompleted</span>(<span class="params"><span class="built_in">object</span> sender, SocketAsyncEventArgs args</span>)</span></span><br><span class="line"><span class="function"></span>  &#123;</span><br><span class="line">    JobDispatcher.ClearAsyncLocal();</span><br><span class="line">    <span class="comment">// ... 소켓 송신 처리</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>썩 만족스러운 솔루션은 아니지만 이걸로 해결이 가능하다는 점은 현재 서비스 중인 회사의 프로젝트를 통해서 입증 받았다. 이렇게 해주지 않으면 AsyncLocal 값들이 제대로 정리되지 않는다. task의 완료를 일정시간 기다리다가 반환을 받는데 실패한 스레드가 AsyncLocal의 값을 유지한채로 ThreadPool로 복귀하는데, 이때 tls 값을 정리해줄 수 있는 마땅한 타이밍이나 인터페이스가 없기 때문이다.</p><h2 id="자식-스레드에도-복사되는-AsyncLocal-변수"><a href="#자식-스레드에도-복사되는-AsyncLocal-변수" class="headerlink" title="자식 스레드에도 복사되는 AsyncLocal 변수"></a>자식 스레드에도 복사되는 AsyncLocal 변수</h2><p>직접 스레드를 생성하는게 아니기에 엄밀히 말하면 틀린 말이겠지만 소제목에 쓸 간결한 표현이 없어서 자식 스레드라고 적었다. 보다 정확히는 <code>Task.Run()</code>, <code>ThreadPool.QueueUserWorkItem()</code> 등을 이용해 스레드 풀에 새로운 작업을 요청하는 경우를 말한다. 이건 여지껏 설명한 비동기 함수 재개 시점의 이슈와는 다르지만 똑같은 현상이 발생한다. 새 작업을 수행하는 스레드가 부모(=작업 요청자) 스레드의 AsyncLocal 과 동일한 변수값을 복사해서 가져가기 때문이다.  </p><p>다행이 이 동작은 ExecutionContext.SuppressFlow / RestoreFlow 라는 메서드가 있어 쉽게 조절할 수 있다. 새 작업을 요청하기 전에 <code>SuppressFlow</code> 를 호출하면 tls의 값을 복사하지 않는다.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">Cs.Messaging</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">BackgroundJob</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params">Action action</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">      <span class="keyword">using</span> <span class="keyword">var</span> control = ExecutionContext.SuppressFlow();</span><br><span class="line">      ThreadPool.QueueUserWorkItem(_ =&gt; action());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params">Func&lt;Task&gt; function</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">      <span class="keyword">using</span> <span class="keyword">var</span> control = ExecutionContext.SuppressFlow();</span><br><span class="line">      ThreadPool.QueueUserWorkItem(<span class="keyword">async</span> _ =&gt;</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="keyword">await</span> function(); <span class="comment">// note: 샘플 코드에서 예외처리는 생략.</span></span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>작업 요청 후에는 <code>RestoreFlow</code> 를 불러 복구해주면 되는데, <code>SuppressFlow</code> 메서드가 IDisposable인  <a href="https://docs.microsoft.com/en-us/dotnet/api/system.threading.asyncflowcontrol?view=net-5.0">AsyncFlowControl</a> 객체를 반환하니까 예시처럼 using을 쓰면 좀 더 심플하게 처리할 수 있다.</p><h2 id="마치면서"><a href="#마치면서" class="headerlink" title="마치면서"></a>마치면서</h2><ul><li>C#의 비동기 메서드는 코드상으로는 매끈하게 이어져 있는듯 보이지만 실은 비동기 요청 지점을 전후로 분리 실행되며, 실행 스레드가 서로 다를 수도 있다.</li><li>이로 인해 <code>ThreadLocal&lt;T&gt;</code> 로는 비대칭적(asymmetric)인 tls 데이터를 다루기가 어렵기 때문에 <code>AsyncLocal&lt;T&gt;</code>라는 클래스가 별도로 존재한다.</li><li><code>AsyncLocal&lt;T&gt;</code>는 비동기 메서드를 실행하다 스레드가 바뀌면 tls의 값을 새 스레드로 복사는 해주지만, 기존 스레드의 tls값을 초기화 해주지는 않으므로 직접 해주어야 한다.</li><li><code>Task.Run()</code> 등으로 새로운 백그라운드 작업을 요청할 때는 기본적으로 <code>AsyncLocal&lt;T&gt;</code> 의 값이 복사된다. <code>ExecutionContext.SuppressFlow()</code> 로 제어가 가능하다.</li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;프로그래밍에서 각 스레드별로 고유한 상태를 설정할 수 있는 공간을 &lt;a href=&quot;https://en.wikipedia.org/wiki/Thread-local_storage&quot;&gt;Thread Local Storage&lt;/a&gt; (이하 TLS. transport layer security 아님) 라고 한다. VC++에서는 &lt;code&gt;__declspec(thread)&lt;/code&gt; 키워드를 이용해서 tls 변수를 선언할 수 있다. &lt;/p&gt;
&lt;p&gt;C#에도 &lt;code&gt;ThreadLocal&amp;lt;T&amp;gt;&lt;/code&gt; 라는 클래스를 이용해 tls를 사용할 수 있지만, 막상 실제로 사용해보면 C++에서 했던 것처럼 쉽게 해결되지 않을것이다. C# 5.0부터 들어온 async / await 문법을 이용해 비동기 프로그래밍을 구현했다면, await 대기 시점 이전과 이후에 스레드가 달라지기 때문이다. &lt;/p&gt;
&lt;p&gt;이를 해결하는 방법과 주의해야 할 사항을 정리해본다. &lt;/p&gt;</summary>
    
    
    
    
    <category term="c#" scheme="http://leafbird.github.io/devnote/tags/c/"/>
    
    <category term="고성능" scheme="http://leafbird.github.io/devnote/tags/%EA%B3%A0%EC%84%B1%EB%8A%A5/"/>
    
    <category term="게임서버" scheme="http://leafbird.github.io/devnote/tags/%EA%B2%8C%EC%9E%84%EC%84%9C%EB%B2%84/"/>
    
    <category term="Thread" scheme="http://leafbird.github.io/devnote/tags/Thread/"/>
    
    <category term="AsyncLocal" scheme="http://leafbird.github.io/devnote/tags/AsyncLocal/"/>
    
    <category term="TLS" scheme="http://leafbird.github.io/devnote/tags/TLS/"/>
    
  </entry>
  
  <entry>
    <title>C# 고성능 서버 - System.IO.Pipeline 도입 후기</title>
    <link href="http://leafbird.github.io/devnote/2020/12/27/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-System-IO-Pipeline-%EB%8F%84%EC%9E%85-%ED%9B%84%EA%B8%B0/"/>
    <id>http://leafbird.github.io/devnote/2020/12/27/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-System-IO-Pipeline-%EB%8F%84%EC%9E%85-%ED%9B%84%EA%B8%B0/</id>
    <published>2020-12-27T08:34:58.000Z</published>
    <updated>2021-01-01T07:31:51.016Z</updated>
    
    <content type="html"><![CDATA[<img src="/devnote/2020/12/27/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-System-IO-Pipeline-%EB%8F%84%EC%9E%85-%ED%9B%84%EA%B8%B0/00.jpg" class=""><p>2018년에 네트워크 레이어 성능을 끌어올리기 위해 도입했던 System.IO.Pipeline을 간단히 소개하고, 도입 후기를 적어본다. </p><p>윈도우 OS에서 고성능을 내기 위한 소켓 프로그래밍을 할 때 IOCP 의 사용은 오래도록 변하지 않는 정답의 자리를 유지하고 있다. 여기에서 좀 더 성능에 욕심을 내고자 한다면 Windows Server 2012부터 등장한 <a href="https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh997032(v=ws.11)">Registerd IO</a> 라는 새로운 선택지가 있다. 하지만 API가 C++ 로만 열려 있어서, C# 구현에서는 사용하기가 쉽지 않다. </p><p>하지만 C#에도 고성능 IO를 위한 새로운 API가 추가되었다. <a href="https://docs.microsoft.com/ko-kr/dotnet/standard/io/pipelines">Pipeline</a> 이다.</p><a id="more"></a><h2 id="System-IO-Pipeline-소개"><a href="#System-IO-Pipeline-소개" class="headerlink" title="System.IO.Pipeline 소개."></a>System.IO.Pipeline 소개.</h2><p>pipeline을 처음 들었을 때는 IOCP의 뒤를 잇는 새로운 소켓 API인줄 알았다. C++의 RIO가 iocp를 완전히 대체할 수 있는 것처럼.</p><p>RIO는 가장 핵심 요소인 <code>등록된 버퍼(registered buffer)</code> 외에, IO 요청 및 완료 통지 방식도 함께 제공하기 때문에 iocp를 완전히 드러내고 대신 사용할 수 있다. 반면 Pipeline은 RIO보다는 커버하는 범위가 좁아서, IOCP를 완전히 대체하는 물건이 될 수는 없다. 이벤트 통지는 기존의 방법들을 이용하면서, 메모리 버퍼의 운용만을 담당하는 라이브러리 이기 때문에 IOCP와 반드시 함께 사용해야 한다.</p><p>Pipeline이라는 이름을 굉장히 잘 지었다. 이름처럼 <strong>메모리 버퍼를 끝없이 연결된 긴 파이프라인처럼 쓸 수 있게 해주는 라이브러리</strong> 이기 때문이다. 단위길이 만큼의 버퍼를 계속 이어붙여서 무한하게 이어진 가상의 버퍼를 만드는데, 이걸 너네가 만들면 시간도 오래 걸리고 버그도 넘나 많을테니 우리가 미리 만들었어. 그냥 가져다 쓰렴. 하고 내놓은 것이 Pipeline이다.</p><img src="/devnote/2020/12/27/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-System-IO-Pipeline-%EB%8F%84%EC%9E%85-%ED%9B%84%EA%B8%B0/01.png" class=""><p>(이미지 출처 : <a href="https://devblogs.microsoft.com/dotnet/system-io-pipelines-high-performance-io-in-net/">devblogs.microsoft.com</a>)</p><p>이미지의 초록색 부분은 <code>class Pipe</code> 의 내부 구조를 도식화한다. 일정한 크기의 작은 버퍼들이 링크드 리스트로 연결 되어있다. 내부 구조는 안에 숨겨져있고 외부로는 <a href="https://docs.microsoft.com/ko-kr/dotnet/api/system.buffers.readonlysequence-1?view=net-5.0">ReadOnlySequence<T></a> 타입을 이용해 버퍼간 이음매가 드러나지 않는 seamless한 인터페이스만을 제공한다. 이것이 Pipeline의 핵심이다.</p><p>이 외의 디테일한 부분은 Pipeline을 이해하기 쉽게 잘 설명한 <a href="https://devblogs.microsoft.com/dotnet/system-io-pipelines-high-performance-io-in-net/">MS 블로그의 포스팅</a>이 있어 이것으로 대신한다.</p><h2 id="장점-불필요한-메모리-복사를-없앤다"><a href="#장점-불필요한-메모리-복사를-없앤다" class="headerlink" title="장점 : 불필요한 메모리 복사를 없앤다."></a>장점 : 불필요한 메모리 복사를 없앤다.</h2><p>고성능 소켓 IO 구현에 관심이 있는 C++ 프로그래머라면 google protobuf의 <a href="https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.io.zero_copy_stream">ZeroCopyStream</a> 을 이미 접해봤을지 모른다. 그렇다면 Pipeline의 중요한 장점을 쉽게 이해할 것이다. Pipeline의 버퍼 운용 아이디어는 프로토콜 버퍼의 ZeroCopyStream과 유사하기 때문이다. 소켓으로 데이터를 주고 받는 과정에서 발생하는 불필요한 버퍼간 메모리 복사를 최소한으로 줄여주어 성능향상을 꾀한다는 점에서 두 라이브러리가 추구하는 방향은 동일하다. </p><p>프로그래밍에 미숙한 개발자가 만든 서버일수록 버퍼간 복사 발생이 빈번하게 발생한다. 커널모드 아래에서 일어나는 소켓버퍼와 NIC 버퍼간의 복사까지는 일단 관두더라도, 최소한 유저모드 위에서의 불필요한 버퍼 복사는 없어야 한다. </p><p> 전송할 데이터 타입을 버퍼로 직렬화 하면서 한 번 복사하고, 이걸 소켓에다가 send 요청을 하자니 OVERLAPPED에 연결된 버퍼에다가 넣어줘야 해서 추가로 또 복사하고… send 완료 통지 받고 나면 transferred bytes 뒤에 줄서있을 미전송 데이터들을 다시 앞으로 당겨주느라 또 한번 복사가 발생하기 쉽다. recv 받은 뒤에도 메시지 단위 하나 분량 만큼만 읽어 fetching하고 나면 뒤에 남은 데이터들을 버퍼 맨 앞으로 당겨와야겠으니… 여기서 또 한 번 추가복사 하게 될것이다.</p><p>서버가 감당할 통신량이 많아질수록 불필요한 복사들이 누적되어 쓸데없이 cpu power를 낭비하게 될텐데, Pipeline의 도입은 이런 부분을 쉽게 해결해 준다. msdn 블로그에서는 Pipeline을 사용하면 복잡한 버퍼 운용 구현을 대신 해결해주니까 프로그래머가 비즈니스 로직의 구현에 좀 더 집중할 수 있게 도와준다고 <del>약을 팔고</del> 설명하고 있다.</p><h2 id="장점-네트워크-버퍼의-고정길이-제약을-없애준다"><a href="#장점-네트워크-버퍼의-고정길이-제약을-없애준다" class="headerlink" title="장점 : 네트워크 버퍼의 고정길이 제약을 없애준다."></a>장점 : 네트워크 버퍼의 고정길이 제약을 없애준다.</h2><p>가장 단순하게 소켓 레이어를 구현하면 송/수신용 고정 사이즈 <code>byte[]</code> 버퍼를 각각 하나씩 붙여서 만들게 될 것이다. 대략 구현중인 게임이 어느 정도 사이즈의 패킷을 주고 받는지를 <strong>귀납적</strong>으로 파악해서 (주로 게임 서버는 작은 사이즈 패킷을 많이 받고, 큰 사이즈 패킷을 많이 보낸다. 로그인할때, 캐릭터 선택할 때 보내는 패킷이 통상 제일 크다) 버퍼의 크기를 눈치껏 결정해서 <code>상수로 고정한다</code>. 버퍼를 거거익선으로 크게크게 잡으면 좋겠지만 대량의 동접을 처리해야 할때 메모리 사용량이 높아져서 부담이 된다. 그러니 적당히 오가는 패킷 사이즈를 봐서 터지지만 않을 정도의 고정길이 버퍼를 걸어두는 식으로 만들게 된다.</p><p>이렇게 만들면 불안하다. 컨텐츠를 점점 추가하다가 언젠가 한 두번은 네트워크 버퍼 overflow가 발생해 버퍼 크기를 늘려잡고 다시 빌드해야 하기 일쑤다. 아니면 버퍼를 넘치게 만든 문제 패킷의 구조를 변경하거나 두 개의 패킷으로 쪼개는 등 다이어트를 시켜서 해결할 수도 있겠다. 어느쪽이든 고성능 서버의 네트워크 레이어 구현으로는 적당하지 않은 솔루션이다. 메모리를 더 써서 해결하거나, 개발에 제약(패킷의 최대 크기)을 두어 해결하거나. 모두 석연치 않다.</p><p>Pipeline과 ZeroCopyStream 의 무한버퍼 컨셉은 이러한 고정길이 버퍼의 단점을 해결해준다. 처음엔 작은 크기의 버퍼만 가지고 있다가, 공간이 모자라면 추가로 더 할당받아 링크드 리스트 뒤에 붙이기만 하면 된다. 각각의 peer(= single socket)가 실제 사용하는 메모리 공간은 주고받는 데이터의 크기에 따라서 늘어나거나 줄어드는 유연성이 생긴다. 메모리를 효율적으로 사용하면서도 단일 메시지의 사이즈 제약도 없어진다.</p><h2 id="단점-너무-많은-Task를-생성한다"><a href="#단점-너무-많은-Task를-생성한다" class="headerlink" title="단점 : 너무 많은 Task를 생성한다."></a>단점 : 너무 많은 Task를 생성한다.</h2><p>위의 두가지 장점만으로 Pipeline의 도입을 시도해볼 가치는 충분했다. 그래서 우리는 게임서버의 수신 버퍼를 Pipeline으로 대체하고, MS Azure 에서 F8s 급 인스턴스 수십대를 동원해 10만 동접 스트레스 테스트를 진행해 보았다. </p><p>결과는 기대와 완전히 달랐는데.. <strong>Pipeline 도입 전보다 영 더 못한 성능을 보여줬다</strong>. 이건 뭐… cpu 사용량이 높고 낮아지는 것이 문제가 아니라, 동접이 일정수치 이상 오르면 서버가 아무 일도 처리하지 않고 멈춰버렸다. 반응없는 프로세스에서 덤프를 떠서 디버거로 살펴보면… 대기상태인 스레드가 잔뜩 생겨있고, 일해야 할 스레드가 부족해서 추가 스레드를 계속해서 만들어내고 있는 것처럼 보였다.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// msdn 블로그에 소개된 코드 일부 발췌. Pipe를 하나 만들면 읽기/쓰기 Task를 2개 만든다.</span></span><br><span class="line"><span class="function"><span class="keyword">async</span> Task <span class="title">ProcessLinesAsync</span>(<span class="params">Socket socket</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">var</span> pipe = <span class="keyword">new</span> Pipe();</span><br><span class="line">    Task writing = FillPipeAsync(socket, pipe.Writer);</span><br><span class="line">    Task reading = ReadPipeAsync(pipe.Reader);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> Task.WhenAll(reading, writing);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>원인은 Pipeline과 함께 사용하는 task (System.Threading.Tasks.Task) 들이었다.  <code>class Pipe</code> 인스턴스 하나를 쓸 때마다 파이프라인에 ‘읽기’와 ‘쓰기’를 담당하는 <code>class Task</code> 객체 두 개를 사용하게 된다. 수신버퍼에만 Pipe를 달면 소켓의 2배, 송수신 버퍼에 모두 달면 소켓의 4배수 만큼의 task가 생성 되어야 하기 때문이다. 게임서버 프로세스당 5,000 명의 동접을 받는다고 하면 최대 20,000개의 task가 생성되고, 이 중 상당수는 waiting 상태로 IO 이벤트를 기다리게 된다.</p><p>task가 아무리 가볍다고 해도 네트워크 레이어에만 몇 만개의 task를 만드는 것은 그리 효율적이지 않다. TPL에 대한 이야기를 시작하면 해야 할 말이 아주 많기 때문에 별도의 포스팅으로 분리해야 할 것이다. 과감히 한 줄로 정리해보면, task는 상대적으로 OS의 커널오브젝트인 스레드보다 가볍다는 것이지 수천 수만개를 만들만큼 깃털같은 물건은 아닌 것이다.</p><p>스레드가 코드를 한 단계씩 수행하다가 아직 완료되지 않은 task를 await 하는 구문을 만나면 호출 스택을 한 단계씩 거꾸로 올라가면서 동기 로직의 수행을 재개한다. 하지만 완료되지 않은 task를 만났다고 해서 그 즉시 task의 완료 및 반환값 획득을 포기하고 호출스택을 거슬러 올라가는 것은 아니다. 혹시 금방 task가 완료되지 않을까 하는 기대감으로 조금 대기하다가 완료된 기미가 보이지 않으면 그 제서야 태세를 전환하게 된다. 이 전략은 task가 동시성을 매끄럽게 처리하기 위해서는 바람직한 모습이지만, 아주 많은 개수의 task를 장시간(게임서버에서 다음 패킷을 받을 때까지의 평균 시간) 동안 대기시켜야 하는 네트워크 모델에 사용하기에는 적합하지 않다. 스레드들은 각 pipeline의 write task가 RecvComplete 통지를 받고 깨어나기를 기다리면서 수십만 cpu clock을 낭비하게 된다.</p><h2 id="의문-Kestrel은-Pipeline-때문에-엄청-빨라졌는데"><a href="#의문-Kestrel은-Pipeline-때문에-엄청-빨라졌는데" class="headerlink" title="의문 : Kestrel은 Pipeline 때문에 엄청 빨라졌는데?"></a>의문 : Kestrel은 Pipeline 때문에 엄청 빨라졌는데?</h2><img src="/devnote/2020/12/27/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-System-IO-Pipeline-%EB%8F%84%EC%9E%85-%ED%9B%84%EA%B8%B0/02.png" class=""><p>(이미지 출처 : <a href="https://stackoverflow.com/questions/34440649/iis-vs-kestrel-performance-comparison">stackoverflow.com</a>)</p><p>ASP.NET Core는 Pipeline으로 구현한 kestrel 웹서버에서 실행할 때 기존의 iis 기반보다 훨씬 더 향상된 퍼포먼스를 보여준다. Pipeline의 버퍼 운용 효율성으로 인한 이득을 제대로 누리고 있는 것이다. kestrel의 뛰어난 성능 결과를 보여주는 여러 벤치마크 결과들 덕분에 나도 기대를 가득 안고 서둘러 Pipeline을 도입하고 테스트 해보았으나.. 결과는 좋지 않았다.</p><p>그럼 우리 게임서버에 도입한 테스트 결과는 왜 이리 처참한 것인가? ms 형들이 잘못 만들었을 리는 없으니 내가 가져다 붙이는 과정에 문제가 있었던 것인가? </p><p>차이가 생기는 원인은 <strong>Kestrel은 http 통신을 하는 웹서버이고, 우리의 게임서버는 연결을 유지하고 있는 TCP 서버이기 때문</strong>이다. Kestrel은 통신량의 거의 전부가 socket이 열린 채로 길게 대기할 필요가 없기 때문에, task을 소켓의 2배수나 4배수만큼 오래도록 유지하고 있을 이유 자체가 없다. 그래서 단점으로 지적한 waiting task가 kestrel에서는 발생하지 않는다. 상술했던 단점을 다시 표현해 보자면 <strong>Pipeline의 사용시 기본적으로 task 대기가 발생하는 것을 성능 하락의 원인으로 볼 수 있지만, 이 task들의 수명 혹은 대기시간이 상당히 길다는 점과 함께 만나면 성능을 더욱 악화시키는 원인이 된다</strong>. Kestrel의 단명하는(?) 소켓들과 task들은 Pipeline와 함께 사용되면서 충분히 좋은 성능을 가져다 줄 것이다. 수많은 벤치마킹 결과들이 증명하듯이.</p><h2 id="대안-불필요한-복사가-없는-가변버퍼를-직접-만들자"><a href="#대안-불필요한-복사가-없는-가변버퍼를-직접-만들자" class="headerlink" title="대안 : 불필요한 복사가 없는 가변버퍼를 직접 만들자."></a>대안 : 불필요한 복사가 없는 가변버퍼를 직접 만들자.</h2><p>우리는 게임서버에서 Pipeline을 다시 드러냈다. http와 유사하게 single pair request/response 통신 후 소켓을 닫아도 되는 경우가 아니면 Pipeline으로 성능상의 혜택을 보기는 힘들다고 판단했기 때문이다. 그래도 불필요한 메모리복사는 만들고 싶지 않으니 메모리 버퍼 운용하는 부분만 직접 구현해 사용하기로 했다.</p><img src="/devnote/2020/12/27/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-System-IO-Pipeline-%EB%8F%84%EC%9E%85-%ED%9B%84%EA%B8%B0/03.png" class=""><p>클래스 이름이 Pipeline과 protobuf를 모두 가져다 섞어놓은 느낌이 들겠지만 착각일 뿐이다. 두 api를 모두 사용해본 경험의 영향을 받긴 했지만… *Stream.cs 클래스들은 실제로 <code>System.IO.Stream</code>을 상속받아서 이름이 좀 비슷해졌다. 이 Stream 구현들이 단위버퍼들간의 연결을 seamless하게 쓸 수 있게해주는 역할을 한다. 주요 구현을 담고 있으나 사용계층에 노출될 필요는 없기 때문에 Detail 아래로 숨겨두었다. 사용자는 부모타입인 Stream 추상 클래스만 보게 된다.</p><p>인터페이스로 <code>ReadOnlySequence&lt;T&gt;</code>를 사용하지 않은 이유는 이 구현을 Unity3D로 만든 클라이언트에서도 똑같이 사용하기 위해서였다. 현시점 유니티의 mono framework가 지원하는 C# 문법 버전이 낮아서 <code>ReadOnlySequence&lt;T&gt;</code>를 지원하지 않기 때문이다. 그런데 Stream 을 이용해도 어렵지 않게 seamless 를 구현할 수 있었고, 실제 사용하기에도 스트림 형태가 훨씬 익숙하고 편해서 결과적으로는 더 만족스러운 선택이었다. <code>ReadOnlySequence&lt;T&gt;</code> 가 뭔지 모르는 프로그래머도 Stream은 알고 있을 것이다.</p><p>실제 사용 계층으로 노출하는 클래스는 아래의 세 클래스 만으로 정리했다. </p><ul><li><code>MemoryPipe</code> : 소켓 수신버퍼 처리 전용. System.IO.Pipeline과 유사하다.</li><li><code>SendBuffer</code> : 소켓 송신버퍼 처리 전용. </li><li><code>ZeroCopyBuffer</code> : 네트워크 버퍼가 아닌 범용적인 용도의 인터페이스.</li></ul><p>패킷을 보낼때는 데이터 타입을 버퍼로 직렬화 한 후, 이 버퍼를 메모리 복사 없이 소켓에 그대로 연결해주기 위한 추가 처리가 있어야 하는데, 이건 송신 버퍼에만 필요한 동작이라서 클래스를 별도로 나누었다. 각 용도에 특화된 메서드가 추가 구현 되어있을 뿐 코어는 모두 비슷하다. 모두 단위 버퍼를 줄줄이 비엔나처럼 연결해 들고 있는 역할을 한다.</p><p>이들 중에 가장 기본이 되는 ZeroCopyBuffer 를 조금 보면 아래와 같다. </p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">Cs.ServerEngine.Network.Buffer</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">sealed</span> <span class="keyword">class</span> <span class="title">ZeroCopyBuffer</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">readonly</span> <span class="title">Queue</span>&lt;<span class="title">LohSegment</span>&gt; segments</span> = <span class="keyword">new</span> Queue&lt;LohSegment&gt;();</span><br><span class="line">    <span class="keyword">private</span> LohSegment last;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">int</span> SegmentCount =&gt; <span class="keyword">this</span>.segments.Count;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">int</span> <span class="title">CalcTotalSize</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">      <span class="built_in">int</span> result = <span class="number">0</span>;</span><br><span class="line">      <span class="keyword">foreach</span> (<span class="keyword">var</span> data <span class="keyword">in</span> <span class="keyword">this</span>.segments)</span><br><span class="line">      &#123;</span><br><span class="line">        result += data.DataSize;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> BinaryWriter <span class="title">GetWriter</span>(<span class="params"></span>)</span> =&gt; <span class="keyword">new</span> BinaryWriter(<span class="keyword">new</span> ZeroCopyOutputStream(<span class="keyword">this</span>));</span><br><span class="line">    <span class="function"><span class="keyword">public</span> BinaryReader <span class="title">GetReader</span>(<span class="params"></span>)</span> =&gt; <span class="keyword">new</span> BinaryReader(<span class="keyword">new</span> ZeroCopyInputStream(<span class="keyword">this</span>));</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="keyword">void</span> <span class="title">Write</span>(<span class="params"><span class="built_in">byte</span>[] buffer, <span class="built_in">int</span> offset, <span class="built_in">int</span> count</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">      <span class="keyword">while</span> (count &gt; <span class="number">0</span>)</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>.last == <span class="literal">null</span> || <span class="keyword">this</span>.last.IsFull)</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="keyword">this</span>.last = LohSegment.Create(LohPool.SegmentSize.Size4k);</span><br><span class="line">          <span class="keyword">this</span>.segments.Enqueue(<span class="keyword">this</span>.last);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">int</span> copied = <span class="keyword">this</span>.last.AddData(buffer, offset, count);</span><br><span class="line"></span><br><span class="line">        offset += copied;</span><br><span class="line">        count -= copied;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">internal</span> LohSegment[] <span class="title">Move</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">      <span class="keyword">var</span> result = <span class="keyword">this</span>.segments.ToArray();</span><br><span class="line">      <span class="keyword">this</span>.segments.Clear();</span><br><span class="line">      <span class="keyword">this</span>.last = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">internal</span> LohSegment <span class="title">Peek</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.segments.Peek();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="keyword">void</span> <span class="title">PopHeadSegment</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">      <span class="keyword">var</span> segment = <span class="keyword">this</span>.segments.Dequeue();</span><br><span class="line">      segment.ToRecycleBin();</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">this</span>.segments.Count == <span class="number">0</span>)</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="keyword">this</span>.last = <span class="literal">null</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>본 주제와 관련한 인터페이스만 몇 개 간추려 보았다. <code>Queue&lt;LogSegment&gt;</code> 가 Pipeline 안에 있는 단위버퍼의 링크드 리스트 역할을 한다. Write()와 Move()는 메모리 복사 없이 데이터를 쓰는 인터페이스가 되고, Peek(), PopHeadSegment()는 데이터를 읽는 인터페이스가 되는데, internal 접근자니까 실제 사용계층에는 노출하지 않는다. Detail 하위의 *Stream 클래스를 위한 메서드들이다.</p><p>조각난 버퍼를 하나의 가상버퍼처럼 추상화해주는 로직은 *Stream들이 담고있다. System.IO.Stream을 상속했기 때문에 사용 계층에서는 보통의 파일스트림, 메모리 스트림을 다루던 방식과 똑같이 값을 읽고 쓰면 된다. 사용한 segment들을 새지 않게 잘 pooling하고, 버퍼 오프셋 계산할때 오차없이 더하기 빼기 잘해주는 코드가 전부인지라 굳이 옮겨붙이지는 않는다. </p><p>이렇게 하니 <code>ZeroCopyBuffer</code>는 가상의 무한 버퍼 역할을 하고, 사용 계층에는 Stream 형식의 인터페이스를 제공하는 <code>System.IO.Pipeline</code>의 유사품이 되었다. 제공되는 메서드 중에는 <code>async method</code> 가 하나도 없으니 cpu clock을 불필요하게 낭비할 일도 없다. 이렇게 디자인 하는것이 기존의 iocp 기반 소켓 구현에 익숙한 프로그래머에겐 더 친숙한 모델이면서, 성능상으로도 Pipeline보다 훨씬 낫고(tcp 기반 게임서버 한정), Unity3D처럼 최신의 Memory api가 지원 안되는 환경에서도 문제없이 사용할 수 있다.</p><h2 id="마치면서"><a href="#마치면서" class="headerlink" title="마치면서"></a>마치면서</h2><p><code>System.IO.Pipeline</code>은 ASP.NET Core의 성능을 크게 끌어올린 네트워크 버퍼 운용 라이브러리다. 이를 적용하면 네트워크 버퍼구현의 여러가지 문제점들과 boilerplate한 구현들을 손쉽게 해결할 수 있으나, 최소 2 tasks/peer를 소켓의 수명만큼 열어두어야 하기 때문에 소켓을 긴 시간 유지하는 타입의 TCP서버라면 도입 전에 신중한 성능 테스트를 거쳐야 한다. </p><p>사이즈가 무한인 가상의 버퍼라는 컨셉만을 가져와 직접 만들어 사용중인 <code>ZeroCopyBuffer</code> 모듈의 인터페이스도 간단하게 소개해 보았다. Unity3D 클라이언트 네트워크 모듈에도 함께 사용하기 위해 <code>ReadOnlySequence&lt;T&gt;</code> 대신 System.IO.Stream으로 추상화한 인터페이스를 제공했는데, 이렇게 하니 요구사항을 충분히 만족하면서도 사용 계층에게는 더 익숙한 형태의 인터페이스를 제공할 수 있어서 만족스러웠다.</p><p>본 포스팅에는 단위버퍼로 이용한 구현체인 <code>LogSegment</code>에 대한 소개가 없었다. 글 분량 조절에 실패하여 일부로 언급하지 않았는데, 다음에 가비지 컬렉터를 주제로 포스팅하면서 추가로 다뤄볼 예정이다. </p><p>참고:</p><ul><li><a href="https://www.slideshare.net/sm9kr/windows-registered-io-rio">https://www.slideshare.net/sm9kr/windows-registered-io-rio</a></li><li><a href="https://docs.microsoft.com/ko-kr/dotnet/standard/io/pipelines">https://docs.microsoft.com/ko-kr/dotnet/standard/io/pipelines</a></li><li><a href="https://devblogs.microsoft.com/dotnet/system-io-pipelines-high-performance-io-in-net/">https://devblogs.microsoft.com/dotnet/system-io-pipelines-high-performance-io-in-net/</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/devnote/2020/12/27/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-System-IO-Pipeline-%EB%8F%84%EC%9E%85-%ED%9B%84%EA%B8%B0/00.jpg&quot; class=&quot;&quot;&gt;

&lt;p&gt;2018년에 네트워크 레이어 성능을 끌어올리기 위해 도입했던 System.IO.Pipeline을 간단히 소개하고, 도입 후기를 적어본다. &lt;/p&gt;
&lt;p&gt;윈도우 OS에서 고성능을 내기 위한 소켓 프로그래밍을 할 때 IOCP 의 사용은 오래도록 변하지 않는 정답의 자리를 유지하고 있다. 여기에서 좀 더 성능에 욕심을 내고자 한다면 Windows Server 2012부터 등장한 &lt;a href=&quot;https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh997032(v=ws.11)&quot;&gt;Registerd IO&lt;/a&gt; 라는 새로운 선택지가 있다. 하지만 API가 C++ 로만 열려 있어서, C# 구현에서는 사용하기가 쉽지 않다. &lt;/p&gt;
&lt;p&gt;하지만 C#에도 고성능 IO를 위한 새로운 API가 추가되었다. &lt;a href=&quot;https://docs.microsoft.com/ko-kr/dotnet/standard/io/pipelines&quot;&gt;Pipeline&lt;/a&gt; 이다.&lt;/p&gt;</summary>
    
    
    
    
    <category term="c#" scheme="http://leafbird.github.io/devnote/tags/c/"/>
    
    <category term="고성능" scheme="http://leafbird.github.io/devnote/tags/%EA%B3%A0%EC%84%B1%EB%8A%A5/"/>
    
    <category term="게임서버" scheme="http://leafbird.github.io/devnote/tags/%EA%B2%8C%EC%9E%84%EC%84%9C%EB%B2%84/"/>
    
    <category term="Network" scheme="http://leafbird.github.io/devnote/tags/Network/"/>
    
    <category term="Socket" scheme="http://leafbird.github.io/devnote/tags/Socket/"/>
    
    <category term="Pipeline" scheme="http://leafbird.github.io/devnote/tags/Pipeline/"/>
    
  </entry>
  
  <entry>
    <title>C# 고성능 서버 - __FILE__, __LINE__ 대체제</title>
    <link href="http://leafbird.github.io/devnote/2020/12/26/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-FILE-LINE-%EB%8C%80%EC%B2%B4%EC%A0%9C/"/>
    <id>http://leafbird.github.io/devnote/2020/12/26/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-FILE-LINE-%EB%8C%80%EC%B2%B4%EC%A0%9C/</id>
    <published>2020-12-26T02:11:05.000Z</published>
    <updated>2021-01-01T14:58:12.410Z</updated>
    
    <content type="html"><![CDATA[<p>C++에서 가장 기본적으로 사용했던 <code>__FILE__, __LINE__, __FUNCTION__</code> 등의 매크로와 유사한 효과를 내는 방법에 대해 적어본다. 이와 함께 나에게는 생소했던 string interning 개념에 대해서도 살짝 소개해본다. 자바 같은 managed 언어를 깊이 다뤄본 적이 없는 네이티브 개발자에게는 생소한 개념일 것이다.<br>UI가 없는 서버에서 동작의 내용을 확인하는 가장 기본적인 방법은 file로 남기는 log다. 정상 동작이나 오류상황에 대한 상세한 로그가 남아야 문제가 생겼을 때 파악하기가 쉽기 때문에, 간단한 동작이지만 아주 빈번하게 호출되는 부분이다. 로그 출력에서 성능을 많이 빼앗기지 않도록 기반을 다져놓으면 비즈니스 로직 구현을 위해 더 많은 H/W 리소스를 배분할 수 있다.</p><p>성능을 굳이 신경쓰지 않는다면 아래 있는 내용을 끝까지 모두 적용할 필요는 없다. </p><a id="more"></a><h2 id="콜스택을-얻어와서-가장-마지막-함수를-찍는-방법"><a href="#콜스택을-얻어와서-가장-마지막-함수를-찍는-방법" class="headerlink" title="콜스택을 얻어와서 가장 마지막 함수를 찍는 방법"></a>콜스택을 얻어와서 가장 마지막 함수를 찍는 방법</h2><p>현재 스레드 컨텍스트에서의 <a href="https://docs.microsoft.com/ko-kr/dotnet/api/system.diagnostics.stackframe?redirectedfrom=MSDN&view=net-5.0">StackFrame</a> 정보를 얻어온 후, 프레임 데이터의 가장 마지막 부분을 읽어 호출자의 정보를 얻어낼 수 있다. C#으로 함수 호출 위치를 얻어올 때 가장 많이 쓰이는 방법이다. 가장 태초부터 있었던 방법이기 때문이다. 다음에 설명할 CompilerServices attribute는 .Net Framework 4.5부터 사용이 가능해졌기 때문에, 초창기 C#에서는 콜스택에서 읽어내는 방법 말고는 딱히 다른 선택지도 없었다.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">StackTrace st = <span class="keyword">new</span> StackTrace(<span class="keyword">new</span> StackFrame(<span class="literal">true</span>)); </span><br><span class="line"></span><br><span class="line">Console.WriteLine(<span class="string">&quot; Stack trace for current level: &#123;0&#125;&quot;</span>, st.ToString()); </span><br><span class="line"></span><br><span class="line">StackFrame sf = st.GetFrame(<span class="number">0</span>); </span><br><span class="line">Console.WriteLine(<span class="string">&quot; File: &#123;0&#125;&quot;</span>, sf.GetFileName()); </span><br><span class="line">Console.WriteLine(<span class="string">&quot; Method: &#123;0&#125;&quot;</span>, sf.GetMethod().Name); </span><br><span class="line">Console.WriteLine(<span class="string">&quot; Line Number: &#123;0&#125;&quot;</span>, sf.GetFileLineNumber()); </span><br><span class="line">Console.WriteLine(<span class="string">&quot; Column Number: &#123;0&#125;&quot;</span>, sf.GetFileColumnNumber()); </span><br></pre></td></tr></table></figure><p>C#에서 흔하게 사용하는 로깅 라이브러리인 <a href="https://logging.apache.org/log4net/">Log4Net</a>, <a href="https://nlog-project.org/">NLog</a> 등에서도 이 방법을 사용한다. </p><h4 id="콜스택-기반-장점-가장-범용적이다-프레임워크-호환성이-가장-좋음"><a href="#콜스택-기반-장점-가장-범용적이다-프레임워크-호환성이-가장-좋음" class="headerlink" title="콜스택 기반 장점 : 가장 범용적이다. 프레임워크 호환성이 가장 좋음"></a>콜스택 기반 장점 : 가장 범용적이다. 프레임워크 호환성이 가장 좋음</h4><p>.Net Framework의 태초부터 있었던 방식이므로 가장 범용적이다. 오래된 버전의 닷넷 프레임워크나 mono 프레임워크 등을 지원해야 하는 상황이라면 이 방법 말고는 마땅한 대안이 없다. 그래서 Log4Net, NLog 등의 유명한 라이브러리도 이 방법을 사용하고 있다. 이들은 불특정 다수의 환경에서 실행되어야 할 범용성이 중요한 모듈이기 때문이다. </p><h4 id="콜스택-기반-단점-말해서-무엇하랴-비용이-비싸고-느리다"><a href="#콜스택-기반-단점-말해서-무엇하랴-비용이-비싸고-느리다" class="headerlink" title="콜스택 기반 단점 : 말해서 무엇하랴. 비용이 비싸고 느리다."></a>콜스택 기반 단점 : 말해서 무엇하랴. 비용이 비싸고 느리다.</h4><p>지금 회사에서 사용하는 게임서버 엔진은 처음에 Log4Net을 쓰다가, 나중에 NLog로 바꾸었다가, 현재는 자체 구현한 파일로그 모듈을 쓰고 있다. 외부 모듈로는 내가 만족하는 성능을 얻지 못했기 때문이다. </p><p>Log4Net, NLog 모두 아주 좋은 로그 모듈인 것은 분명하다. Log4Net은 apache 소프트웨어 재단의 모듈인 만큼 아주 많은 곳에서 쓰이고 있을것이다. 두 모듈 모두 설정 문서만 읽어봐도 정말 기능이 많다. 로그파일을 사이즈나 시간에 맞춰 새 파일로 나눠주는 것은 물론이고, 메일로 로그를 전송할 수도 있고, 로그 레벨 설정도 자유롭고, 파일 생성 정책도 디테일하게 조절할 수 있고… 아무튼 아주 많다. </p><p>내가 이 두 모듈을 떠나서 직접 만들어 사용하는 가장 큰 이유는 <code>성능</code> 때문이다. 나에게는 굳이 내가 사용도 하지 않을 것 같은 다수의 편의기능들보다도 딱 내가 필요한 동작만 가지고 있더라도 가볍고 빠른 로그 모듈이 필요했다. Log4Net은 오래되서 잘 기억이 나지 않지만 NLog같은 경우 모듈 자체에서 스레드도 제법 많이 만들어서 운용하는걸 디버깅하다 본 기억이 있는데, 이런 내부 구조도 고성능 엔진을 만든다는 측면에서 부담스러웠다. (고성능을 위한 File IO 전략은 이 글의 주제에서 벗어나니까 다음 기회에 별도의 포스트로 다뤄보겠다.)</p><p>범용적인 로그 모듈들은 성능 또한 일반적이다. 크게 좋지도 않고 아주 나쁘지도 않는 수준을 보여준다. NLog를 사용할 때 설정에서 파일 이름과 라인 위치를 출력하는 동작을 끈 채로 사용해도 성능에는 별반 차이가 없었는데, 아마도 파일로 출력만 하지 않을 뿐  내부에서는 동일하게 <code>StacFrame</code> 을 얻어오는 동작이 실행되고 있을거라고 추측했다. 혹은 StackFrame 때문이 아닌, 다른 많은 부수 기능들 때문일 수도 있을 텐데, 아무튼 나의 기대치에는 맞지 않았다.</p><h2 id="System-Runtime-CompilerServices"><a href="#System-Runtime-CompilerServices" class="headerlink" title="System.Runtime.CompilerServices"></a>System.Runtime.CompilerServices</h2><p>.NET Framework 4.5부터 새로운 방식으로 함수 호출자의 정보를 가져올 수 있게 되었다. 요즘 .NET 6에 대한 뉴스도 돌고 있는 현시점에서 보면 충분히 오래된 방식이다. 만들어야 하는 프로그램의 런타임을 특정 프레임워크만 사용하도록 한정할 수 있다면 이 방식을 사용하는 것을 추천한다. 게임서버는 런타임 환경을 단 하나의 프레임워크로 고정할 수 있으니, 크게 문제될 것이 없다.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">DoProcessing</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    TraceMessage(<span class="string">&quot;Something happened.&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">TraceMessage</span>(<span class="params"><span class="built_in">string</span> message,</span></span></span><br><span class="line"><span class="function"><span class="params">    [CallerMemberName] <span class="built_in">string</span> memberName = <span class="string">&quot;&quot;</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">    [CallerFilePath] <span class="built_in">string</span> sourceFilePath = <span class="string">&quot;&quot;</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">    [CallerLineNumber] <span class="built_in">int</span> sourceLineNumber = <span class="number">0</span></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Trace.WriteLine(<span class="string">&quot;message: &quot;</span> + message);</span><br><span class="line">    Trace.WriteLine(<span class="string">&quot;member name: &quot;</span> + memberName);</span><br><span class="line">    Trace.WriteLine(<span class="string">&quot;source file path: &quot;</span> + sourceFilePath);</span><br><span class="line">    Trace.WriteLine(<span class="string">&quot;source line number: &quot;</span> + sourceLineNumber);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>함수 인자에 기본값이 있기 때문에 작업자가 함수를 호출할 때 값을 전달하지는 않지만, 그래도 보이지 않게 뒤쪽 인자를 통해 호출자의 파일명, 라인수 등이 넘어가는 방식이다. 인자에 붙어있는 attribute로 인해 함수 호출 위치에 맞는 값들이 <code>런타임에</code> 채워진다.</p><p>과거의 오래된 프레임워크를 지원할 수 없다는 점이 거꾸로 단점이 될텐데, 사실 NLog같이 누구나 어디서나 사용해야할 로그모듈을 만들게 아니고, 게임서버처럼 특정 비즈니스 프로젝트로 사용처를 한정한다면 오래된 프레임워크 미지원은 그렇게 큰 단점은 아니다. </p><h4 id="CompilerServices-장점-가볍고-빠르다"><a href="#CompilerServices-장점-가볍고-빠르다" class="headerlink" title="CompilerServices 장점 : 가볍고 빠르다."></a>CompilerServices 장점 : 가볍고 빠르다.</h4><p>위에서 언급했던 StackFramek 클래스를 사용하는 방식보다 훨씬 빠르다. C++의 <code>__FILE__, __LINE__</code> 은 매크로니까 이미 컴파일 타임에 문자열과 숫자로 치환되어 코드에 포함된다. CompilerServices 사용 방식은 런타임에 함수의 인자로 넘어가는 방식이니까 이것만큼 optimal할 수는 없지만, 콜스택을 긁어오는 것보다는 훨씬 빠르다.</p><h4 id="CompilerService-단점-가변인자-인터페이스-사용이-불가능-해진다"><a href="#CompilerService-단점-가변인자-인터페이스-사용이-불가능-해진다" class="headerlink" title="CompilerService 단점 : 가변인자 인터페이스 사용이 불가능 해진다."></a>CompilerService 단점 : 가변인자 인터페이스 사용이 불가능 해진다.</h4> <figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">DoProcessing</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  WriteLog(<span class="string">&quot;invalid value:&#123;0&#125;&quot;</span>, <span class="keyword">value</span>); <span class="comment">// 불가능합니다.</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">WriteLog</span>(<span class="params"><span class="built_in">string</span> format,</span></span></span><br><span class="line"><span class="function"><span class="params">  <span class="keyword">params</span> <span class="built_in">object</span>[] list,</span></span></span><br><span class="line"><span class="function"><span class="params">  [CallerFilePath] <span class="built_in">string</span> sourceFilePath = <span class="string">&quot;&quot;</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  [CallerLineNumber] <span class="built_in">int</span> sourceLineNumber = <span class="number">0</span></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>함수의 뒷부분 인자를 사용하게 되니까, 위와 같은 사용이 불가능하다. 예시처럼 formatting이 될 문자열을 처음에 받고 두번째부터 가변 인자를 받는 방법은  C++에서 로그 인터페이스를 만드는 가장 익숙한 방식이다. </p><p>하지만 C#은 나름대로의 해결법이 있다. <a href="https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/tokens/interpolated">보간 문자열</a>을 이용해 문자열을 포매팅하면 된다. .NET Framework 4.6 과 함께 C# 문법이 6.0으로 올라갔고 이 때부터 보간 문자열이 사용 가능해졌다. 최신의 C#에서는 String.Format보다 보간 문자열의 사용이 더 권장된다. - Effective C#, 빌 와그너. Chapter 1.4 <code>string.Format()을 보간 문자열로 대체하라</code> </p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">DoProcessing</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="comment">// WriteLog(&quot;invalid value:&#123;0&#125;&quot;, value); // C++스러워 보이지만, 촌스러운 방식이예요.</span></span><br><span class="line">  WriteLog(<span class="string">$&quot;invalid value:<span class="subst">&#123;<span class="keyword">value</span>&#125;</span>&quot;</span>); <span class="comment">// 가능합니다. 권장됩니다. Effective C# 읽어보세요.</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">WriteLog</span>(<span class="params"><span class="built_in">string</span> message,</span></span></span><br><span class="line"><span class="function"><span class="params">  [CallerFilePath] <span class="built_in">string</span> sourceFilePath = <span class="string">&quot;&quot;</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  [CallerLineNumber] <span class="built_in">int</span> sourceLineNumber = <span class="number">0</span></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>C#이 5.0이었을 시점만 해도 이건 큰 단점이었다. 하지만 현 시점에서 이것도 그리 문제될 것이 없다.</p><h2 id="C-은-코드영역을-사용하지만-C-은-힙을-사용한다"><a href="#C-은-코드영역을-사용하지만-C-은-힙을-사용한다" class="headerlink" title="C++은 코드영역을 사용하지만, C#은 힙을 사용한다."></a>C++은 코드영역을 사용하지만, C#은 힙을 사용한다.</h2><p>좀 더 성능에 집착해보자(?).</p><p>윗부분에서 잠시 언급했듯이, C++의 <code>__FILE__, __LINE__</code> 은 컴파일 시점에 이미 실제 값으로 변환을 완료하는 preprocessing 이다. 런타임에 함수 호출자 정보를 얻기 위해 추가로 들이는 비용이 거의 없다.</p><img src="/devnote/2020/12/26/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-FILE-LINE-%EB%8C%80%EC%B2%B4%EC%A0%9C/00.jpg" class=""><p>(이미지 출처 : <a href="https://en.wikipedia.org/wiki/Data_segment">wikipedia</a>)</p><p>이미지에서 text로 표현된 부분이 코드영역이다. 이 공간은 고정적인 읽기 전용의 공간이다. C++의 <code>__FILE__</code> 매크로를 다르게 표현하면 결국 이 코드영역의 특정 위치를 가르키는 char*로 변환될 뿐이다. 추가적인 객체 할당은 없다.</p><p>하지만 C#은 코드영역을 사용하지 않는다. <code>[CallerFilepath] string filePath</code> 는 <strong>함수 호출이 일어날 때마다 heap 영역에 스트링 객체를 할당한다</strong>. 디버그를 위해 상세하게 로그를 달면 달 수록 heap에는 동일한 텍스트가 반복적으로 할당되어 메모리에 압력을 가하게 된다. </p><p>C#에서는 C++처럼 코드영역을 참조하는 문자열을 만드는 방법이 없다. 모든 참조형식의 객체는 heap이 아닌 공간을 사용할 수 없기 때문으로 추측이 된다. value type을 object 형식으로 가리키면 굳이 비싼 비용을 들이면서까지 heap에 추가할당을 만드는 boxing을 하는 이유와 같을 것이다. </p><p>반복적으로 사용하는 똑같은 문자열인데도, 매번 함수가 불릴 때마다 이걸 heap에 재할당을 할까? 하고 나도 처음엔 그렇게 생각했다. C++을 하면서 생긴 사고의 관성일 것이다. C#의 string은 참조 타입이고, immutable해서 한 번 할당하면 변경도 불가한 성격을 갖고 있기 때문에 충분히 착각할 만한 상황이기도 하다 - 라고 자기 합리화를 해본다.  하지만 windbg를 이용해 heap을 디버깅 하던 중 무수히 많은 파일 경로 텍스트가 중복으로 잔뜩 들어있는걸 보고 나서야 아닌 것을 깨달았다. </p><h2 id="Interned-String"><a href="#Interned-String" class="headerlink" title="Interned String"></a>Interned String</h2><p>완전하게 내용이 같은 string을 pooling하여 heap에 한 번만 할당하고 돌려쓰는 방법이 없는 것은 아니다. 이렇게 언어 자체적으로 문자열을 풀링하는 처리를 Java와 C#에서는 모두 Interning이라고 부른다. </p><ul><li>Java - <a href="https://www.javatpoint.com/java-string-intern">String Intern()</a></li><li>C# - <a href="https://docs.microsoft.com/ko-kr/dotnet/api/system.string.intern?view=net-5.0">String Intern()</a></li></ul><p>사용법은 간단하다. 풀링하고 싶은 문자열을 사용할 때 <code>string.Intern()</code> 메소드를 한 번 더 감싸주면 된다. 현재 회사에서 실제 사용중인 모듈의 인터페이스 부분만 보면 아래처럼 되어있다. </p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.IO;</span><br><span class="line"><span class="keyword">using</span> System.Runtime.CompilerServices;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">Log</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Debug</span>(<span class="params"><span class="built_in">string</span> message, [CallerFilePath] <span class="built_in">string</span> file = <span class="string">&quot;&quot;</span>, [CallerLineNumber] <span class="built_in">int</span> line = <span class="number">0</span></span>)</span></span><br><span class="line"><span class="function"></span>  &#123;</span><br><span class="line">    <span class="comment">// ... 중략...</span></span><br><span class="line">    provider.Debug(<span class="string">$&quot;<span class="subst">&#123;message&#125;</span> (<span class="subst">&#123;BuildTag(file, line)&#125;</span>)&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="built_in">string</span> <span class="title">BuildTag</span>(<span class="params"><span class="built_in">string</span> file, <span class="built_in">int</span> line</span>)</span></span><br><span class="line"><span class="function"></span>  &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">string</span>.Intern(<span class="string">$&quot;<span class="subst">&#123;Path.GetFileName(file)&#125;</span>:<span class="subst">&#123;line.ToString()&#125;</span>&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>전달받은 파일명을 바로 사용하지 않고 string.Intern()으로 한 번 감싸서 사용한다. 로그를 출력하면 아래처럼 찍힌다. </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">2020-12-21 12:08:02.144 [Debug] [ConnectionMonitor] add uid:1 #connection:1 (ConnectionMonitor.cs:32)</span><br><span class="line">2020-12-21 12:08:02.145 [Info] [Send] [20017] kREGISTER_GAME_SERVER_REQ actionId:3 (SerializableExt.cs:92)</span><br><span class="line">2020-12-21 12:08:02.205 [Info] db connection Initialized. type:Auth server:localhost count:16 (DbPool.cs:40)</span><br><span class="line">2020-12-21 12:08:02.221 [Info] db connection Initialized. type:Contents server:localhost count:16 (DbPool.cs:40)</span><br><span class="line">2020-12-21 12:08:02.238 [Info] db connection Initialized. type:Game server:localhost count:16 (DbPool.cs:40)</span><br></pre></td></tr></table></figure><p>interning은 입구만 있고, 출구는 없는 string pool이다. 풀에 등록은 할 수 있지만 해제할 수는 없다. 한 번 쓰고 마는 동적인 문자열은 당연히 interning해서는 안된다. 반복적으로 사용하더라도 빈도가 낮아서, heap의 할당과 해제에 큰 압력을 주지 않는다면 이것도 굳이 interning할 필요는 없다. 이런 문자열들을 interning하면 장시간 떠있어야 하는 서버 프로그램의 경우 오히려 더 악영향을 끼칠 수 있다. 용도에 맞게 적절하게 적용해야 한다. </p><p>C#에서 코드에 함께 적혀있는 literl text들은 기본적으로 interning된다. C++처럼 code segment를 직접 가르키지는 않지만, 비슷한 효과를 내기 위함이다. 그 외에 프로그램이 사용하는 나머지 문자열에 대해서는 어떤 것을 interning할지 직접 판단하고 선별 적용해야 한다. 로그 메세지에 반복적으로 찍히는 소스코드 파일명은 interning하기에 적합한 대상이다. </p><h2 id="마치면서"><a href="#마치면서" class="headerlink" title="마치면서"></a>마치면서</h2><p>로그파일에서 로그 출력 위치를 남기는 방식에 관련해 성능 위주의 고려사항을 정리해 보았다. </p><ul><li>함수 호출자 정보를 얻고 싶을 땐 StackFrame 사용 보다 CompileServices 하위 어트리뷰트를 쓰는게 낫다. </li><li>C#은 모든 문자열을 항상 heap에 할당한다. 심지어 literal text같은 상수 문자열을 사용한다 하더라도 메모리 코드영역의 직접 참조가 불가능하다. </li><li>로그를 찍을 때마다 heap에 불필요한 객체 할당이 발생하는 것을 줄이고 싶다면 문자열을 Interning하면 된다.</li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;C++에서 가장 기본적으로 사용했던 &lt;code&gt;__FILE__, __LINE__, __FUNCTION__&lt;/code&gt; 등의 매크로와 유사한 효과를 내는 방법에 대해 적어본다. 이와 함께 나에게는 생소했던 string interning 개념에 대해서도 살짝 소개해본다. 자바 같은 managed 언어를 깊이 다뤄본 적이 없는 네이티브 개발자에게는 생소한 개념일 것이다.&lt;br&gt;UI가 없는 서버에서 동작의 내용을 확인하는 가장 기본적인 방법은 file로 남기는 log다. 정상 동작이나 오류상황에 대한 상세한 로그가 남아야 문제가 생겼을 때 파악하기가 쉽기 때문에, 간단한 동작이지만 아주 빈번하게 호출되는 부분이다. 로그 출력에서 성능을 많이 빼앗기지 않도록 기반을 다져놓으면 비즈니스 로직 구현을 위해 더 많은 H/W 리소스를 배분할 수 있다.&lt;/p&gt;
&lt;p&gt;성능을 굳이 신경쓰지 않는다면 아래 있는 내용을 끝까지 모두 적용할 필요는 없다. &lt;/p&gt;</summary>
    
    
    
    
    <category term="c#" scheme="http://leafbird.github.io/devnote/tags/c/"/>
    
    <category term="고성능" scheme="http://leafbird.github.io/devnote/tags/%EA%B3%A0%EC%84%B1%EB%8A%A5/"/>
    
    <category term="게임서버" scheme="http://leafbird.github.io/devnote/tags/%EA%B2%8C%EC%9E%84%EC%84%9C%EB%B2%84/"/>
    
    <category term="메모리" scheme="http://leafbird.github.io/devnote/tags/%EB%A9%94%EB%AA%A8%EB%A6%AC/"/>
    
    <category term="string interning" scheme="http://leafbird.github.io/devnote/tags/string-interning/"/>
    
  </entry>
  
  <entry>
    <title>테크니컬 리더십: 시작하기</title>
    <link href="http://leafbird.github.io/devnote/2018/11/12/%ED%85%8C%ED%81%AC%EB%8B%88%EC%BB%AC-%EB%A6%AC%EB%8D%94%EC%8B%AD-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0/"/>
    <id>http://leafbird.github.io/devnote/2018/11/12/%ED%85%8C%ED%81%AC%EB%8B%88%EC%BB%AC-%EB%A6%AC%EB%8D%94%EC%8B%AD-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0/</id>
    <published>2018-11-12T14:05:29.000Z</published>
    <updated>2020-12-31T02:09:39.508Z</updated>
    
    <content type="html"><![CDATA[<p>예전에 트위터 하다가 읽었던 글인데, 개인적으로 마음에 들어서 부족하게나마 번역해 보았습니다.<br>원문은 슬랙 개발 블로그의 <a href="https://slack.engineering/technical-leadership-getting-started-e5161b1bf85c">Technical Leadership: Getting Started</a>라는 글입니다.<br>번역에 크게 자신이 없으니 부담이 없으신 분들은 원문을 보셔요. </p><img src="/devnote/2018/11/12/%ED%85%8C%ED%81%AC%EB%8B%88%EC%BB%AC-%EB%A6%AC%EB%8D%94%EC%8B%AD-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0/00.png" class="" title="개발 실무자의 리더십은 스스로를 리딩하는 것에서 시작한다"><a id="more"></a><h2 id="테크니컬-리더십-시작하기"><a href="#테크니컬-리더십-시작하기" class="headerlink" title="테크니컬 리더십: 시작하기"></a>테크니컬 리더십: 시작하기</h2><p>내가 소프트웨어 엔지니어가 되기 전에는 이 직업에서 가장 중요한 점은 코딩이라고 생각했다. 그것은 잘못된 생각이었고, 소프트웨어 공학의 가장 중요한(그리고 가장 어려운)점은 다른 사람들과 원만하게 잘 협력하는 것이다. </p><p>나는 “관리자는 되지 않을거야!”라고 스스로에게 말해왔고, “그렇게 하면, 내 모든 에너지를 개발에만 집중시킬 수 있을거야!” 라고 생각했다. 내 이후의 경력도 기술 지향적인 실무자 위주로만 관리해 간다면 이 어려운 대인관계를 어느 정도 무시할 수 있을 거라고 생각했다. </p><blockquote><p>빨리 가려거든 혼자 가고, 멀리 가려거든 함께 가라.</p></blockquote><p>내가 업무에서 대인관계를 소홀히 여기던 때 의아하게 생각했던 점은 “왜 사람들은 나의 의견을 들어주지 않지?” 하는 점이었다. 이는 슬랙(Slack)의 플랫폼 팀에서 처음 작업을 시작했을 때 특히 그러했다. 나는 슬랙의 API가 토큰을 사용하고 있는 점을 변경하여 보안을 강화하고, 제품 개발팀 전체에 걸쳐 일관된 개발 과정을 유지하도록 개선하고 싶었다. 그러나 몇 달 동안, 나의 제안이 많은 이들의 시간을 보다 가치있게 활용할 수 있는 방법이라고 PM이나 팀원들을 설득하는 것은 불가능했다.</p><p>이후로도 몇 차례 나의 의견은 받아들여지지 않고, 같은 팀 수석 엔지니어들의 의견이 채택되는 것을 지켜보면서 내게 무언가 빠진 요소가 있다는 것을 알게 되었는데, 그것은 바로 ‘리더십’이었다. 나는 매일같이 키보드에만 코를 박고 있으면 안되는 것이었다. 내가 성장하기를 원한다면, 다른 사람들이 나와 동등한 수준으로 기여할 수 있도록 도움을 주어야 했던 것이다. 나는 리더십을 통해 나의 영향력을 키워야 할 필요가 있었다. </p><p>이 글을 통해 필자 스스로가 리더십에 대해 배운 점과, 개발자 리더십의 절차(Path)에 대해 이야기해 보고자 한다. </p><h2 id="자기-자신을-리딩하기"><a href="#자기-자신을-리딩하기" class="headerlink" title="자기 자신을 리딩하기"></a>자기 자신을 리딩하기</h2><p>슬랙의 엔지니어로 지내면서, 나는 관리(management)와 리더십(leadership)이 어떻게 다른지 이해하게 되었다. </p><p>관리자(manager)는 자신의 보고서에 대한 책임이 있다. 관리자들은 코칭과 구조화를 통해 좋은 팀을 구축하는 것에 중점을 둔다. 또한 팀의 성장을 위해 성과를 관리한다. </p><p>관리자(manager)는 종종 리더(leader)를 겸임하지만, 리딩은 사실 다른 누구라도 할 수 있는 별개의 것이다. 리딩은 권위에 의존하는 무언가가 아니라, 다른 사람에게 미치는 영향력에 대한 것이다. 리딩은 비전에 대해 소통하고, 비전을 실현하기 위해 다른 이들에게 힘을 실어주는 것이다.</p><p>당신은 다른 이들을 리드하기 전에, 먼저 당신 자신을 리드할 수 있어야 한다. 자신을 리딩하는 것은 타인을 리드하거나, 조직을 리드하기 전에 반드시 먼저 선행되어야 한다. 자신을 리딩한다는 개념은 다양한 분야와 기업에서 정리한 여러 리더십의 정의들에서 찾아볼 수 있다. </p><p>자기 자신을 리딩하는 것은 그 사람의 우수한 역량과 밀접한 관련이 있다. 모범적인 자세를 통해 드러나는 리더십은 타인에게 자극을 주는 가장 강력한 방법이기 때문이다. 자신을 리딩한다는 말은 늘 최선을 다해서 개인의 업무를 수행하고, 스스로가 만들어내는 결과물의 품질에 대해 책임을 지는 것을 의미한다. </p><p>자기 자신에게 성공적인 리더십을 발휘하기 위한 다섯하기 요소는 방향 맞추기, 전문가 되기, 공유하기, 일관되게 실행하기, 효과적인 의사소통하기 이다. </p><h2 id="방향-맞추기-Finding-Alignment"><a href="#방향-맞추기-Finding-Alignment" class="headerlink" title="방향 맞추기(Finding Alignment)"></a>방향 맞추기(Finding Alignment)</h2><img src="/devnote/2018/11/12/%ED%85%8C%ED%81%AC%EB%8B%88%EC%BB%AC-%EB%A6%AC%EB%8D%94%EC%8B%AD-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0/01.jpg" class="" title="“Surya Namaskara” by Indian Navy licensed under Creative Commons."><p>직장에서 우수함을 나타내려면 먼저, 팀을 이해해야 하고, 회사를 이해해야 한다. </p><p>‘원칙’이란 어떤 행동이 바람직한지, 혹은 바람직하지 않은지를 안내하는 회사의 규범을 말한다. 대개는 이런 원칙들이 명확하게 규정되지 않은 경우도 많은데, 이런 숨은 원칙을 잘 찾아내는 것 역시 개인의 몫이다. 이 원칙들은 당신의 나침반과도 같다. 원칙들은 당신이 회사의 목표와 가치에 맞는 결정을 내리는데 큰 도움을 줄 것이다. </p><p>슬랙에서의 예를 들어보면, 우리는 슬랙의 사용자들에게 매우 뛰어난 사용 경험을 제공하고 있다는 믿음이 있다. 어떤 고객이 슬랙의 핵심 기능 중의 하나가 망가졌다는 제보를 한다면, 나에게는 그 즉시 내가 하고 있던 일을 모두 멈추고 현상을 확인해 즉시 문제를 해결하는 것이 가장 중요하다. 하지만 다른 회사에서는 내가 하던 일을 내팽개치는 것이 완전히 잘못된 판단이 될 수도 있는 것이다. </p><p>대부분의 결정은 여러가지 가치를 두고 다각도로 고민하면서 내려져야 한다. 오늘은 그동안 쌓아둔 기술 부채를 해결하는데 시간을 쓸 것인가? 아니면 좀 더 미루고 내일의 작업을 위한 기반작업을 할 것인가? 버그를 잡는 것, 툴을 만드는 것, 새로운 기능을 개발하는 것이 더 중요하진 않은가? 직장에서 할애할 수 있는 총 시간과 에너지의 양은 제한되어 있다. 회사가 중요하게 생각하는 것과 개인이 노력을 기울이는 방향을 동일하게 맞출 때 당신의 기여도는 가장 최대의 효율을 발휘할 것이다. </p><p>방향성 맞추기는 단지 회사가 당신에게 바라는 일을 수행하는 것만을 뜻하지 않는다. 여러분들은 리더로서 여러가지 문제를 직면하고, 이를 해결하기 위한 (숨어있는) 솔루션을 제시할 숱한 기회들을 마주하게 될 것이다. 하지만 그 때마다 다른 동료들에게 이것이 왜 문제이며, 왜 이를 해결하기 위해 에너지를 써야 하는가를 납득시키기 위해서는 먼저 회사가 무엇을 중요하게 생각하는지를 이해하고 다른사람에게 잘 설명할 수 있어야 한다. </p><h2 id="전문가-되기-Become-an-Expert"><a href="#전문가-되기-Become-an-Expert" class="headerlink" title="전문가 되기(Become an Expert)"></a>전문가 되기(Become an Expert)</h2><p>전문가가 되는 것은 개인 스킬을 연마하는 것에 관한 이야기다. 잠재력을 가진 상태라는 것이 하나의 좋은 자질일 순 있겠지만, 그걸로는 충분하지 않다. 리더는 실제로 뛰어난 전문가(export)여야 한다. 콜로라도 대학의 앤더스 에릭슨 교수에 따르면, 전문가가 되기 위해서는 평균 10년 이상 <a href="https://hbr.org/2007/07/the-making-of-an-expert">높은 수준의 의식적인 노력을 10,000시간 이상 기울여야 한다</a>고 말한다.</p><p>사람들은 종종 내가 오페라를 불렀던 경험이 소프트웨어 공학 경력에 도움이 되는지를 묻곤한다. 맞다! 음악을 통해서 나는 스스로의 마음가짐을 발전시킬 수 있었다. 아리아를 연습할 때면 가장 자신 없는 파트를 제일 자신있는 파트만큼의 자연 스러운 소리가 나올 때까지 몇시간이고 반복해서 연습했다. 소프트웨어 공학도 이것과 똑같다. 우리는 자신이 취약한 부분을 개발하는데 더욱 많은 시간을 투자해야 한다.</p><p>숙련을 쌓는 방법에 지름길이란 없다. 다만 꾸준하고 의식적인 노력으로 개발시키는 것 뿐이다. 내 자신에게(그리고 당신 주변의 사람들에게) 질문을 던져보자: 내가 가장 크게 성장할 수 있는 분야는 무엇인가? 전문가가 되기 위해서 나는 어떤 스킬을 개발해야 하는가?</p><p>당신이 개발하기 원하는 많은 스킬들이 있을 수 있지만, 노력을 기울이기 전에 먼저 다음의 질문을 던져보기를 권장한다: 그 스킬은 회사가 추구하는 방향에 부합하는가? 그 스킬은 나의 개인적인 목표에도 부합하는가?</p><p>‘아직 아무것도 이룬 것이 없다’는 생각만 하고 있을 게 아니라 매일 꾸준히 지식과 스킬을 체득하고자 노력하는 과정이 필요하다. 누구나 태어날 때부터 전문가였던 사람은 없다. </p><h2 id="공유하기-Share"><a href="#공유하기-Share" class="headerlink" title="공유하기(Share)"></a>공유하기(Share)</h2><p>자기 자신을 리딩하는 과정이 지나면, 다른 사람을 리드할 기회가 주어지고, 당신의 동료들이 최고의 성과를 내도록 역할을 부여하게 된다. 이를 성공적으로 수행하기 위해서는 먼저 지식을 공유해야 한다. </p><p>스킬을 습득하기 위해 많은 개인 시간을 소비한 후라면 선뜻 지식을 공유하는 것이 쉽지 않을 수도 있다. 특별한 전문성을 혼자만 “소유”하고 싶은 것은 본능적인 생각이다. 전문 지식은 체득 과정의 노력이 보이지 않을 땐 마치 마술처럼 느껴질 수도 있다. 당신은 혼자만의 마법을 비밀 상자에 숨겨놓고 외딴 곳에 보관하고 있다가 필요할 때만 꺼내서 사용하고 싶어할 수 있다. 다른 사람들은 그걸 어떻게 하는지 모르기 때문에, 당신만의 전문성은 여전히 마법을 유지하게 될 것이다. </p><p>하지만 바로 이 부분이 핵심이다. 당신의 노하우를 혼자만 알고 있으면 동료들은 당신에게 의존하게 되고, 결국 동료들의 성장을 방해하는 셈이 된다. 당신 스스로도 새로운 일을 배우는 것을 불안하다고 여기게 되어, 자신의 성장마저 방해하는 셈이 된다. 당신은 동료들이 팀에 기여하는 것을 막고 있으며, 팀을 아주 적극적으로 망치고 있는 셈이다. </p><p>나도 내가 가진 정보를 혼자만 유지하곤 했는데, 일부러 숨기고자 해서 그랬다기 보다는 이것이 유익한 정보인지 깨닫지 못했던 경우였다. 예를 들어, 나의 프로젝트에서는 업무의 진행을 방해하는 일반적인 문제점들에 대해 탐구하고 정리해왔다: 킥오프, 최종 마일스톤, 회귀 없는 릴리즈 같은 것들(역주: 예시의 내용들이 무엇을 말하는지는 잘 모르겠습니다). 나는 주위 동료들도 함께 성공했으면 하는 마음에 내가 유지하던 정보들 중 다른 팀들과 공유할 수 있는 기술들을 분류하기 시작했다. 사실 내 프로젝트만 잘 돌아가면 상관 없는 일이었지만.. 그것은 추가 확장이 없는 x1배의 영향력이다. 허나 이런 정보들은 모든 팀들에게 적용 가능한 것들이었고, 이것은 xN 배의 영향력을 발휘하게 된다.</p><p>지식을 숨기는 대신 공유하라. 멘토링이나 페어 프로그래밍 같은 1:1 방식도 좋고, 프레젠테이션이나 문서화 같은 1:N 방식도 좋다. 당신이 배운 사실을 다른 사람들에게도 가르쳐라. 그럼 다른 사람들은 다시 그 다음 사람들을 가르칠 것이다. 당신은 다시 배우고자 하는 그 다음 스킬로 자유롭게 이동할 수 있다. 지식이란 마르지 않는 샘이다. 아무리 배워도 항상 더 많이 남아있다. </p><img src="/devnote/2018/11/12/%ED%85%8C%ED%81%AC%EB%8B%88%EC%BB%AC-%EB%A6%AC%EB%8D%94%EC%8B%AD-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0/02.jpeg" class="" title="by Nguyen Hung Vu licensed under Creative Commons."><h2 id="일관되게-실행하기-Execute-Consistently"><a href="#일관되게-실행하기-Execute-Consistently" class="headerlink" title="일관되게 실행하기(Execute Consistently)"></a>일관되게 실행하기(Execute Consistently)</h2><p>일전에 나의 관리자와 나눴던 대화가 기억난다. 나는 관리자에게 최근의 프로젝트에서 내가 매우 뛰어난 성과를 기록했다고 말하고, 내가 언제쯤 승진할 수 있느냐고 질문했다. 그는 현명하게 대답했다: “당신은 이번과 같은 좋은 성과를 일관되고 꾸준하게 달성할 수 있음을 증명해야 합니다.”</p><p>일관성. 그것은 일시적인 운과 리더십의 차이를 말해준다.</p><p>당신이 어느 한가지 일을 딱 한 번 잘해냈다는 것은 별로 중요하지 않다. 정말 중요한 것은 당신이 그 일을 다시, 또 다시, 그리고 또 다시 잘 해낼 수 있는가 하는 것이다. </p><p>일관성 있는 실행력을 갖기 위해서는, 다양한 규모와 유형의 여러가지 프로젝트를 해봐야 할 것이다. 작은 규모, 큰 규모, 복합적인 기능, 사용자 친화적 UX, 백엔드 솔루션 등등. 이러한 경험들에서 당신은 다양한 도전 과제를 마주하고 해결 방안들을 개발하게 된다. 당신의 약점이 무엇인지를 드러내주고 당신이 스킬을 연마하도록 도울것이다.</p><p>당신의 관리자에게, 당신이 익히려고 하는 기술들을 미리 공유하라. 앞으로 맡게 될 프로젝트를 주시하고 그 중에 자신이 흥미가 가는 부분이 무엇이며 왜 그렇게 생각하는지를 관리자에게 미리 알려라. 당신이 지금 프로젝트를 진행중이라면, 작업하는 동안 나는 어떤 스킬을 선정해 발전시켜갈 것인가에 대해 생각하라. 이것은 직장에서의 시간을 최대의 효율로 활용하는데 큰 도움을 줄 것이다. </p><p>때로는 당신이 크게 열정을 느끼지 못하지만 팀의 임무에는 중요한(mission-critical)일에 배정이 될 때도 있다. 당신은 이 또한 잘 해낼 수 있음을 증명해야 한다. </p><p>일관되게 실행하는 것은 개인의 브랜드를 개발시키고 동료들에게 신뢰를 쌓을 수 있는 방법이다. 신뢰감을 형성하고 키우는 데에는 많은 시간과 경험이 필요하다. 하룻밤 만에 만들어지지 않는다. 한 번 신뢰를 얻었다 하더라도 지속적인 노력이 뒤따라야만 이를 오래도록 유지할 수 있다. </p><h2 id="효과적인-의사소통하기-Communicate-Effectively"><a href="#효과적인-의사소통하기-Communicate-Effectively" class="headerlink" title="효과적인 의사소통하기(Communicate Effectively)"></a>효과적인 의사소통하기(Communicate Effectively)</h2><p>“왜 사람들이 내 말을 들어주지 않는거야?” 하고 궁금해 한 적이 있는가?</p><p>나는 신입일 때 여러 차례 위와 같은 질문을 하곤 했다. 그러던 어느날 문득 내가 성장의 준비가 되었을 즈음에, 사장님이 중요한 단서를 주었다: 나는 동료들에게 부정적인 성향으로 인식되고 있었다는 점이다. 처음엔 그 피드백을 듣고 기분이 상했다. 하지만 이것이 나의 경력에서 중요한 전환점이 되었다. 그 후로 나는 ‘목소리’ 코치와 함께 일하게 되었고, 효과적인 커뮤니케이션의 중요한 비밀을 깨닫게 되었다. 그것은 경청(listening)이다.</p><p>경청이란 단순히 정보를 받아들이는 것이 아니다. 경청은 정보와 함께 그것의 맥락을 모두 합쳐 하나의 덩어리로 합성하는 것이다. 경청은 상대방의 의견이 어디에서 왔는지를 이해하고, 더 깊은 이해를 얻기 위해 명확한 질문을 던지는 것을 말한다. 이 합성의 듣기는 효과적인 커뮤니케이션의 가장 기본임과 동시에, 당신이 말하고자 하는 아이디어에도 엄청난 힘을 실어준다 - 믿거나 말거나.</p><div class="twitter-wrapper"><blockquote class="twitter-tweet"><a href="https://twitter.com/sarahmei/status/862584755849539584"></a></blockquote></div><script async defer src="//platform.twitter.com/widgets.js" charset="utf-8"></script><blockquote><p>레벨이 올라감에 따라 관리자 트랙과 엔지니어 트랙에게는 모두 동일한 의사소통 기술이 요구됩니다. 각 트랙의 진정한 능력자들이 서로 다른 트랙의 능력자를 존재할 수 있게 만듭니다. - Sarah Mei</p></blockquote><p>효과적인 의사소통의 또 다른 측면은 적절한 맥락으로 반복하는 것이다. 사람들이 왜 내 말에 귀기울이지 않는지 몰랐을 때의 나는 했던 말을 다시 반복해야 할 때면 화를 내면서 말했다. </p><p>나중에서야 효과적인 의사소통의 고수들을 관찰하기 시작했다. 그들은 다방면으로 정보를 노출한다. 적절한 시간 간격을 두고 반복적으로 정보를 전달하고, 듣는 사람이 누군가에 따라 그에 맞는 다양한 세부 정보들을 제공한다.</p><p>정보를 듣고 종합하고, 효과적으로 공유하는 방법을 익히는 것은 직급에서 오는 권위에 의존하지 않고 사람들에게 영향을 미치는 기본적 기술이다. 모두가 하나의 비전을 바라하도록 사람들을 모으기 위해서는 이러한 영향력이 필요하다.</p><p>. . . </p><p>소프트웨어 엔지니어로 일을 시작할 때, 왜 나의 아이디어가 회사에서-그리고 업계에서-잘 받아들여지지 않는 것인가를 궁금해했다. 그러던 중 컴퓨터만 골똘히 들여다보던 시선을 잠시 벗어나, 주변의 훌륭한 동료들을 만나보게 되면서 깨달았다. 내가 생각하는 방향성을 다른 사람들이 함께 공감하고, 실현하기 위해 같이 노력하도록 동기부여할 수 있다면 훨씬 더 큰 영향력을 미칠 수 있다는 것을.</p><p>리더십에 관해서는 배워야 할 것이 많고, 필자 개인적으로는 더 많은 것들을 배워야 한다. 리더급 개발자가 되고자 한다면, 먼저 자기 자신을 리딩하는 것부터 시작하기를 권한다. 이 외에 당신이 찾아낸 리더십에 대해 내게도 알려주길 바란다!</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;예전에 트위터 하다가 읽었던 글인데, 개인적으로 마음에 들어서 부족하게나마 번역해 보았습니다.&lt;br&gt;원문은 슬랙 개발 블로그의 &lt;a href=&quot;https://slack.engineering/technical-leadership-getting-started-e5161b1bf85c&quot;&gt;Technical Leadership: Getting Started&lt;/a&gt;라는 글입니다.&lt;br&gt;번역에 크게 자신이 없으니 부담이 없으신 분들은 원문을 보셔요. &lt;/p&gt;
&lt;img src=&quot;/devnote/2018/11/12/%ED%85%8C%ED%81%AC%EB%8B%88%EC%BB%AC-%EB%A6%AC%EB%8D%94%EC%8B%AD-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0/00.png&quot; class=&quot;&quot; title=&quot;개발 실무자의 리더십은 스스로를 리딩하는 것에서 시작한다&quot;&gt;</summary>
    
    
    
    
    <category term="리더십" scheme="http://leafbird.github.io/devnote/tags/%EB%A6%AC%EB%8D%94%EC%8B%AD/"/>
    
  </entry>
  
  <entry>
    <title>C++ 코드 정리 자동화 - 3. pch 사이즈 확인, #include 순서정리</title>
    <link href="http://leafbird.github.io/devnote/2014/09/30/cleanup-cpp-project-3rd/"/>
    <id>http://leafbird.github.io/devnote/2014/09/30/cleanup-cpp-project-3rd/</id>
    <published>2014-09-30T06:17:15.000Z</published>
    <updated>2020-12-31T02:15:11.027Z</updated>
    
    <content type="html"><![CDATA[<h2 id="pch-파일-사이즈"><a href="#pch-파일-사이즈" class="headerlink" title="pch 파일 사이즈"></a>pch 파일 사이즈</h2><p>팀에서 만지는 코드에서는, 290Mb에 육박하는 pch파일을 본 적이 있다(…) 그 땐 코드를 정리하면서 pch 사이즈 변화를 자주 확인해봐야 했는데, 탐색기나 커맨드 창에서 매번 사이즈를 조회하기가 불편했던 기억이 있어서 pch 사이즈 확인하는 걸 만들어봤다.</p><a id="more"></a><p>MSBuild로 단일 cpp 파일을 컴파일하면 이런 메시지가 나오는데,</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\amd64\CL.exe </span><br><span class="line">&#x2F;c </span><br><span class="line">&#x2F;ID:\Dev\uni\External\ </span><br><span class="line">&#x2F;ID:\Dev\uni\Test\ </span><br><span class="line">&#x2F;ID:\Dev\uni\ </span><br><span class="line">&#x2F;Zi </span><br><span class="line">&#x2F;nologo </span><br><span class="line">&#x2F;W4 </span><br><span class="line">&#x2F;WX </span><br><span class="line">&#x2F;sdl </span><br><span class="line">&#x2F;Od </span><br><span class="line">&#x2F;D WIN32 </span><br><span class="line">&#x2F;D _DEBUG </span><br><span class="line">&#x2F;D _CONSOLE </span><br><span class="line">&#x2F;D _LIB </span><br><span class="line">&#x2F;D _UNICODE </span><br><span class="line">&#x2F;D UNICODE </span><br><span class="line">&#x2F;Gm </span><br><span class="line">&#x2F;EHsc </span><br><span class="line">&#x2F;RTC1 </span><br><span class="line">&#x2F;MDd </span><br><span class="line">&#x2F;GS </span><br><span class="line">&#x2F;fp:precise </span><br><span class="line">&#x2F;Zc:wchar_t </span><br><span class="line">&#x2F;Zc:forScope </span><br><span class="line">&#x2F;Yc&quot;stdafx.h&quot; </span><br><span class="line">&#x2F;Fp&quot;x64\Debug\unittest.pch&quot; </span><br><span class="line">&#x2F;Fo&quot;x64\Debug\\&quot; </span><br><span class="line">&#x2F;Fd&quot;x64\Debug\vc120.pdb&quot; </span><br><span class="line">&#x2F;Gd </span><br><span class="line">&#x2F;TP </span><br><span class="line">&#x2F;errorReport:queue </span><br><span class="line">stdafx.cpp</span><br></pre></td></tr></table></figure><p>여기 <code>cl.exe</code>로 들어가는 인자 중에 <code>/Fp&quot;x64\Debug\unittest.pch&quot;</code> 요 부분에 pch 경로가 있음. 그러니까 결국 툴에서 pch사이즈를 구하려면</p><ol><li>프로젝트 리빌드하고</li><li>pch 생성 헤더를 cl.exe로 컴파일하면서 /Fp 스위치를 읽어 경로 파악.</li><li>위에서 새로 생성된 pch파일의 사이즈를 확인.</li></ol><p>… 해주면 된다.</p><h2 id="include-순서-자동-정렬"><a href="#include-순서-자동-정렬" class="headerlink" title="#include 순서 자동 정렬"></a>#include 순서 자동 정렬</h2><p>구글의 C++ 스타일 가이드 문서 중에 <a href="http://jongwook.github.io/google-styleguide/trunk/cppguide.xml#include%EC%9D%98_%EC%9D%B4%EB%A6%84%EA%B3%BC_%EC%88%9C%EC%84%9C">include 의 이름과 순서</a> 항목에 보면 헤더 인클루드에 몇가지 카테고리와 순서를 정해 두었는데, </p><blockquote><p>주된 목적이 dir2/foo2.h에 있는 것들을 구현하거나 테스트하기 위한 dir/foo.cc나 dir/foo_test.cc에서 include를 아래처럼 순서에 따라 배열하라.</p><ol><li>dir2/foo2.h (아래 설명 참조).</li><li>C 시스템 파일</li><li>C++ 시스템 파일</li><li>다른 라이브러리의 .h 파일</li><li>현재 프로젝트의 .h 파일</li></ol></blockquote><p>팀에서 정한 컨벤션도 이 규칙을 그대로 따라야 해서.. 매번 코딩할 때마다 인클루드 순서에 신경쓰기 싫어서 자동화 처리를 작성. 더불어 경로 없이 파일명만 적은 경우나 상대경로를 사용한 인클루드도 지정된 path를 모두 적어주도록 컨버팅하는 처리도 만듦. 만드는 과정이야 대단한 건 없다. sln, vcxproj파일 파싱하는 것은 만들어 두었으니, 그냥 스트링 처리만 좀 더 해주면 금방 만들어진다. 툴로 sorting하고나면 아래처럼 만들어줌.</p><figure class="highlight cpp"><figcaption><span>TestCode.cpp</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;stdafx.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;TestAsset/ProjRoot/TestCode.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// system headers</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// other project&#x27;s headers</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;TestAsset/OuterProject.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;TestAsset/OuterProjectX.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// inner project&#x27;s headers</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;TestAsset/ProjRoot/InterProject.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;TestAsset/ProjRoot/InterProjectA.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;TestAsset/ProjRoot/InterProjectB.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;TestAsset/ProjRoot/InterProjectC.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="epilog"><a href="#epilog" class="headerlink" title="epilog"></a>epilog</h2><p>대충 이정도 돌아가는 툴을 만들어서 개인 pc에 셋팅해둔 jenkins에 물려놓고 사용중. 원래는 필요없는 include찾아주는 기능만 만들려다가 include sorting 기능은 그냥 한 번 추가나 해볼까 싶어 넣은건데, 아주 편하다. 코딩할 땐 순서 상관 없이 상대경로로 대충 넣어놓고 툴을 돌리면 컨벤션에 맞게 예쁘게 수정해준다.</p><p>불필요 인클루드를 찾는 동작은 회사 코드 기준으로 컨텐츠 코드 전체 검색시 50분 정도 걸리는 듯. 이건 매일 새벽에 jenkins가 한 번씩 돌려놓게 해놓고, 매일 아침에 출근해서 확인한다.</p><p>pch사이즈는 baseline 구축을 생각하고 만들어 본건데.. (박일, <a href="http://www.slideshare.net/parkpd/in-ndc2010">사례로 살펴보는 디버깅</a> 참고) baseline을 만들려면 지표들을 좀 더 모아야 하고, db도 붙여야 하니 이건 제대로 만들려면 시간이 필요할 것 같다(..라고 쓰고 ‘더이상 업데이트 되지 않는다’ 라고 읽는다.)</p><h3 id="그리고-C"><a href="#그리고-C" class="headerlink" title="그리고 C#."></a>그리고 C#.</h3><p>C#은 재미있다. 이번에 툴 만들때도 한참 빠져들어서 재미있게 만들었다. Attribute를 달아서 xml 파일을 자동으로 로딩하는 처리를 만들어 보았는데, cpp에서 하기 힘든 깔끔한 이런 가능성들이 마음에 든다. 규모 큰 프로젝트는 안해봐서 모르겠지만 개인적으로 가지고 놀기에는 제일 맘에 듬. 디버깅 하기 좋고 코드 짜기도 좋고.</p><h3 id="Visual-Stuio-Online"><a href="#Visual-Stuio-Online" class="headerlink" title="Visual Stuio Online"></a>Visual Stuio Online</h3><p>코드 관리를 <a href="http://www.visualstudio.com/en-us/products/what-is-visual-studio-online-vs.aspx">visual studio online</a>에서 해봤다. 비공개 코드는 주로 개인 Nas나 bitbucket에 올려놓는데, VS IDE에서 링크가 있길래 한 번 눌러봤다가 한 번 써봄.<br>bitbucket보다 좀 더 많은 기능이 있다. 빌드나 단위테스트를 돌려볼 수 있고(하지만 유료), backlog, splint관리용 보드가 좀 더 디테일하다. 개인 코딩 말고 팀을 꾸려서 작업을 한다면 한 번 제대로 사용해 보는 것을 고려해 볼 순 있겠으나… 왠지 그냥 마음이 안간다. 나같으면 그냥 github 유료 결제해서 쓸 거 같애 ‘ㅅ’)</p><p>이제 이건 고마하고 다음 toy project로 넘어가야지.</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;pch-파일-사이즈&quot;&gt;&lt;a href=&quot;#pch-파일-사이즈&quot; class=&quot;headerlink&quot; title=&quot;pch 파일 사이즈&quot;&gt;&lt;/a&gt;pch 파일 사이즈&lt;/h2&gt;&lt;p&gt;팀에서 만지는 코드에서는, 290Mb에 육박하는 pch파일을 본 적이 있다(…) 그 땐 코드를 정리하면서 pch 사이즈 변화를 자주 확인해봐야 했는데, 탐색기나 커맨드 창에서 매번 사이즈를 조회하기가 불편했던 기억이 있어서 pch 사이즈 확인하는 걸 만들어봤다.&lt;/p&gt;</summary>
    
    
    
    
    <category term="c++" scheme="http://leafbird.github.io/devnote/tags/c/"/>
    
  </entry>
  
  <entry>
    <title>C++ 코드 정리 자동화 - 2. 불필요한 #include 찾기 下</title>
    <link href="http://leafbird.github.io/devnote/2014/09/17/cleanup-cpp-project-2nd/"/>
    <id>http://leafbird.github.io/devnote/2014/09/17/cleanup-cpp-project-2nd/</id>
    <published>2014-09-17T11:30:24.000Z</published>
    <updated>2020-12-31T02:16:27.690Z</updated>
    
    <content type="html"><![CDATA[<p>이전 포스트 ‘<a href="/devnote/2014/09/12/claenup-cpp-project-1st/" title="C++ 코드 정리 자동화 - 1. 불필요한 #include 찾기 上">C++ 코드 정리 자동화 - 1. 불필요한 #include 찾기 上</a>‘ 에서 이어진다.</p><h2 id="지워도-되는-인클루드를-찾아냈다"><a href="#지워도-되는-인클루드를-찾아냈다" class="headerlink" title="지워도 되는 인클루드를 찾아냈다"></a>지워도 되는 인클루드를 찾아냈다</h2><p>개별 파일 하나씩을 컴파일 할 수 있다면 이제 모든 인클루드를 하나씩 삭제하면서 컴파일 가능 여부를 확인해보면 된다. 이 부분은 간단한 file seeking과 string 처리 작업일 뿐이니 굳이 부연 설명은 필요 없다. 카페에서 여유롭게 음악을 들으며 즐겁게 툴을 만들자. 뚝딱뚝딱.</p><p>이정도 하고 나니 이제 vcxproj파일 경로를 주면 해당 프로젝트에 들어있는 소스코드에서 불필요한 인클루드를 색출해 위치정보를 출력해주는 물건이 만들어졌다.</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">작업 대상으로 1개의 프로젝트가 입력 되었습니다.</span><br><span class="line">-------------------------------------------------</span><br><span class="line">Service : 프로젝트 정리.</span><br><span class="line">Service : PCH 생성.</span><br><span class="line">컴파일 : stdafx.cpp ... 성공. 걸린 시간 : 1.04초</span><br><span class="line">Client.cpp의 인클루드를 검사합니다.</span><br><span class="line"> - process #1 Client.cpp (1&#x2F;2) ... X</span><br><span class="line"> - process #1 Client.cpp (2&#x2F;2) ... X</span><br><span class="line">ClientAcceptor.cpp의 인클루드를 검사합니다.</span><br><span class="line"> - process #1 ClientAcceptor.cpp (1&#x2F;2) ... 컴파일 가능!</span><br><span class="line"> - process #1 ClientAcceptor.cpp (2&#x2F;2) ... X</span><br><span class="line">ClientConnection.cpp의 인클루드를 검사합니다.</span><br><span class="line"> - process #1 ClientConnection.cpp (1&#x2F;3) ... X</span><br><span class="line"> - process #1 ClientConnection.cpp (2&#x2F;3) ... X</span><br><span class="line"> - process #1 ClientConnection.cpp (3&#x2F;3) ... X</span><br><span class="line">Start.cpp의 인클루드를 검사합니다.</span><br><span class="line"> - process #1 Start.cpp (1&#x2F;4) ... X</span><br><span class="line"> - process #1 Start.cpp (2&#x2F;4) ... X</span><br><span class="line"> - process #1 Start.cpp (3&#x2F;4) ... X</span><br><span class="line"> - process #1 Start.cpp (4&#x2F;4) ... X</span><br><span class="line">ThreadEntry.cpp의 인클루드를 검사합니다.</span><br><span class="line"> - process #1 ThreadEntry.cpp (1&#x2F;1) ... X</span><br><span class="line">-------------------------------------------------</span><br><span class="line">Project : Service 모두 1개의 인클루드가 불필요한 것으로 의심됩니다.</span><br><span class="line">D:\Dev\uni\World\Service\ClientAcceptor.cpp</span><br><span class="line"> - 2 line : #include &quot;World&#x2F;Service&#x2F;Client.h&quot;</span><br><span class="line"></span><br><span class="line">총 소요 시간 : 13.289 sec</span><br></pre></td></tr></table></figure><a id="more"></a><p>이 정도 만들어서 회사에서 만들고 있는 프로젝트에 조금 돌려 보았는데, <strong>덕분에 꽤나 많은 불필요 인클루드를 색출해 내었다.</strong> 회사 프로젝트는 덩치가 제법 크고, 아직 서비스 중이지 않은 코드여서 용감무쌍한 리팩토링이 자주 일어나기 때문에 관리가 잘 안되는 파일이 제법 있더라. 아무튼 덕을 톡톡히 보았다.</p><h2 id="튜닝-솔루션-단위로-검사할-수-있게-만들자"><a href="#튜닝-솔루션-단위로-검사할-수-있게-만들자" class="headerlink" title="튜닝 : 솔루션 단위로 검사할 수 있게 만들자"></a>튜닝 : 솔루션 단위로 검사할 수 있게 만들자</h2><p>프로젝트 파일 단위로 어느 정도 돌아가니까, 솔루션 파일 단위로도 돌릴수 있게 확장했다. sln 파일을 파싱해서 프로젝트 리스트만 얻어오면 끝나는 일이다. </p><p>하지만 sln 파일은 vcxproj 파일처럼 쉽게 파싱할 수는 없다. 이녀석은 xml 포맷이 아니라, 자체적인 포맷을 가지고 있다. 사실 sln 파일을 파싱해 본 게 이번이 처음이 아닌데, 예전에는 lua를 써서 직접 노가다 파싱을 했더니 별로 재미도 없고 잘 돌아가지도 않고 코딩하는 재미도 별로 없더라. </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"> &#x2F;&#x2F; 솔루션 파일은 이렇게 생겼다. 왜죠...</span><br><span class="line"></span><br><span class="line">Microsoft Visual Studio Solution File, Format Version 12.00</span><br><span class="line"># Visual Studio 2013</span><br><span class="line">VisualStudioVersion &#x3D; 12.0.30723.0</span><br><span class="line">MinimumVisualStudioVersion &#x3D; 10.0.40219.1</span><br><span class="line">... 중략 ...</span><br><span class="line">Project(&quot;&#123;2150E333-8FDC-42A3-9474-1A3956D46DE8&#125;&quot;) &#x3D; &quot;External&quot;, &quot;External&quot;, &quot;&#123;F95C61E3-AF95-4CA9-8837-A203762B2B29&#125;&quot;</span><br><span class="line">EndProject</span><br><span class="line">Project(&quot;&#123;8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942&#125;&quot;) &#x3D; &quot;gtest&quot;, &quot;External\gtest\gtest.vcxproj&quot;, &quot;&#123;C7A81BFC-6E28-4859-A8B5-2FEA80E012B2&#125;&quot;</span><br><span class="line">EndProject</span><br><span class="line">Project(&quot;&#123;2150E333-8FDC-42A3-9474-1A3956D46DE8&#125;&quot;) &#x3D; &quot;Test&quot;, &quot;Test&quot;, &quot;&#123;042F2157-2118-44AA-8BB9-8B5DD01FA3A9&#125;&quot;</span><br><span class="line">EndProject</span><br><span class="line">Project(&quot;&#123;8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942&#125;&quot;) &#x3D; &quot;unittest&quot;, &quot;Test\unittest.vcxproj&quot;, &quot;&#123;24A57754-D332-4575-AEBF-2AFCBC0A7E4B&#125;&quot;</span><br><span class="line">EndProject</span><br><span class="line">... 후략 ...</span><br></pre></td></tr></table></figure><p>C#으로 sln 파일을 파싱해주는 적당히 괜찮은 코드가 인터넷 어딘가에 돌아다닌다. <a href="http://stackoverflow.com/questions/707107/library-for-parsing-visual-studio-solution-files">이곳</a>에 있는 놈을 가져다 붙였다. build configuration 같은 걸 얻어올 순 없지만 프로젝트 리스트 얻는 데에는 충분하다.</p><h2 id="튜닝-느리다-멀티-스레드로-돌리자"><a href="#튜닝-느리다-멀티-스레드로-돌리자" class="headerlink" title="튜닝 : 느리다. 멀티 스레드로 돌리자"></a>튜닝 : 느리다. 멀티 스레드로 돌리자</h2><p>한때는 툴을 만들때 lua도 써보고 python도 써봤지만 요즘은 C#만 쓰게된다. 디버깅 하기도 편하고, <strong>특히 멀티스레딩으로 돌리기가 너무 편하다.</strong> TPL, Concurrent Collection조금 갖다 끄적거리면 금방 병렬처리된다.</p><p>특히나 이런 식으로 병렬성이 좋은 툴은 훨씬 빠르게 돌릴 수 있게 된다. 커맨드 라인 인자로 <code>--multi-thread</code>를 주면 주요 작업을 <code>Parallel.ForEach</code>로 돌리도록 처리했다. 다만 멀티스레드로 돌리면 파일로 남기는 로그가 엉망이 되기 때문에… 단일 스레드로도 돌 수 있도록 남겨둠. </p><p>이번엔 병렬처리할 때 thread-safe한 container가 필요했는데, <a href="http://msdn.microsoft.com/ko-kr/library/system.collections.concurrent.aspx">System.Collections.Concurrent</a>에 가면 queue, stack, dictionary등 종류별로 잔뜩 들어있으니 적당한 놈으로 바로 갖다 쓰면 된다. 편하다 C#. 네이티브 코더는 그냥 웁니다 ㅠㅠ…</p><p>지금 내가 가진 개인 코드 중에는 덩치큰 cpp 프로젝트가 없어서, 조그만 솔루션 하나 시험삼아 돌려봤다.</p><img src="/devnote/images/140917_00.PNG" class="center"><p>87초 걸리던 것이 24초로 빨리짐. 대충 4배 가량 빨라졌다. 내일 회사에서 대빵 큰 프로젝트에 한 번 돌려봐야지. 생각하니 기대된다.</p><h2 id="More-Improvement-불필요한-전방선언-forward-declaration-색출"><a href="#More-Improvement-불필요한-전방선언-forward-declaration-색출" class="headerlink" title="More Improvement : 불필요한 전방선언(forward declaration) 색출."></a>More Improvement : 불필요한 전방선언(forward declaration) 색출.</h2><p>툴을 좀 더 확장할 수 있을거 같다. 클래스와 구조체 전방선언을 써놓고 지우지 않아서 찌꺼기가 된 부분을 이것으로 찾아낼 수 있을 것 같다. 이건 파일을 일일이 컴파일 하지 않아도 되니까 훨씬 빠르게 가능할 듯.</p><p>전방선언 확인 작업도 따지고 보면 단순 string 처리니까… 시간될 때 카페에 가서 찬찬히 코딩하다보면 금방 짤 수 있겠지. cpp 파일을 write하는 작업도 없어서 read만 하면 되기 때문에 아마 병렬성도 훨씬 더 좋을 것이다.  </p>]]></content>
    
    
    <summary type="html">&lt;p&gt;이전 포스트 ‘&lt;a href=&quot;/devnote/2014/09/12/claenup-cpp-project-1st/&quot; title=&quot;C++ 코드 정리 자동화 - 1. 불필요한 #include 찾기 上&quot;&gt;C++ 코드 정리 자동화 - 1. 불필요한 #include 찾기 上&lt;/a&gt;‘ 에서 이어진다.&lt;/p&gt;
&lt;h2 id=&quot;지워도-되는-인클루드를-찾아냈다&quot;&gt;&lt;a href=&quot;#지워도-되는-인클루드를-찾아냈다&quot; class=&quot;headerlink&quot; title=&quot;지워도 되는 인클루드를 찾아냈다&quot;&gt;&lt;/a&gt;지워도 되는 인클루드를 찾아냈다&lt;/h2&gt;&lt;p&gt;개별 파일 하나씩을 컴파일 할 수 있다면 이제 모든 인클루드를 하나씩 삭제하면서 컴파일 가능 여부를 확인해보면 된다. 이 부분은 간단한 file seeking과 string 처리 작업일 뿐이니 굳이 부연 설명은 필요 없다. 카페에서 여유롭게 음악을 들으며 즐겁게 툴을 만들자. 뚝딱뚝딱.&lt;/p&gt;
&lt;p&gt;이정도 하고 나니 이제 vcxproj파일 경로를 주면 해당 프로젝트에 들어있는 소스코드에서 불필요한 인클루드를 색출해 위치정보를 출력해주는 물건이 만들어졌다.&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;작업 대상으로 1개의 프로젝트가 입력 되었습니다.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;-------------------------------------------------&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Service : 프로젝트 정리.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Service : PCH 생성.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;컴파일 : stdafx.cpp ... 성공. 걸린 시간 : 1.04초&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Client.cpp의 인클루드를 검사합니다.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 Client.cpp (1&amp;#x2F;2) ... X&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 Client.cpp (2&amp;#x2F;2) ... X&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;ClientAcceptor.cpp의 인클루드를 검사합니다.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 ClientAcceptor.cpp (1&amp;#x2F;2) ... 컴파일 가능!&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 ClientAcceptor.cpp (2&amp;#x2F;2) ... X&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;ClientConnection.cpp의 인클루드를 검사합니다.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 ClientConnection.cpp (1&amp;#x2F;3) ... X&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 ClientConnection.cpp (2&amp;#x2F;3) ... X&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 ClientConnection.cpp (3&amp;#x2F;3) ... X&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Start.cpp의 인클루드를 검사합니다.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 Start.cpp (1&amp;#x2F;4) ... X&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 Start.cpp (2&amp;#x2F;4) ... X&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 Start.cpp (3&amp;#x2F;4) ... X&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 Start.cpp (4&amp;#x2F;4) ... X&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;ThreadEntry.cpp의 인클루드를 검사합니다.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - process #1 ThreadEntry.cpp (1&amp;#x2F;1) ... X&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;-------------------------------------------------&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Project : Service 모두 1개의 인클루드가 불필요한 것으로 의심됩니다.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;D:\Dev\uni\World\Service\ClientAcceptor.cpp&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; - 2 line : #include &amp;quot;World&amp;#x2F;Service&amp;#x2F;Client.h&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;총 소요 시간 : 13.289 sec&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="c++" scheme="http://leafbird.github.io/devnote/tags/c/"/>
    
  </entry>
  
  <entry>
    <title>C++ 코드 정리 자동화 - 1. 불필요한 #include 찾기 上</title>
    <link href="http://leafbird.github.io/devnote/2014/09/12/claenup-cpp-project-1st/"/>
    <id>http://leafbird.github.io/devnote/2014/09/12/claenup-cpp-project-1st/</id>
    <published>2014-09-12T11:26:14.000Z</published>
    <updated>2020-12-31T02:17:36.329Z</updated>
    
    <content type="html"><![CDATA[<h2 id="지워도-되는-헤더-인클루드를-색출하고-싶다"><a href="#지워도-되는-헤더-인클루드를-색출하고-싶다" class="headerlink" title="지워도 되는 헤더 인클루드를 색출하고 싶다"></a>지워도 되는 헤더 인클루드를 색출하고 싶다</h2><p>매우 느리게 찔끔찔끔 진행하는 토이 프로젝트가 있는데, 오늘 처음으로 무언가 그럴싸한 아웃풋이 나오게 되어 스냅샷을 하는 느낌으로 간단히 포스팅.</p><p>cpp 프로젝트 규모가 점점 커지게 되면 빌드 시간 때문에 많은 고통을 겪는다. 이때문에 <a href="https://www.incredibuild.com/">increadi build</a> 같은 분산 빌드 솔루션도 쓰는거고 <a href="http://www.slideshare.net/devcatpublications/ndc2010-unity-build">unity build</a> 같은 꼼수도 사용하게 되는거다. </p><p>하지만 저런 솔루션들을 사용하기 이전에, 코드를 정리하는 것이 먼저 선행될 필요가 있다. cpp는 특성상 작업하다보면 소스파일에 불필요한 헤더파일의 #include가 남게되고, 이것들이 불필요한 dependency를 만들어내면서 늘어지는 빌드 시간을 무시할 수 없기 때문이다. </p><p>그런데 문제는 그렇게 생긴 불필요 인클루드 구문이 무엇인지를 골라내기가 힘들다는 점이다. 프로젝트 규모가 커질수록 더욱 힘들다. c#같은 경우 불필요 using 구문을 아예 visual studio IDE가 자체적으로 정리해주기까지 하지만, cpp는 색출조차 힘들다 보니 이런 기능을 제공하는 3rd party tool도 없어 보인다. <a href="http://www.wholetomato.com/downloads/spaghettiDownload.asp">Whole Tomato의 Spaghetti</a> 처럼 인클루드간의 관계를 그래프로 보여주는 툴은 몇 번 본 적 있다. 조낸 멋지게 그래프까지 보여주었지만 정작 불필요한 놈이 무언지 콕 짚어주는 녀석은 없음. 참으로 척박한 현실이다. </p><p>그래서 한 번 직접 만들어보기로 했다. </p><a id="more"></a><h2 id="프로젝트-내의-cpp-파일을-개별-컴파일-하기"><a href="#프로젝트-내의-cpp-파일을-개별-컴파일-하기" class="headerlink" title="프로젝트 내의 cpp 파일을 개별 컴파일 하기"></a>프로젝트 내의 cpp 파일을 개별 컴파일 하기</h2><p>일단은 만들려는 툴에서, 입력으로 받은 vc 프로젝트에 포함된 cpp 파일을 개별로 컴파일 할 수 있어야 한다.<br>그렇게 되면 cpp 파일마다 돌면서 코드 안에 있는 #include를 직접 하나씩 제거해보면서 컴파일이 성공하는지를 확인할거다. 그러면 불필요할 것이라 예상되는 #include의 후보를 만들 수 있다. </p><p>무식한 방법이다. cpu를 많이 먹을거고 시간도 오래 걸릴거다. 하지만 저렇게라도 알 수 있다면 새벽에 실행해서 리포트 뽑아놓도록 CI에 물려놓으면 그만이다.</p><p>무식하기도 하지만 또한 불완전한 방법이기도 하다. 위의 동작으로 불필요 #include 후보 리스트를 만들었다고 해도,<br> 헤더파일 끼리의 상호 참조관계, 내부 포함 관계등이 여러 복잡한 상황을 연출하기 때문에<br>후보로 지목된 헤더가 실은 필요한 녀석일 수도 있다. </p><p>하지만 일단은 후보 리스트 색출까지 먼저 진행해 보기로 한다.<br>사실 정말 확실한 불필요 #include가 색출 가능하다면 tool이 아예 코드를 코치는 것까지 자동으로 처리해 줄 수도 있을 것 같지만… 일단 나중에 생각하기로.</p><p>프로젝트에 포함된 cpp 파일의 리스트를 구하는 것은 일도 아니다. vcxproj파일은 xml 형태로 되어 있으므로, <code>/Project/ItemGroup/ClCompile</code> 경로의 xml element를 얻어와 파일 경로를 읽어내면 끝이다. </p><p>그다음은 이 파일을 각각 컴파일 할 수 있어야 하는데… 이것은 생각보다 만만치가 않다. <code>cl.exe</code>를 실행해서 컴파일 하면 되지만, <code>cl.exe</code>의 커맨드라인 옵션으로 들어가야 하는 인자가 엄청나게 많고, 이 옵션을 vcxproj 파일에서 일일이 파싱하고 다시 조합하기란 상당히 귀찮고 짜증나는 작업이다. </p><p>이 귀찮은 작업을 MSBuild에 맡겨버릴 수 있다. MSBuild에 <code>/t:BuildCompile</code> 옵션과 <code>/p:SelectedFiles=xxx</code>을 쓰면 vcxproj를 알아서 파싱해서 cl.exe의 커맨드라인 인자를 직접 만들어준다. </p><p>이렇게 해서 일단 프로젝트 파일에 있는 cpp를 개별 컴파일 하는 것까지 성공.</p><img src="/devnote/images/140912_00.png" class="center"><p>여기까지 하고 나니 cpp 파일당 컴파일 시간까지 덤으로 얻게 됨.<br>앗싸.</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;지워도-되는-헤더-인클루드를-색출하고-싶다&quot;&gt;&lt;a href=&quot;#지워도-되는-헤더-인클루드를-색출하고-싶다&quot; class=&quot;headerlink&quot; title=&quot;지워도 되는 헤더 인클루드를 색출하고 싶다&quot;&gt;&lt;/a&gt;지워도 되는 헤더 인클루드를 색출하고 싶다&lt;/h2&gt;&lt;p&gt;매우 느리게 찔끔찔끔 진행하는 토이 프로젝트가 있는데, 오늘 처음으로 무언가 그럴싸한 아웃풋이 나오게 되어 스냅샷을 하는 느낌으로 간단히 포스팅.&lt;/p&gt;
&lt;p&gt;cpp 프로젝트 규모가 점점 커지게 되면 빌드 시간 때문에 많은 고통을 겪는다. 이때문에 &lt;a href=&quot;https://www.incredibuild.com/&quot;&gt;increadi build&lt;/a&gt; 같은 분산 빌드 솔루션도 쓰는거고 &lt;a href=&quot;http://www.slideshare.net/devcatpublications/ndc2010-unity-build&quot;&gt;unity build&lt;/a&gt; 같은 꼼수도 사용하게 되는거다. &lt;/p&gt;
&lt;p&gt;하지만 저런 솔루션들을 사용하기 이전에, 코드를 정리하는 것이 먼저 선행될 필요가 있다. cpp는 특성상 작업하다보면 소스파일에 불필요한 헤더파일의 #include가 남게되고, 이것들이 불필요한 dependency를 만들어내면서 늘어지는 빌드 시간을 무시할 수 없기 때문이다. &lt;/p&gt;
&lt;p&gt;그런데 문제는 그렇게 생긴 불필요 인클루드 구문이 무엇인지를 골라내기가 힘들다는 점이다. 프로젝트 규모가 커질수록 더욱 힘들다. c#같은 경우 불필요 using 구문을 아예 visual studio IDE가 자체적으로 정리해주기까지 하지만, cpp는 색출조차 힘들다 보니 이런 기능을 제공하는 3rd party tool도 없어 보인다. &lt;a href=&quot;http://www.wholetomato.com/downloads/spaghettiDownload.asp&quot;&gt;Whole Tomato의 Spaghetti&lt;/a&gt; 처럼 인클루드간의 관계를 그래프로 보여주는 툴은 몇 번 본 적 있다. 조낸 멋지게 그래프까지 보여주었지만 정작 불필요한 놈이 무언지 콕 짚어주는 녀석은 없음. 참으로 척박한 현실이다. &lt;/p&gt;
&lt;p&gt;그래서 한 번 직접 만들어보기로 했다. &lt;/p&gt;</summary>
    
    
    
    
    <category term="c++" scheme="http://leafbird.github.io/devnote/tags/c/"/>
    
  </entry>
  
  <entry>
    <title>Yoda Notation</title>
    <link href="http://leafbird.github.io/devnote/2014/08/19/yoda-notation/"/>
    <id>http://leafbird.github.io/devnote/2014/08/19/yoda-notation/</id>
    <published>2014-08-19T08:08:44.000Z</published>
    <updated>2020-12-31T02:18:10.109Z</updated>
    
    <content type="html"><![CDATA[<img src="/devnote/images/yoda1.jpg" class="center"><p>지난번에 <a href="/devnote/2014/07/19/google-c-plus-plus-style-guide/" title="google c++ style guide">google c++ style guide</a> 에 대해서 한참 수다를 떨었는데,<br>요즘에도 비슷한 주제의 책을 읽고 있습니다. 임백준씨가 번역하신 <a href="http://www.yes24.com/24/goods/6692314?scode=032&OzSrank=1">‘읽기 좋은 코드가 좋은 코드다’</a> 인데요,<br> 이것도 가볍게 읽을 수 있는 내용이어서 빌드 시간 중간에 띄엄띄엄 읽고 있어요.  </p><p>이 책을 읽다가 ‘Yoda Notation’이란 표현을 처음 접했습니다. 표현이 재미있어서 블로그에 한 번 적어봅니다. 구글링해보면 <a href="http://en.wikipedia.org/wiki/Yoda_conditions">Yoda Conditions</a> 라고도 부르는 것 같네요. 프로그램 코드 상에서 조건문에 값 비교 구문을 적을 때 변수와 상수의 위치를 바꾸어 적는 것을 말합니다. </p><figure class="highlight cpp"><figcaption><span>May the force be with you.</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> val = <span class="number">20</span>;</span><br><span class="line"><span class="keyword">if</span>(<span class="number">20</span> == val) &#123; <span class="comment">// &lt;- yoda notation here.</span></span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure> <a id="more"></a><p>조건문을 <code>val == 20</code>으로 적는 것이 일반적인 언어 어순과 같아서 읽기가 좋지만<br>프로그래머의 실수로 <code>val = 20</code>과 같이 잘못된 코드가 만들어지고 컴파일 에러 없이 그대로 실행되는 것을 막기 위해서<br>일부러 변수와 상수의 위치를 서로 바꾸는 거죠. </p><p>요다는 영화 스타워즈에서 영문권 사람들도 이해하기 어려울 정도로 꼬인 문법의 말을 사용합니다. 이를 빗대어 위와 같은 조건문 표기 방식을 Yoda Notation이라고 부르는군요. 재미있는 네이밍입니다 :)</p><p>책에서는 요즘의 컴파일러들이 조건절 내에서의 할당에 대해 경고를 내주기 때문에, 요다 표기법은 ‘점차 불필요한 과거의 일이 되어가고 있다’ 고 말합니다.<br>저도 쉽게 읽히지 않는 이상한 순서 때문에 요다 표기법을 안 좋아하는데,<br>책에서도 저랑 같은 생각을 이야기 하고 있어서 반갑네요. 이 뿐만 아니라 이 책은 전반적으로 소스코드의 스타일에 대해 많은 부분 공감가는 방식들을 다수 소개하고 있습니다.</p><p>예전에 함께 작업했던 어떤 프로그래머분이, 제가 올린 코드를 리뷰하고 나서 제가 추가한 코드의 조건절을 모두 요다 표기법으로 바꾸었던 적이 있습니다. 그거 참… 별 거 아닌데 기분이 나쁘더군요. 그 뒤로 요다 표기법이 싫어졌는지도 모르겠습니다. 하지만 어쨌든 이젠 옛날 이야기가 되어가고 있는겁니다. Visual Studio 2012 기준으로 /W4(경고 수준 4) 설정에 /WX(경고를 오류로 처리) 설정을 더하면 <a href="http://msdn.microsoft.com/ko-kr/library/7hw7c1he.aspx">C4706 경고</a>의 발생으로 인해 컴파일 시점에서 코딩 실수를 미리 잡아낼 수 있습니다. </p><p>에, 그러니까 내가 하고 싶었던 말은, <strong>이제 이런 거 필요 없다</strong> 이겁니다 :)</p>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/devnote/images/yoda1.jpg&quot; class=&quot;center&quot;&gt;

&lt;p&gt;지난번에 &lt;a href=&quot;/devnote/2014/07/19/google-c-plus-plus-style-guide/&quot; title=&quot;google c++ style guide&quot;&gt;google c++ style guide&lt;/a&gt; 에 대해서 한참 수다를 떨었는데,&lt;br&gt;요즘에도 비슷한 주제의 책을 읽고 있습니다. 임백준씨가 번역하신 &lt;a href=&quot;http://www.yes24.com/24/goods/6692314?scode=032&amp;OzSrank=1&quot;&gt;‘읽기 좋은 코드가 좋은 코드다’&lt;/a&gt; 인데요,&lt;br&gt; 이것도 가볍게 읽을 수 있는 내용이어서 빌드 시간 중간에 띄엄띄엄 읽고 있어요.  &lt;/p&gt;
&lt;p&gt;이 책을 읽다가 ‘Yoda Notation’이란 표현을 처음 접했습니다. 표현이 재미있어서 블로그에 한 번 적어봅니다. 구글링해보면 &lt;a href=&quot;http://en.wikipedia.org/wiki/Yoda_conditions&quot;&gt;Yoda Conditions&lt;/a&gt; 라고도 부르는 것 같네요. 프로그램 코드 상에서 조건문에 값 비교 구문을 적을 때 변수와 상수의 위치를 바꾸어 적는 것을 말합니다. &lt;/p&gt;
&lt;figure class=&quot;highlight cpp&quot;&gt;&lt;figcaption&gt;&lt;span&gt;May the force be with you.&lt;/span&gt;&lt;/figcaption&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;int&lt;/span&gt; val = &lt;span class=&quot;number&quot;&gt;20&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;20&lt;/span&gt; == val) &amp;#123; &lt;span class=&quot;comment&quot;&gt;// &amp;lt;- yoda notation here.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="coding convention" scheme="http://leafbird.github.io/devnote/tags/coding-convention/"/>
    
    <category term="c++" scheme="http://leafbird.github.io/devnote/tags/c/"/>
    
  </entry>
  
  <entry>
    <title>Octopress Tips on windows</title>
    <link href="http://leafbird.github.io/devnote/2014/07/21/octopress-tips-on-windows/"/>
    <id>http://leafbird.github.io/devnote/2014/07/21/octopress-tips-on-windows/</id>
    <published>2014-07-21T07:44:48.000Z</published>
    <updated>2020-12-31T02:18:26.682Z</updated>
    
    <content type="html"><![CDATA[<p>개인적으로 Octopress를 윈도우에서 사용하도록 구성하면서 도움이 되었던 팁들을 몇가지 정리해 보려고 합니다.<br>앞으로 계속 사용해 가면서 추가적인 팁이 생길 때에도 이 포스팅에 업데이트 할 생각이예요. </p><a id="more"></a><h2 id="윈도우-실행-Windows-R-창에서-블로그-패스로-바로-이동-하기"><a href="#윈도우-실행-Windows-R-창에서-블로그-패스로-바로-이동-하기" class="headerlink" title="윈도우 실행 (Windows + R) 창에서 블로그 패스로 바로 이동 하기"></a>윈도우 실행 (Windows + R) 창에서 블로그 패스로 바로 이동 하기</h2><img src="/devnote/images/140721_00.png" class="center"><p>이거야 뭐… 환경변수에 블로그 경로를 넣어주면 된다. 이렇게 하면 실행 창에 <code>%변수이름%</code>만 입력하면 바로 탐색기를 열 수 있다.<br>환경 변수 설정을 해주는 PowerShell 스크립트를 만들어서 블로그 폴더의 루트에 놔두면 경로를 옮기거나 depot을 새로 받아도 편하게 셋팅할 수 있다. </p><figure class="highlight powershell"><figcaption><span>ps_register_path.ps1</span><a href="https://github.com/leafbird/devnote/blob/master/ps_register_path.ps1">code from github</a></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 현재 스크립트의 실행 경로를 얻는다.</span></span><br><span class="line"><span class="variable">$blog_path</span> = (<span class="built_in">Get-Item</span> <span class="literal">-Path</span> <span class="string">&quot;.\&quot;</span> <span class="literal">-Verbose</span>).FullName</span><br><span class="line"></span><br><span class="line"><span class="comment"># 경로 확인</span></span><br><span class="line"><span class="string">&quot;blog path : <span class="variable">$blog_path</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 실행 경로를 환경변수에 등록(유저 레벨)</span></span><br><span class="line">[<span class="type">Environment</span>]::SetEnvironmentVariable(<span class="string">&quot;blogpath&quot;</span>, <span class="variable">$blog_path</span>, <span class="string">&quot;User&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># output result</span></span><br><span class="line"><span class="string">&quot;Environment Variable update. &#123;0&#125; = &#123;1&#125;&quot;</span> <span class="operator">-f</span> <span class="string">&quot;blogpath&quot;</span>, <span class="variable">$blog_path</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># pause</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;Press any key to continue ...&quot;</span></span><br><span class="line"><span class="variable">$x</span> = <span class="variable">$host</span>.UI.RawUI.ReadKey(<span class="string">&quot;NoEcho,IncludeKeyDown&quot;</span>)</span><br></pre></td></tr></table></figure><h2 id="blogpath-이외에-자주-접근하는-경로는-바로가기를-만든다"><a href="#blogpath-이외에-자주-접근하는-경로는-바로가기를-만든다" class="headerlink" title="%blogpath% 이외에 자주 접근하는 경로는 바로가기를 만든다"></a>%blogpath% 이외에 자주 접근하는 경로는 바로가기를 만든다</h2><img src="/devnote/images/140721_01.PNG" class="center"><p>octopress를 쓰면서 커맨드를 실행하는 주된 경로는 root path다. 이외에도 첨부파일 경로나 글 본문을 저장하는 <code>./source/_posts</code> 등이 흔히 쓰이는데, 이런 경로에 대한 .lnk 파일을 만들어두면 훨씬 편하다.<br>위 스샷처럼 바로가기를 만들어두고 <code>po</code>정도 타이핑하고 엔터하면 <code>./source/_posts</code>로 이동한다.</p><p>나는 탐색기를 주로 이용하고자 이렇게 했지만 cmd창에서 바로가기 하고 싶다면 symbolic link를 만들면 될거다. </p><p>웹페이지 바로가기도 만들어 두면 편하게 이동 가능. (웹 바로가기는 .url 확장자. 브라우저 주소창에서 슥 끌어다 놓으면 생김)</p><h2 id="자주-쓰는-동작들은-스크립트로-자동화한다"><a href="#자주-쓰는-동작들은-스크립트로-자동화한다" class="headerlink" title="자주 쓰는 동작들은 스크립트로 자동화한다"></a>자주 쓰는 동작들은 스크립트로 자동화한다</h2><img src="/devnote/images/140721_02.PNG" class="center"><p><strong>Note : 이 항목이 이 포스팅의 핵심 입니다.</strong></p><p>Octopress를 쓰면서 마음에 드는 점 중에 하나인데, 마음만 먹으면 조작 과정을 내맘대로 스크립팅할 수 있다는 점이다.<br>처음 octopress를 이용하려면 갖가지 명령어들을 일일이 숙지하고 사용하기가 불편한 것이 사실이지만,<br>batch파일과 PowerShell을 통해서 얼마든지 내 입맛대로 자동화 할 수 있다.<br>PowerShell을 한 번 다뤄보고 싶었지만 딱히 기회가 없었는데 이참에 다뤄보게 되어 재미있었다.<br>지금은 몇 개 안되긴 하지만 개인적으로 만들어 사용중인 스크립트들은 <a href="http://github.com/leafbird/devnote/">http://github.com/leafbird/devnote/</a> 에서 확인할 수 있다. </p><p>예제로 한 가지만 살펴보자.</p><h3 id="자동화-예시-새글-작성을-간편하게"><a href="#자동화-예시-새글-작성을-간편하게" class="headerlink" title="자동화 예시 : 새글 작성을 간편하게"></a>자동화 예시 : 새글 작성을 간편하게</h3><p>ocotpress에서 새 글을 적으려면 아래의 순서대로 실행해야 한다. </p><ol><li>blog path로 이동.</li><li>cmd창 오픈</li><li><code>rake new_post[&#39;포스팅 제목&#39;]</code> 명령 실행</li><li><code>./source/_posts</code>로 이동</li><li>자동으로 생성된 .markdown 파일을 찾아서 오픈</li><li>글 작성 시작</li></ol><p>이 절차를 아래처럼 PowerShell로 스크립팅한다.</p><figure class="highlight powershell"><figcaption><span>ps_rake_new_post.ps1</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 환경변수 BLOG_PATH에 설정된 블로그 root 경로로 이동</span></span><br><span class="line"><span class="built_in">cd</span> <span class="variable">$env:blogpath</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#input으로 새 글의 제목을 받는다. </span></span><br><span class="line"><span class="variable">$title</span> = <span class="built_in">Read-Host</span> <span class="string">&#x27;Enter Title&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 실행 : rake new_post[&#x27;제목&#x27;]</span></span><br><span class="line"><span class="variable">$argument</span> = [<span class="built_in">string</span>]::Format(<span class="string">&quot;new_post[&#123;0&#125;]&quot;</span>, <span class="variable">$title</span>)</span><br><span class="line"><span class="variable">$out</span> = rake.bat <span class="variable">$argument</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 생성된 파일의 이름과 경로를 추출한다.</span></span><br><span class="line"><span class="variable">$out</span> = <span class="variable">$out</span>.Replace(<span class="string">&quot;Creating new post: &quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 생성된 파일을 gvim으로 오픈!</span></span><br><span class="line"><span class="variable">$new_file_path</span> = [<span class="type">System.IO.Path</span>]::Combine(<span class="variable">$PSScriptRoot</span>, <span class="variable">$out</span>)</span><br><span class="line">gvim.exe <span class="variable">$new_file_path</span></span><br></pre></td></tr></table></figure><p>커맨드 창에 <code>PowerShell ./ps_rake_new_post.ps1</code> 입력하는 것도 귀찮으니 이것도 batch파일로 만들자.</p><figure class="highlight bat"><figcaption><span>02_ps_rake_new_post.bat</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">@<span class="built_in">echo</span> off</span><br><span class="line">powershell ./ps_rake_new_post.ps1</span><br></pre></td></tr></table></figure><p>이제 이 batch를 실행해서 새 글 제목을 입력하면 에디터까지 자동으로 열린다. </p><h2 id="git-conflict-여러-머신에서-하나의-블로그에-번갈아-포스팅-하는-경우"><a href="#git-conflict-여러-머신에서-하나의-블로그에-번갈아-포스팅-하는-경우" class="headerlink" title="git conflict : 여러 머신에서 하나의 블로그에 번갈아 포스팅 하는 경우"></a>git conflict : 여러 머신에서 하나의 블로그에 번갈아 포스팅 하는 경우</h2><p>git을 사용할 때 불편한 점 중의 하나가 머지(merge)다. 여러 머신을 사용할 경우엔 다른 곳에서 수정했던 사항을 미리 pull 받고 난 후 작업해야 하는데, 이걸 혹시나 깜박 잊고 새 글을 써서 generate했다면 conflict 대 참사가 일어난다. </p><p>blog root경로는 보통의 git repository를 사용하는 것과 유사하기 때문에 큰 문제가 없는데 <code>_deploy</code>폴더가 문제다. 이 폴더는 블로그 엔진이 generate한 블로그 리소스를 배포하기 위해 사용하는데, 실제로는 <code>gh-pages</code> 브랜치의 clone이기 때문이다. 그래서 서로 다른 여러 개의 depot clone을 가지고 블로깅을 할 땐 blog root와 함께 <code>_deploy</code>도 함께 <code>git pull</code> 해주어야 문제가 없다. </p><p>하지만 <code>_deploy</code>폴더는 굳이 동기화까지 받을 필요는 없다. 어차피 블로그 엔진이 배포하는 과정에서 새로 만들기 때문이다.<br>어떻게 활용하든 상관없지만 만약 <code>_deploy</code>폴더가 충돌이나서 html파일을 한땀 한땀 머지해야 하는 상황이 되었다면 주저없이 삭제해 버리고 새로 만들자.</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">cd %blogpath%</span><br><span class="line">rmdir &#x2F;s &#x2F;q _deploy</span><br><span class="line">mkdir _deploy</span><br><span class="line">cd _deploy</span><br><span class="line">git init</span><br><span class="line">git remote add origin https:&#x2F;&#x2F;....</span><br><span class="line">git pull</span><br><span class="line">git check --track origin&#x2F;gh-pages</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;개인적으로 Octopress를 윈도우에서 사용하도록 구성하면서 도움이 되었던 팁들을 몇가지 정리해 보려고 합니다.&lt;br&gt;앞으로 계속 사용해 가면서 추가적인 팁이 생길 때에도 이 포스팅에 업데이트 할 생각이예요. &lt;/p&gt;</summary>
    
    
    
    
    <category term="octopress" scheme="http://leafbird.github.io/devnote/tags/octopress/"/>
    
    <category term="windows" scheme="http://leafbird.github.io/devnote/tags/windows/"/>
    
  </entry>
  
  <entry>
    <title>google c++ style guide</title>
    <link href="http://leafbird.github.io/devnote/2014/07/19/google-c-plus-plus-style-guide/"/>
    <id>http://leafbird.github.io/devnote/2014/07/19/google-c-plus-plus-style-guide/</id>
    <published>2014-07-19T02:12:37.000Z</published>
    <updated>2020-12-31T02:18:46.182Z</updated>
    
    <content type="html"><![CDATA[<p>지금 참여중인 프로젝트에서 얼마전에 코딩 컨벤션을 통일하는 작업이 있었습니다.<br>본격적으로 컨벤션을 통일하고 이제 한 서너달? 정도 지난 것 같네요. </p><p>처음에는 팀원 대다수가 많이 혼란스러워 했지만 이제 어느 정도 시간이 지나고 나니 팀 내 프로그래머 모두가 거의 유사한 스타일의 코드를 작성하게 됐습니다. 이렇게 되니 전보다 코드 가독성이 좋아지고 협업을 할 때 이런 저런 많은 도움이 됩니다. </p><a id="more"></a><p>사실 컨벤션이 통일되면 좋다는 것은 아주 상식적인 말입니다만, 개개인이 선호하는 스타일이 다 다르기 때문에 통일을 하기가 쉽지 않다는 것이 문제입니다. 팀에서도 그동안 몇 차례 시도 했었지만 잘 안되었다가, 이번에서야 겨우 성공했어요. </p><p>이번에 컨벤션의 통일을 성공한 주된 요인 중의 하나는 구글 내부에서 사용하는 컨벤션을 정리해서 공개한 <a href="http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml">구글 C++ 스타일 가이드</a>라고 볼 수 있습니다. 이 문서의 내용을 가져와 몇 가지 사항만 프로젝트에 맞게 조정하여 적용 하였지요. 구글 컨벤션의 코드들은 처음 볼 땐 좀 낮설었지만 적응하고 나니 이젠 괜찮군요. </p><p>팀에 도입하는 과정에서, 팀 내 능력자분들께서 원문을 <a href="http://jongwook.github.io/google-styleguide/trunk/cppguide.xml">한글로 깔끔하게 번역 &amp; 정리</a>해 주셨습니다.<br><a href="https://www.google.co.kr/search?q=google+c+++style+guide+%EB%B2%88%EC%97%AD&oq=gooel+c+++st&aqs=chrome.2.69i57j0l5.5908j0j4&sourceid=chrome&es_sm=93&ie=UTF-8">구글에서 검색해보니</a> 오래전에 번역되다가 말았던 문서들은 몇 개 보이는데 이번에 팀 내에서 번역한 문서는 아직 공유가 널리 안 된 것 같아서 다시 한 번 소개도 할 겸 포스팅 합니다. - 이 글의 목적입니다.</p><p>일단 간단한 샘플을 한 번 볼까요? (제가 구글 컨벤션을 100% 체득(?)한 상황은 아니지만, 대략적으로 분위기만 한 번 둘러보죠.)</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Test</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  Test();</span><br><span class="line">  <span class="keyword">virtual</span> ~Test();</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">int</span> <span class="title">some_value</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">  <span class="function"><span class="keyword">void</span> <span class="title">set_some_value</span><span class="params">(<span class="keyword">const</span> <span class="keyword">int</span> val)</span> </span>&#123;</span><br><span class="line">    some_value_ = val;      <span class="comment">// 간단한 예제이니 inline으로 짜봅니다.</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  <span class="keyword">int</span> some_value_;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>구글 컨벤션의 아주 일부 규칙들이 몇 개 적용된 클래스 선언입니다. </p><ul><li>opening brace을 아랫줄로 내리지 않고 바로 붙여쓰는 것이나, </li><li>들여쓰기는 스페이스 2칸.</li><li>클래스의 멤버변수는 c스타일처럼 <code>_</code>로 연결된 소문자 단어를 사용하고 <code>_</code>를 끝에 붙인다는 점</li><li>getter는 멤버 변수의 이름과 같게, </li><li>setter는 <code>set_변수명()</code>의 규칙을 지닌다. </li><li>클래스 접근 권한 지정자(public, private)는 스페이스 1칸 들여쓰기.</li></ul><p>…정도가 보이네요. (변수명 선언 방식이 헝가리안 표기가 아니예요!)</p><p>구글 컨벤션을 따르는 코드의 예제는 breakpad, protobuf같은 구글의 오픈소스 프로젝트에서 볼 수 있습니다. 구글 코드 이외에도 몇몇 오픈소스들을 보면 구글 컨벤션의 영향을 받은듯한 코드들이 제법 보입니다. 얼마전에 잠시 가지고 놀았던 <a href="https://github.com/msgpack/msgpack-c">msgpack</a>도 어느정도 구글 컨벤션의 영향을 받은 듯한 모양새를 가지고 있더군요. </p><p>구글 컨벤션은 위의 예제에서 보이는 단순한 들여쓰기, 줄바꿈 같은 형식 이야기 말고도 디자인 철학과 관련된 규약들이 많이 있어서, 평소 생각하지 못했던 여러가지 이슈들을 상기시켜줍니다. 문서 내용을 읽는 것만 해도 자신의 코딩 스타일에 대해 많은 점검(?)을 할 수 있어요.</p><p>개인적으로는 팀에서 정해진 룰 때문에 먼저 좀 겪어보게 되었는데 나쁘지 않더군요. 아직까지 마음에 안 드는 조항들도 몇 가지 있지만, 앞으로는 팀 코드가 아닌 개인 작업을 할 때에도 구글 컨벤션을 지켜 코딩해볼 생각입니다. </p><h2 id="다시-한-번-링크"><a href="#다시-한-번-링크" class="headerlink" title="다시 한 번 링크 :"></a>다시 한 번 링크 :</h2><ul><li>원문 : <a href="http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml">http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml</a></li><li>번역문 : <a href="http://jongwook.github.io/google-styleguide/trunk/cppguide.xml">http://jongwook.github.io/google-styleguide/trunk/cppguide.xml</a></li></ul><h2 id="본격-컨벤션-적용을-위한-팁"><a href="#본격-컨벤션-적용을-위한-팁" class="headerlink" title="본격 컨벤션 적용을 위한 팁 :"></a>본격 컨벤션 적용을 위한 팁 :</h2><h3 id="vs2013의-서식-설정-옵션-활용"><a href="#vs2013의-서식-설정-옵션-활용" class="headerlink" title="vs2013의 서식 설정 옵션 활용"></a>vs2013의 서식 설정 옵션 활용</h3><p>visual studio를 이용해 윈도우에서 개발하는 환경일 경우, IDE로 vs2013을 사용하면 많은 도움이 됩니다. 빌드는 예전 버전으로 하더라도 IDE만 vs2013을 사용할 수 있습니다. 2013에는 IDE의 자동 formatting 방식을 직접 설정할 수 있어요.</p><img src="/devnote/images/140719_00.png" class="center"><p>게다가 vs2013 Update 2를 설치하면 설정 가능한 옵션이 좀 더 늘어납니다! Update 3는 아직 안나왔지만 나오면 설정이 더 늘어날지도!!</p><h3 id="포맷팅을-자동으로-고쳐주는-AStyle-활용"><a href="#포맷팅을-자동으로-고쳐주는-AStyle-활용" class="headerlink" title="포맷팅을 자동으로 고쳐주는 AStyle 활용"></a>포맷팅을 자동으로 고쳐주는 AStyle 활용</h3><p><a href="http://astyle.sourceforge.net/">AStyle</a>이란 멋진 프로그램이 있어요. 포맷팅을 자동으로 고쳐주는 프로그램인데, 오픈소스로 되어있어 직접 수정 &amp; 활용할 수 있습니다. 이걸 perforce 클라이언트인 p4v.exe pending changelist 창에서 일괄 적용하게 설정할 수도 있고, vs 플러그인으로 만들어서 코딩 중에도 실행해 볼 수 있어요. 포맷팅을 알아서 고쳐주니까 코딩중에는 들여쓰기가 어떻고 빈 칸이 어떤지 일일이 신경 쓸 필요 없으니 아주 편리합니다 -_-)b 강추예요.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;지금 참여중인 프로젝트에서 얼마전에 코딩 컨벤션을 통일하는 작업이 있었습니다.&lt;br&gt;본격적으로 컨벤션을 통일하고 이제 한 서너달? 정도 지난 것 같네요. &lt;/p&gt;
&lt;p&gt;처음에는 팀원 대다수가 많이 혼란스러워 했지만 이제 어느 정도 시간이 지나고 나니 팀 내 프로그래머 모두가 거의 유사한 스타일의 코드를 작성하게 됐습니다. 이렇게 되니 전보다 코드 가독성이 좋아지고 협업을 할 때 이런 저런 많은 도움이 됩니다. &lt;/p&gt;</summary>
    
    
    
    
    <category term="coding convention" scheme="http://leafbird.github.io/devnote/tags/coding-convention/"/>
    
    <category term="c++" scheme="http://leafbird.github.io/devnote/tags/c/"/>
    
  </entry>
  
  <entry>
    <title>벤츠타는 프로그래머</title>
    <link href="http://leafbird.github.io/devnote/2014/07/16/the-benz-programmer/"/>
    <id>http://leafbird.github.io/devnote/2014/07/16/the-benz-programmer/</id>
    <published>2014-07-16T11:20:38.000Z</published>
    <updated>2020-12-31T02:19:11.867Z</updated>
    
    <content type="html"><![CDATA[<img src="/devnote/images/140708_00.png" class="center" width="300"><p>요 며칠간 이 책을 읽었습니다. 회사 도서관에 갔다가 제목이 끌려서 한 번 읽어봤어요.<br>누가 정한건지 모르겠지만 책 제목 참 멋지게 지었습니다. 주변에서 제가 이 책 읽는 것 보면 모두들 제목에 대해 관심을 보이더군요 ㅎ</p><p>진로를 고민중인 학생이나 일을 시작한지 얼마 되지 않는 신입 개발자들을 주 대상으로 삼은 책입니다. 다소 주관적이긴 하지만 선배 개발자 입장에서 들려주는 이런 저런 이야기들이 적혀 있어요.<br>저자는 자기관리를 잘 하는 분이신 듯 합니다. 구체적인 개인 목표를 세우고 달성을 위해 노력하는 점이라든지, 꾸준한 자기계발에 관심을 두는 점 같은 좋은 습관을 많이 만들어두신 것 같네요. </p><a id="more"></a><p>저는 작업 도중에 빌드 걸어놓고 잠깐씩 기다리는 동안에 주로 읽었습니다.<br>빌드시간에 조금 난이도가 있는 기술서적을 읽을 때는, 내용을 좀 따라가려다 보면 빌드가 끝나서 흐름이 끊기고, 이게 계속 반복되다보니 책에 제대로 집중할 수가 없었습니다.<br>그래서 빌드시간에 책읽는 것은 거의 포기를 하고 있었는데, 이런 책은 부담없이 읽을 수 있어서 빌드 중에 읽어도 괜찮더군요.<br>그래서 앞으로는 빌드하는 중에 이런 가벼운 책들 읽으면 되겠구나 하는 생각을 해봤습니다.</p><p>저는 책을 읽다가 조금 엉뚱한 구절에 눈길이 확 쏠렸는데, </p><blockquote><p>…결혼하고 아이들이 생긴 이후에는 집에서 어떤 일을 한다는 게 쉽지 않았다. 그래서 집중해서 집필하거나 공모전 참가 준비를 할 때는 주말마다 본가로 달려갔다. 본가에서는 식사 시간 이외에는 누구도 방해하는 사람이 없어서 원하는 일에 집중할 수 있었기 때문이다. </p></blockquote><p>이 부분입니다. 저도 아이가 생긴 후에는 개인 시간을 내기가 쉽지 않아서 적잖이 고민을 하고 있는데, 주말마다 본가에 가서 혼자만의 시간을 가질 수 있었다는 저 이야기는 정말 부럽기 짝이 없네요 ㅜㅠ…</p><img src="/devnote/images/140716_00.jpg" class="center"><p>저는 집에 아이가 생기고 한동안은 개인 시간은 아예 포기하고 지냈습니다. 주말마다 즐겁게 참여하던 스터디도 못 나가게 되었고, 집에서 컴퓨터 앞에 앉아 코딩을 하는 것은 거의 꿈도 꾸질 못했어요. </p><p>이제는 아이도 어느 정도 자랐고 하니 조금씩 개인 시간을 확보하고 다시 자기관리에 신경을 좀 써야겠다고 다짐했습니다. 이런 다짐을 한 것에는 최근에 이 책을 읽었던 것도 어느 정도 영향이 있었겠지요. 벤츠 타는 것도 난 바라지 않아요. 그냥 원하는 만큼 양껏 코딩하고 놀 수 있게만 됐으면 좋겠네요 ;ㅁ;)…</p>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/devnote/images/140708_00.png&quot; class=&quot;center&quot; width=&quot;300&quot;&gt;

&lt;p&gt;요 며칠간 이 책을 읽었습니다. 회사 도서관에 갔다가 제목이 끌려서 한 번 읽어봤어요.&lt;br&gt;누가 정한건지 모르겠지만 책 제목 참 멋지게 지었습니다. 주변에서 제가 이 책 읽는 것 보면 모두들 제목에 대해 관심을 보이더군요 ㅎ&lt;/p&gt;
&lt;p&gt;진로를 고민중인 학생이나 일을 시작한지 얼마 되지 않는 신입 개발자들을 주 대상으로 삼은 책입니다. 다소 주관적이긴 하지만 선배 개발자 입장에서 들려주는 이런 저런 이야기들이 적혀 있어요.&lt;br&gt;저자는 자기관리를 잘 하는 분이신 듯 합니다. 구체적인 개인 목표를 세우고 달성을 위해 노력하는 점이라든지, 꾸준한 자기계발에 관심을 두는 점 같은 좋은 습관을 많이 만들어두신 것 같네요. &lt;/p&gt;</summary>
    
    
    
    
    <category term="book" scheme="http://leafbird.github.io/devnote/tags/book/"/>
    
  </entry>
  
  <entry>
    <title>move to octopress!</title>
    <link href="http://leafbird.github.io/devnote/2014/07/14/move-to-octopress/"/>
    <id>http://leafbird.github.io/devnote/2014/07/14/move-to-octopress/</id>
    <published>2014-07-13T15:24:24.000Z</published>
    <updated>2020-12-31T02:19:33.464Z</updated>
    
    <content type="html"><![CDATA[<p>기존에 티스토리에서 운영 중이던 <a href="http://devnote.tistory.com/">프로그래밍 관련 블로그(devnote.tistory.com)</a>를 Octopress로 이사합니다. 사실 운영이라고 말하기도 뭣할 만큼 오랫동안 방치되어 있었는데, 다시금 분위기를 쇄신하고자 환경을 바꿔볼까 합니다. </p><a id="more"></a><p>기존 블로그를 feedburner 주소로 구독중이었다면 새로운 블로그로 자동으로 넘어갑니다. 하지만 티스토리 기본 rss 주소를 사용중이었다면, 이참에 feed-burner로 갈아타 주세요 ‘ㅁ’)/</p><p>feed burder address : <a href="http://feeds.feedburner.com/florist_devnote">http://feeds.feedburner.com/florist_devnote</a></p><p>Octopress는 기존과는 다른 형태의 static engine이라서 호감이 갑니다. 맘에 드는 점을 몇가지만 꼽아보면</p><ul><li>vim으로 글을 적을 수 있다는 것</li><li>본문 글이 로컬에 text(markdown)파일로 남는 다는 점</li><li>블로그 주소에 github.io를 쓴다는 것</li><li>기본적으로 큰 글씨를 사용하는 시원한 테마들.</li></ul><p>… 등입니다. markdown으로 글을 적게 된다면 <a href="http://pad.haroopress.com/user.html">하루패드</a>를 사용해야 겠다고 생각했었는데, vim으로 적는게 더 느낌이 좋네요 :) vim을 무척 잘 쓰는 편은 못되지만, octopress덕에 git이나 vim을 자주 접하게 되면 좀 더 익숙해 지는 계기가 될테니 그런 점도 마음에 듭니다. </p><p>그 외 나머지 추가 기능이나 설정 같은 건 아직 제대로 모르는 상태이지만, 하루 이틀 미루다보면 너무 늘어져 버릴 것 같아서 우선 이사 공표(?)부터 내지릅니다. </p><p>집에 애가 생기고 난 후 부터는 개인 시간이 많이 줄어들면서 블로그에도 소홀해지게 되었는데, 앞으로는 굳이 테크니컬한 내용의 글이 아니더라도 개발에 관련된 소소한 글들도 올릴 생각입니다. 이를테면 기계식 키보드에 대한 이야기라던가… 하는 것도요. (글쓰기 연습을 위해서라도 무엇이든 꾸준히 글을 좀 적어야 겠다는 개인적인 욕망(?) 때문입니다.)</p><p>앞으로 여러가지 글들 종종 올리겠습니다.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;기존에 티스토리에서 운영 중이던 &lt;a href=&quot;http://devnote.tistory.com/&quot;&gt;프로그래밍 관련 블로그(devnote.tistory.com)&lt;/a&gt;를 Octopress로 이사합니다. 사실 운영이라고 말하기도 뭣할 만큼 오랫동안 방치되어 있었는데, 다시금 분위기를 쇄신하고자 환경을 바꿔볼까 합니다. &lt;/p&gt;</summary>
    
    
    
    
    <category term="octopress" scheme="http://leafbird.github.io/devnote/tags/octopress/"/>
    
  </entry>
  
  <entry>
    <title>octopress on windows</title>
    <link href="http://leafbird.github.io/devnote/2013/12/30/octopress-on-windows/"/>
    <id>http://leafbird.github.io/devnote/2013/12/30/octopress-on-windows/</id>
    <published>2013-12-30T14:06:15.000Z</published>
    <updated>2020-12-31T02:19:45.237Z</updated>
    
    <content type="html"><![CDATA[<img src="/devnote/images/octopress.jpeg" class="center"><p>octopress도 대게는 ruby가 기본 설치된 mac에서 많이들 사용하는 듯 하다. 검색해보면 대부분 OS X를 기준으로 한 셋팅법이다. 윈도우에서 사용하는 것도 많이 어렵진 않지만 <strong>한글 인코딩 때문에 많이 헤맸음 ㅜㅠ</strong>…</p><p>일단 기본적으로 아래 두 개의 글을 참고해 설치했는데,</p><ol><li><a href="http://stb.techelex.com/setup-octopress-on-windows7/">http://stb.techelex.com/setup-octopress-on-windows7/</a></li><li><a href="http://chulhankim.github.io/blog/2013/07/31/octopress-and-github.html">http://chulhankim.github.io/blog/2013/07/31/octopress-and-github.html</a></li></ol><p>ruby는 생소한 언어이기도 하고 링크가 사라지면 다시 헤맬수도 있으니 간략하게 다시 정리.</p><a id="more"></a><h1 id="Ruby-설치"><a href="#Ruby-설치" class="headerlink" title="Ruby 설치"></a>Ruby 설치</h1><p>일단 윈도우에는 Ruby가 없기 때문에 먼저 설치를 해야 한다.<br><a href="http://rubyinstaller.org/downloads/">다운로드 페이지</a>에서 Ruby와 DevKit을 다운받는다.<br>내가 사용한 버전은 Ruby 2.0.0-p353 (x64)와 DevKit-mingw64-64-4.7.2-20130224-1432-sfx.exe</p><p>DevKit을 사용하기 전에 install 과정이 필요하다. 이 단계를 실행하기 전에 ruby의 bin 폴더가 path에 잡혀 있는 것이 좋다. 그러면 DevKit 초기화 과정에서 ruby의 경로를 알아서 감지하므로, config.yml을 수정할 필요가 없다. </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> C:/RubyDevKit</span><br><span class="line">ruby dk.rb init <span class="comment"># 이 때 config.yml이 생김. 이 전에 ruby bin을 path에 넣자.</span></span><br><span class="line">ruby dk.rb install</span><br></pre></td></tr></table></figure><h1 id="python-설치"><a href="#python-설치" class="headerlink" title="python 설치"></a>python 설치</h1><p>python은 없어도 상관없다. 하지만 syntax highlighting을 하려거든 python이 필요하다. 이것도 OS X는 기본 설치되어 있어서 크게 이슈가 없는듯. 나는 한참 써보다가 알았는데, 나중에 python을 설치하면 <a href="https://github.com/imathis/octopress/issues/262">뭔가 더 해주어야 하는 것 같아 귀찮다</a>. 그냥 처음부터 python을 설치해놓고 path에 python이 포함되도록 해두는게 좋겠다. </p><h1 id="Octopress-받기"><a href="#Octopress-받기" class="headerlink" title="Octopress 받기"></a>Octopress 받기</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> c:/github</span><br><span class="line">git <span class="built_in">clone</span> git://github.com/imathis/octopress.git octopress </span><br><span class="line"><span class="built_in">cd</span> octopress      <span class="comment">#replace octopress with username.github.com  </span></span><br><span class="line">ruby --version  <span class="comment"># Should report Ruby 1.9.3</span></span><br></pre></td></tr></table></figure><p>ruby 패키지들 (dependencies) 설치:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> c:/github/octopress       <span class="comment">#replace octopress with username.github.com</span></span><br><span class="line">gem install bundler</span><br><span class="line">bundle install</span><br></pre></td></tr></table></figure><p>octporess의 기본 테마 설치:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ rake install</span><br></pre></td></tr></table></figure><p>이부분에서 말을 안들을 수가 있는데, 뭔가 모듈의 버전이 맞지 않는 문제다.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">D:\Blog\DevNote&gt;rake install</span><br><span class="line">rake aborted!</span><br><span class="line">You have already activated rake 0.9.6, but your Gemfile requires rake 0.9.2.2. P</span><br><span class="line">repending `bundle <span class="built_in">exec</span>` to your <span class="built_in">command</span> may solve this.</span><br><span class="line">D:/Blog/DevNote/Rakefile:2:<span class="keyword">in</span> `&lt;top (required)&gt;<span class="string">&#x27;</span></span><br><span class="line"><span class="string">(See full trace by running task with --trace)</span></span><br></pre></td></tr></table></figure><p>이 때 <code>bundle update rake</code> 해주면 해결. <a href="http://stackoverflow.com/questions/6080040/you-have-already-activated-rake-0-9-0-but-your-gemfile-requires-rake-0-8-7">다음 글을 참고했다.</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">D:\Blog\DevNote&gt;bundle update rake</span><br><span class="line">Fetching gem metadata from https://rubygems.org/.......</span><br><span class="line">Fetching additional metadata from https://rubygems.org/..</span><br><span class="line">Resolving dependencies...</span><br><span class="line">Using rake (0.9.6)</span><br><span class="line">...(중략)...</span><br><span class="line">Your bundle is updated!</span><br></pre></td></tr></table></figure><h1 id="Octopress를-Github-Pages용으로-설정"><a href="#Octopress를-Github-Pages용으로-설정" class="headerlink" title="Octopress를 Github Pages용으로 설정"></a>Octopress를 Github Pages용으로 설정</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ rake setup_github_pages</span><br></pre></td></tr></table></figure><p>Github Pages는 계정 페이지와 프로젝트 페이지로 나뉜다.<br>각각의 경우에 따라 수동설정을 해주어야 하는데(이 부분은 두 번째 글에 잘 설명되어 있다.), 프로젝트 페이지의 경우가 조금 더 손댈 곳이 많다.</p><ul><li>계정 페이지 설정인 경우</li></ul><p><code>_config.yml</code>에서 url, title, subtitle, author 정도만 수정해주면 된다.</p><ul><li>프로젝트 페이지 설정의 경우</li></ul><p>먼저 <code>git remote</code> 추가.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ git remote add origin `https://github.com/username/projectname.git</span><br><span class="line">$ git config branch.master.remote origin</span><br></pre></td></tr></table></figure><p><code>_config.yml, config.rb, Rakefile</code> 을 열어서 <code>/github</code>라고 된 부분을 repository 명으로 수정.</p><h1 id="한글-인코딩-문제-해결"><a href="#한글-인코딩-문제-해결" class="headerlink" title="한글 인코딩 문제 해결"></a>한글 인코딩 문제 해결</h1><p>이제 부푼 꿈을 안고 첫 포스팅을 만들어보면 잘 동작한다.<br>하지만.. 한글을 사용하면 다시 인코딩 관련 에러를 만나게 된다.<br><strong>여기서 엄청난 시간을 소모</strong>했는데, octopress 안에서 해결을 보려고 하니 힘들다. ruby는 한 번도 안써봐서 코드 보기도 힘들고 ㅡㅠ…<br><a href="http://www.qstata.com/blog/2013/06/20/rake-generate-utf-8-errors-on-windows/">검색해보면</a> jekyll 코드 일부를 직접 수정하는 방법도 있는데,<br>그것보다 cmd창의 코드 페이지를 변경해주면 간단하게 해결된다. </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chcp 65001 <span class="comment"># 다시 되돌리려면 chcp 949</span></span><br></pre></td></tr></table></figure><p><code>rake generate</code>를 하거나 <code>rake preview</code>를 하기 전에, 코드페이지를 항상 변경해주고 실행한다. batch파일을 미리 만들어두니 편하다.</p><p>markdown 문법은 <a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet">검색하면</a> 어렵지 않게 찾을 수 있다. </p><h1 id="블로그-내부-링크-만들기"><a href="#블로그-내부-링크-만들기" class="headerlink" title="블로그 내부 링크 만들기"></a>블로그 내부 링크 만들기</h1><p>기본으로 제공되는 기능이 없는듯? 플러그인 폴더에 아래 파일 하나 넣어주어야 한다.</p><ul><li><a href="https://github.com/michael-groble/jekyll/blob/master/lib/jekyll/tags/post_url.rb">https://github.com/michael-groble/jekyll/blob/master/lib/jekyll/tags/post_url.rb</a></li></ul><p><a href="http://kqueue.org/blog/2012/01/05/hello-world/#internal-post-linking">여기</a> 에서 참고했다. 아래 문법을 사용한다.</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[link to this post](&#123;% post_url 2012-01-05-hello-world %&#125;)</span><br></pre></td></tr></table></figure><p>eof.</p>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/devnote/images/octopress.jpeg&quot; class=&quot;center&quot;&gt;

&lt;p&gt;octopress도 대게는 ruby가 기본 설치된 mac에서 많이들 사용하는 듯 하다. 검색해보면 대부분 OS X를 기준으로 한 셋팅법이다. 윈도우에서 사용하는 것도 많이 어렵진 않지만 &lt;strong&gt;한글 인코딩 때문에 많이 헤맸음 ㅜㅠ&lt;/strong&gt;…&lt;/p&gt;
&lt;p&gt;일단 기본적으로 아래 두 개의 글을 참고해 설치했는데,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;http://stb.techelex.com/setup-octopress-on-windows7/&quot;&gt;http://stb.techelex.com/setup-octopress-on-windows7/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://chulhankim.github.io/blog/2013/07/31/octopress-and-github.html&quot;&gt;http://chulhankim.github.io/blog/2013/07/31/octopress-and-github.html&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ruby는 생소한 언어이기도 하고 링크가 사라지면 다시 헤맬수도 있으니 간략하게 다시 정리.&lt;/p&gt;</summary>
    
    
    
    
    <category term="octopress" scheme="http://leafbird.github.io/devnote/tags/octopress/"/>
    
    <category term="windows" scheme="http://leafbird.github.io/devnote/tags/windows/"/>
    
    <category term="encoding" scheme="http://leafbird.github.io/devnote/tags/encoding/"/>
    
  </entry>
  
</feed>
