<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>oak_cassia.log</title>
        <link>https://velog.io/</link>
        <description>https://velog.io/@oak_cassia/A-Game-Developers-Vision</description>
        <lastBuildDate>Fri, 05 Dec 2025 14:49:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>oak_cassia.log</title>
            <url>https://velog.velcdn.com/images/oak_cassia/profile/d5f714a7-f425-4718-8a27-c41251cb90eb/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. oak_cassia.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/oak_cassia" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[AI Agent에 대한 생각과 학습]]></title>
            <link>https://velog.io/@oak_cassia/AI-Agent%EC%97%90-%EB%8C%80%ED%95%9C-%EC%83%9D%EA%B0%81%EA%B3%BC-%ED%95%99%EC%8A%B5</link>
            <guid>https://velog.io/@oak_cassia/AI-Agent%EC%97%90-%EB%8C%80%ED%95%9C-%EC%83%9D%EA%B0%81%EA%B3%BC-%ED%95%99%EC%8A%B5</guid>
            <pubDate>Fri, 05 Dec 2025 14:49:35 GMT</pubDate>
            <description><![CDATA[<p><a href="https://medium.com/smilegate-dev-community/%ED%85%8C%ED%81%AC%ED%95%98%EC%9D%B4%ED%82%B9-3%EA%B8%B0-ai-agent%EB%9E%80-e2382af39432">https://medium.com/smilegate-dev-community/테크하이킹-3기-ai-agent란-e2382af39432</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[쉽게 알려주는 제네릭 변성]]></title>
            <link>https://velog.io/@oak_cassia/%EC%89%BD%EA%B2%8C-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B3%80%EC%84%B1</link>
            <guid>https://velog.io/@oak_cassia/%EC%89%BD%EA%B2%8C-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B3%80%EC%84%B1</guid>
            <pubDate>Thu, 28 Aug 2025 15:51:56 GMT</pubDate>
            <description><![CDATA[<h3 id="개요">개요</h3>
<p>주석에 나름 쉽게 적어보았다.</p>
<p>제네릭 변성은 인터페이스·델리게이트에서 LSP의 대체 가능성을 컴파일 타임에 타입 안전을 보장해, 필요할 때 캐스팅 없이 더 일반/구체적인 타입을 그대로 재사용할 수 있게 한다.
덕분에 LSP의 핵심인 “상위 타입에 하위 타입을 넣어도 정상 동작한다”는 점을 실제 코드에서 자연스럽게 활용할 수 있고, 궁극적으로는 “부품을 바꿔 끼워도 안전하게 동작하는 모듈식 설계”를 이루는 객체지향의 장점을 활용할 수 있다.</p>
<hr>
<h3 id="제네릭-변성generic-variance">제네릭 변성(Generic Variance)</h3>
<ul>
<li>제네릭 타입 매개변수가 상속 관계를 반영해 공변/반공변 변환을 허용하는 기능
이는 <strong>인터페이스와 델리게이트에만 적용</strong>되며, 클래스와 구조체는 항상 <strong>무공변(invariant)</strong>이다.</li>
</ul>
<h3 id="공변성covariance-out">공변성(covariance, <code>out</code>)</h3>
<ul>
<li><p>반환 타입으로 사용할 때, <code>IEnumerable&lt;Derived&gt;</code> → <code>IEnumerable&lt;Base&gt;</code> 변환 허용</p>
</li>
<li><p>타입 매개변수가 <strong>출력/생산</strong> 위치에서 사용될 때 적용</p>
</li>
<li><p>출력/생산 관점에서는 더 구체적인 타입을 더 일반적인 타입으로 취급 가능</p>
<ul>
<li>예: <code>string → object</code>, <code>int → long</code></li>
</ul>
</li>
<li><p>공변 변환은 <strong>A(원래 타입) → B(변환 타입)</strong> 변환이 <strong>항등 변환</strong>이거나 <strong>암시적 참조 변환</strong>일 때만 허용</p>
</li>
</ul>
<blockquote>
<p>“나는 string/int를 만들어요. 그렇다면 object/long을 만든다고 해도 되겠죠. 내가 만든 걸 가져다 쓰세요.”</p>
</blockquote>
<h3 id="반공변성contravariance-in">반공변성(contravariance, <code>in</code>)</h3>
<ul>
<li><p>매개변수 타입으로 사용할 때, <code>Action&lt;Base&gt;</code> → <code>Action&lt;Derived&gt;</code> 변환 허용</p>
</li>
<li><p>타입 매개변수가 <strong>입력/소비</strong> 위치에서 사용될 때 적용</p>
</li>
<li><p>입력/소비 관점에서는 더 일반적인 타입을 더 구체적인 타입으로 취급 가능</p>
<ul>
<li>예: <code>object → string</code>, <code>long → int</code></li>
</ul>
</li>
<li><p>반공변 변환은 <strong>B(원래 타입) → A(변환 타입)</strong> 변환이 <strong>항등 변환</strong>이거나 <strong>암시적 참조 변환</strong>일 때만 허용</p>
</li>
</ul>
<blockquote>
<p>“나는 object/long을 소비할 수 있어요. 그렇다면 string/int를 소비한다고 해도 되죠. string/int를 줘보세요.”</p>
</blockquote>
<h3 id="무공변성invariant">무공변성(invariant)</h3>
<ul>
<li>기본 제네릭은 상속 변환을 허용 안함</li>
<li>타입 매개변수가 <strong>입력과 출력 모두</strong>로 사용될 때 적용</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[LLM으로 실제 문제 해결하기: 예측 불가능성]]></title>
            <link>https://velog.io/@oak_cassia/LLM%EC%9C%BC%EB%A1%9C-%EC%8B%A4%EC%A0%9C-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-%EC%98%88%EC%B8%A1-%EB%B6%88%EA%B0%80%EB%8A%A5%EC%84%B1</link>
            <guid>https://velog.io/@oak_cassia/LLM%EC%9C%BC%EB%A1%9C-%EC%8B%A4%EC%A0%9C-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-%EC%98%88%EC%B8%A1-%EB%B6%88%EA%B0%80%EB%8A%A5%EC%84%B1</guid>
            <pubDate>Sun, 24 Aug 2025 15:10:11 GMT</pubDate>
            <description><![CDATA[<h3 id="개요">개요</h3>
<p>LLM을 활용해 실제 문제를 해결하며 얻은 인사이트를 공유한다. 바로 소개하면 <strong>예측 가능성을 어떻게 확보하는가</strong>에 대한 과정이다.</p>
<h3 id="바이브-코딩의-결과물을-배포할-수-없는-이유">바이브 코딩의 결과물을 배포할 수 없는 이유</h3>
<p>복잡도가 높은 상용 서비스에서는 바이브 코딩 결과물을 그대로 배포할 수 없다. LLM이 만든 코드는 출력이 매번 달라질 수 있거나(또는 일관되게 잘못될 수 있어) 매번 어떤 논리로 동작하는지 완전히 파악해야 한다. 안정적인 서비스는 코드의 과정과 결과를 모두 예측할 수 있어야 한다. 이를 위해서는 프롬프트와 컨텍스트를 구체적이고 모호하지 않으며, 모순되지 않게 구성해, 과하지도 부족하지도 않은 맥락을 제공해야 한다. 결국 배포 가능한 코드는 <strong>결정적(deterministic)</strong>이어야 한다.</p>
<h3 id="deterministic">Deterministic</h3>
<p>본질적으로 예측 불가능한 LLM로부터 어떻게 결정적인 결과를 얻을 것인가? 단순히 프롬프트와 컨텍스트를 조절하는 것을 넘어서는 전략이 필요하다. 퇴근 후 코딩 에이전트를 활용하면서, 그리고 회사에서 코드 생성 자동화를 연구하며 내린 두 가지 방법이 있다.</p>
<ol>
<li>작업을 작게 분할한다.</li>
<li>결정적인 도구와 연계한다.</li>
</ol>
<h3 id="작업-분할하기">작업 분할하기</h3>
<p>작업이 커지고 컴포넌트와 클래스 간 관계가 얽힐수록 <code>엉망이지만 동작하는 코드</code>나 <code>일관되게 잘못된 코드</code>가 나왔다. 반면 작은 기능 단위로 세분화하고, 참조할 파일, 사용해야 할 라이브러리, 코딩 스타일, 설계 등의 제약을 명시하면 예측 가능한 결과가 나올 확률이 높았다. 그리고 결과물의 흐름을 확인하기 쉽다.
이 과정에는 상당한 리소스가 든다. 상세 명세를 전달하고, 결과가 마음에 들지 않으면 추가 지시로 보정하거나 직접 수정하는 조정이 필요하다. <code>&quot;숙련된 개발자는 에이전트를 쓰면 오히려 느릴 수 있다&quot;</code>는 말은, 결국 이 예측 가능성 확보 비용 때문이라 본다. 그래도 목표까지의 과정이 짧다면 에이전트만으로도 충분히 예측 가능한 코드를 만들 수 있었고, 컨텍스트를 조절하면 작업 범위가 커져도 어느 정도 쓸만한 결과물을 얻을 수 있었다.</p>
<h3 id="결정적인-도구와-연계하기">결정적인 도구와 연계하기</h3>
<p>LLM으로 생성한 코드를 활용하려고 claude 3.7로 시도했다. 코드 명세서를 만든 뒤, 프롬프트를 구체적으로 작성하고, 컨텍스트를 조절했지만 빈 파일에서 코드를 생성하는 것은 매번 달라졌다. 실제 서비스에 사용할 수 도 없었다.
개선하기 위해 시도한 방법들은 다음과 같다.</p>
<ol>
<li>LLM에 제공되는 컨텍스트를 조절하기 위해 코드 명세를 기능별로 분리했다.</li>
<li>제공된 템플릿을 보고 파일을 생성 -&gt; LLM이 템플릿 파일을 수정</li>
<li>템플릿에 LLM이 작성 및 수정할 부분을 주석으로 명시<pre><code class="language-c#">// Agent_Start
// 기능
// Agent_Parmeter (param) 가변 요소
// if (param) ...
// AgentEnd</code></pre>
위 방법들은 효과가 있었다. 이제 결과물이 동일하게 나오는 빈도가 높아졌으며, 해당 코드를 기반으로 작업을 시작해도 됐다.
하지만 주석을 보니 학부 때 사용했던 Jinja 템플릿 엔진이 떠올랐다. 일관된 결과를 내기 위해서 템플릿 엔진을 활용하여 코드를 생성하는 방법이 더 적합해 보였다. <code>python Jinja</code>와 <code>fast_mcp</code>로 MCP 도구를 만들고 LLM이 활용할 수 있게 하였다. 파라미터는 LLM으로 만들고 결과물 생성은 MCP를 통해 생성할 수 있었다.</li>
</ol>
<blockquote>
<p>지금 상위 모델을 사용하면 코드 생성의 결과를 활용할 수 있을까?</p>
</blockquote>
<h3 id="llm-코딩-에이전트를-잘-활용하는-방법">LLM, 코딩 에이전트를 잘 활용하는 방법</h3>
<p>결국 LLM 코딩 에이전트를 잘 쓰는 능력이란, 특정 직접 코딩과 에이전트를 통한 결정적인 결과물 확보 중 어느 쪽이 더 적은 리소스로 목표를 달성하는지 판단하는 것이다. 이 판단에 silver bullet은 없다. 프로젝트와 모델마다 다르며, 경험을 통해 휴리스틱 하게 쌓여 간다. 때로는 외부 도구와 연계하는 편이 좋을 것이다.
무엇보다도 문제 정의와 해결에 LLM이 활용 가능한지 판단 하는 게 우선 일 것이다.</p>
<blockquote>
<p>최근에는 랭체인, 구글 adk로 작업에 적합한 에이전트를 만드는 것이 괜찮아 보인다. n8n을 활용하려고 했지만 노코드 툴이라 (개발자에게) 답답한 부분이 존재한다.
다음 내용을 학습하고 문제를 해결할 수 있는 에이전트를 만들어 보려고 한다.
<a href="https://docs.google.com/document/d/1rsaK53T3Lg5KoGwvf8ukOUvbELRtH-V0LnOIFDxBryE/preview?ref=pytorchkr&amp;tab=t.0#heading=h.pxcur8v2qagu">Agentic Design Patterns</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[누군가 만나면 들려주고 싶은 C# 비동기 이야기]]></title>
            <link>https://velog.io/@oak_cassia/%EB%88%84%EA%B5%B0%EA%B0%80-%EB%A7%8C%EB%82%98%EB%A9%B4-%EB%93%A4%EB%A0%A4%EC%A3%BC%EA%B3%A0-%EC%8B%B6%EC%9D%80-C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9D%B4%EC%95%BC%EA%B8%B0</link>
            <guid>https://velog.io/@oak_cassia/%EB%88%84%EA%B5%B0%EA%B0%80-%EB%A7%8C%EB%82%98%EB%A9%B4-%EB%93%A4%EB%A0%A4%EC%A3%BC%EA%B3%A0-%EC%8B%B6%EC%9D%80-C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9D%B4%EC%95%BC%EA%B8%B0</guid>
            <pubDate>Sun, 27 Jul 2025 16:34:24 GMT</pubDate>
            <description><![CDATA[<h3 id="동시성-concurrency">동시성 (Concurrency)</h3>
<p>비동기 프로그래밍을 이해하기에 앞서, <strong>동시성(Concurrency)</strong> 개념을 명확히 해야 한다. 동시성은 단순히 여러 작업을 번갈아 처리하는 <strong>시분할(Time-sharing)</strong>과 동일한 개념이 아니며, <strong>여러 작업을 동시에 처리하는 능력을 의미하는 추상적인 정책</strong>에 가깝다.</p>
<p>초기 싱글 코어 컴퓨터 시절에는 시분할 방식인 <strong>컨텍스트 스위칭(Context Switching)</strong>이 동시성을 구현하는 주요 방식이었기에 두 개념이 혼용되었다. 하지만 현대의 동시성은 다중 코어를 활용한 <strong>병렬 처리(Parallelism)</strong>나 시분할을 모두 포함하는 더 넓은 개념이다.</p>
<blockquote>
<ul>
<li><strong>멀티 스레딩</strong>: 다수의 스레드를 만들어 동시 실행</li>
<li><strong>병렬 처리</strong>: 여러 작업을 다수의 코어(또는 스레드)에 분배하여 동시 실행</li>
</ul>
</blockquote>
<p>동시성을 코드 레벨에서 구현하는 방식 중 하나가 <strong>비동기 프로그래밍</strong>이다. 특정 작업의 완료를 기다리며 프로그램의 주 흐름이 멈추지 않고, 해당 작업을 독립적으로 처리하는 방식이다. 이를 통해 서버는 한 유저의 요청을 처리하는 동안 다른 유저의 요청에도 응답할 수 있어 효율을 극대화한다.</p>
<hr>
<h3 id="continuation">Continuation</h3>
<p>비동기 프로그래밍은 코드 흐름이 순차적이지 않아 특정 작업이 끝난 뒤 <strong>&#39;무엇을 할 것인가&#39;</strong>를 명시해야 한다. 이를 <strong>후속 작업(Continuation)</strong>이라고 한다. 과거에는 다음 패턴으로 후속 작업을 표현했다.</p>
<ul>
<li><strong>콜백(Callback) 패턴</strong>: 코드가 깊어지며 복잡해지는 단점</li>
<li><strong>APM(Asynchronous Programming Model)</strong>: 작업이 동기적으로 완료될 경우 스택 오버플로우 위험성, <code>IAsyncResult.CompletedSynchronously</code> 속성으로 해결할 수 있지만, 동일한 로직이 호출부와 콜백 내부에 분산되어 유지보수성 하락</li>
<li><strong>EAP(Event-based Asynchronous Pattern)</strong>: 메서드가 대부분 <code>void</code>를 반환하여 개별 작업 추적이 어렵고, <code>EventArgs</code>를 통해 결과 확인. <code>SynchronizationContext</code>를 도입하여 후속 작업이 특정 스레드(예: UI 스레드)에서 실행가능 하게 한 발전</li>
</ul>
<p>기존 모델의 문제점을 해결하기 위해 <strong>TPL(Task Parallel Library)</strong>과 <strong><code>Task</code></strong>가 등장했다.</p>
<hr>
<h3 id="task"><strong>Task</strong></h3>
<p>과거에는 <code>Thread</code>를 직접 사용해 멀티스레딩을 구현했다. 하지만 각 스레드는 OS 스레드에 직접 매핑되어 생성과 소멸 비용이 컸고, <code>Join</code> 메서드는 스레드를 블로킹하여 비효율적이었다.</p>
<p><strong><code>Task</code></strong>는 비동기 작업을 나타내는 <strong>고수준으로 추상화된 객체</strong>다. <code>Task</code>는 특정 스레드를 의미하는 것이 아니라, <strong>미래에 완료될 작업 자체</strong>를 의미한다. 작업의 상태, 결과, 예외를 캡슐화하여 호출자가 작업의 상태를 안전하고 간단하게 처리할 수 있다.</p>
<ul>
<li><strong><code>Task.Run</code></strong>: CPU 바운드 작업을 스레드 풀에서 비동기적으로 실행할 때 쓴다.<code>Task.Factory.StartNew</code>에 비해 파라미터가 단순화되었다.</li>
<li><strong><code>async/await</code></strong>: I/O 바운드 작업에 사용됩니다. (자세한 내용은 아래에서 설명)</li>
<li><strong><code>Task.ContinueWith</code></strong>: 후속 작업을 연결할 때 사용한다. 선행 <code>Task</code>가 후속 작업 델리게이트에 인자로 전달되며, 여러 <code>ContinueWith</code>를 호출하여 작업을 체이닝할 수 있다.</li>
</ul>
<hr>
<h3 id="asyncawait"><strong>async/await</strong></h3>
<p><code>async/await</code>는 <strong>비동기 코드를 동기 코드처럼 보이게 만들어</strong> 가독성과 유지보수성이 높다. <code>async</code> 메서드가 반환한 <code>Task</code>에 <code>await</code> 키워드를 사용하면, 현재 코드의 진행 흐름은 잠시 멈추고 스레드 풀(ThreadPool)에서 다른 작업을 가져와 실행한다. 이 간단한 처리 이면에 다음 과정이 숨어있다.</p>
<ol>
<li><p><strong>상태 머신(State Machine) 생성</strong></p>
<ul>
<li>컴파일러는 <code>async</code> 한정자가 붙은 메서드를 <strong>상태 머신 객체</strong>를 생성하고 시작하는 코드로 대체한다. (디버그 모드에서는 <code>struct</code>, 릴리즈 모드에서는 <code>class</code>)</li>
</ul>
</li>
<li><p><strong><code>AsyncTaskMethodBuilder</code>의 역할</strong></p>
<ul>
<li>상태 머신 내부에 <code>AsyncTaskMethodBuilder</code> 생성</li>
<li>이 빌더는 <code>async</code> 메서드의 호출자에게 반환될 <code>Task</code>를 생성한다.</li>
<li><code>await</code> 키워드가 있는 코드에서, <code>awaiter</code>에게 다음 작업(<code>MoveNext</code>)을 후속 작업으로 등록한다.</li>
<li>상태 머신이 완료되거나 예외가 발생하면 <code>Task</code>의 상태를 변경한다.</li>
</ul>
</li>
<li><p><strong>상태 머신의 실행 흐름 (<code>MoveNext</code>)</strong></p>
<ul>
<li>대체된 코드에서 Start 함수를 통해 ExecutionContext를 캡처/복원하고 MoveNext를 호출한다.</li>
<li><code>async</code> 메서드의 실제 로직은 <code>MoveNext</code> 메서드 안으로 옮겨진다.</li>
<li><code>state</code> 필드 값을 기준으로 실행 흐름을 분기한다.</li>
<li><code>await</code> 지점에서 작업이 완료되지 않았다면, <code>state</code>를 변경하고 <code>awaiter</code>를 통해 후속 작업(<code>MoveNext</code> 호출)을 등록한다.</li>
<li>작업이 완료되어 <code>MoveNext</code>가 다시 호출되면, 중단되었던 지점부터 실행한다.</li>
</ul>
</li>
</ol>
<hr>
<h3 id="awaiter-패턴"><strong>Awaiter 패턴</strong></h3>
<p><code>await</code> 키워드는 <strong>덕 타이핑(Duck Typing)</strong> 방식으로 동작한다. <code>await</code> 하려는 대상의 <code>GetAwaiter()</code> 메서드를 찾는다. 이 메서드가 반환하는 <code>awaiter</code> 객체는 다음 멤버를 포함한다.</p>
<ul>
<li><code>bool IsCompleted</code> { get; }<ul>
<li>작업이 동기적으로 즉시 완료되었는지 여부 반환</li>
</ul>
</li>
<li><code>void OnCompleted(Action continuation)</code><ul>
<li>작업이 비동기적으로 진행될 때 호출되며, 인자로 전달된 <code>continuation</code> (상태 머신의 <code>MoveNext</code>를 호출하는 래퍼 델리게이트)을 작업 완료 후 스케줄링하여 실행한다.</li>
</ul>
</li>
<li><code>TResult GetResult()</code><ul>
<li>작업이 최종 완료된 후 호출되어 결과를 반환하거나, 작업 중 발생한 예외를 던진다.</li>
</ul>
</li>
</ul>
<p>또한 <code>awaiter</code> 객체는 <code>INotifyCompletion</code> 인터페이스를 구현해야 한다 (<code>OnCompleted</code> 메서드).</p>
<blockquote>
<p>미완료일 때의 <code>task.GetAwaiter().GetResult()</code>와 task.Result, Task.Wait()를 사용하면 스레드가 블로킹 될 수 있으니 주의</p>
</blockquote>
<hr>
<h3 id="threadpool과-taskscheduler"><strong>ThreadPool과 TaskScheduler</strong></h3>
<p>이러한 <code>Task</code>들은 어디서 어떻게 수행되는 걸까? <strong><code>TaskScheduler</code></strong>에 의해 <strong><code>ThreadPool</code></strong>의 스레드에서 실행되도록 스케줄링된다.</p>
<ul>
<li><p><strong>ThreadPool</strong>: 미리 생성된 스레드 집합을 유지하고 재사용하여 스레드 생성/제거 오버헤드를 줄인다. TPL은 CPU 코어 수와 현재 부하를 고려하여 스레드 수를 동적으로 조정한다. <code>TaskContinuationOptions</code> 열거형으로 간단히 후속 작업의 실행과 관련된 제어를 할 수 있다. 해당 풀의 스레드는 백그라운드 스레드로 애플리케이션 종료 시 같이 종료된다.</p>
<ul>
<li><strong>Worker Threads</strong>: CPU 바운드 작업을 처리한다.</li>
<li><strong>IOCP (I/O Completion Port) Threads</strong>: I/O 작업의 완료를 처리합니다. 두 스레드 유형을 분리하여 I/O 대기가 CPU 연산을 방해하는 것을 막는다. (정확한 동작은 아직 모르겠다. 네이티브 완료 신호를 수신한 뒤 Worker Thread가 실행하도록 하는 건지...)</li>
</ul>
</li>
<li><p><strong>TaskScheduler</strong>: <code>ThreadPool</code>보다 더 높은 수준의 추상화 계층으로, <strong>어떤 작업을, 언제, 어디서 실행할지 결정</strong>한다.</p>
<ul>
<li>기본적으로 <code>ThreadPool</code>을 사용하며, <strong>전역 큐(Global Queue)</strong>와 각 스레드가 가진 <strong>로컬 큐(Local Queue)</strong>를 사용한다.</li>
<li><strong>Work-Stealing</strong>: 자신의 로컬 큐가 비면, 전역 큐를 확인한다. 그래도 작업이 없으면 다른 스레드의 로컬 큐에서 가져온다.<ul>
<li>자신의 로컬 큐 접근: <strong>LIFO (후입선출)</strong> -&gt; 캐시 지역성(Cache Locality) 극대화<ul>
<li>자식 작업은 부모 작업이 사용 중이던, 캐시에 올라가 있는 데이터를 사용할 확률이, 다른 작업보다 높다.</li>
</ul>
</li>
<li>다른 스레드의 로컬 큐 접근: <strong>FIFO (선입선출)</strong> -&gt; 경합 감소</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="execution-context와-synchronization-context"><strong>Execution Context와 Synchronization Context</strong></h3>
<ul>
<li><p><strong><code>ExecutionContext</code></strong>: 비동기 작업 간에 <strong>보안, 호출 컨텍스트 등 실행에 필요한 문맥 정보</strong>를 유지한다. 후속 작업이 실행될 때 이전에 캡처된 <code>ExecutionContext</code>를 복원한다. 상태 머신의 필드로 관리되기도 한다.</p>
</li>
<li><p><strong><code>SynchronizationContext</code></strong>: 후속 작업을 <strong>특정 스레드</strong>에서 실행한다.</p>
<ul>
<li>UI, HTTP 요청 스레드로 설정될 수 있다.</li>
<li>일반적인 콘솔 앱 등에서는 스레드 풀 스레드이다.</li>
<li><code>ConfigureAwait(false)</code>를 호출하면, 스레드 풀 스레드에서 후속 작업을 실행한다. <strong>라이브러리 같이 실행 환경을 특정할 수 없는 코드를 작성할 때는 <code>ConfigureAwait(false)</code>를 호출해야 한다.</strong></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[협업에 관해]]></title>
            <link>https://velog.io/@oak_cassia/%ED%98%91%EC%97%85%EC%97%90-%EA%B4%80%ED%95%B4</link>
            <guid>https://velog.io/@oak_cassia/%ED%98%91%EC%97%85%EC%97%90-%EA%B4%80%ED%95%B4</guid>
            <pubDate>Sun, 13 Jul 2025 07:25:16 GMT</pubDate>
            <description><![CDATA[<h3 id="개요">개요</h3>
<p>4월 진행한 업무에서의 경험을 공유한다. </p>
<blockquote>
<p><strong>협업은 분업이 아니며 팀이 목표를 달성하기 위해 시너지를 만드는 과정</strong></p>
</blockquote>
<hr>
<h3 id="처음-했던-생각">처음 했던 생각</h3>
<p>일전의 <a href="https://github.com/oak-cassia">목표</a>를 보면 협업을 잘하는 인재로 방향을 설정했다. 협업은 어떻게 잘하는 것일까? 적은 경험으로 도출한 방법은 의사소통이었다. </p>
<ul>
<li>짧고 이해하기 쉬운 문장</li>
<li>상대방의 배경지식을 고려</li>
<li>충분한 맥락과 정보</li>
<li>전달의 완료(이해 여부)까지 책임</li>
</ul>
<p>의사 소통을 잘하면 협업을 잘하는 것일까? 완전하지는 않다고 생각했다.</p>
<hr>
<h3 id="업무에서">업무에서</h3>
<h4 id="부족한-시간">부족한 시간</h4>
<p>콘텐츠 개선 작업 중 기획서가 늦었다. 그동안 시스템을 파악했다. 근무 일 7일을 남기고 받은 기획서는 미정인 부분이 많았다. 파악한 시스템을 봤을 때 작업량이 많고 복잡도가 높아, 팀에 상황을 공유하고 지원을 받았다. 4명이서 작업을 하게 되었다. </p>
<h4 id="야식을-먹다가-문득">야식을 먹다가 문득</h4>
<p>야근과 주말 출근은 당연했다. 점심시간 마다 컨디션 관리를 위해 30분씩 산책을 나갔다.
며칠 후 저녁을 먹으러 가다가 문득 웃음이 터졌다. 10시 아니 12시 이전이라도 퇴근하면 좋겠다고 생각이 들었던 것이 우스웠다.
작업 중간에 야식을 먹으며 이야기를 나누면, 각자의 작업이 하나의 목표를 향해 완성되고 있는 것이 보였다. 빠른 작업을 위해 각자 잘하는 것을 맡아 진행했다.</p>
<ul>
<li>나: 신규 기능, 멱등성을 고려한 정산</li>
<li>A: 다른 기능에 영향가는 부분의 수정</li>
<li>B: 본인이 맡았던 기능의 변경</li>
<li>C: 인프라 기반 의사결정</li>
</ul>
<p>작업은 큰 이슈 없이 마무리 되었고 협업에 대한 인사이트가 생겼다. </p>
<blockquote>
<p>기획자가 기획하고 개발자가 개발하거나, 
책임을 나눈 클래스를 개발하고 합치는 게 협업의 전부가 아니구나</p>
</blockquote>
<h3 id="ndc를-보니">NDC를 보니</h3>
<p>NDC 2025에서 조직 관련 세션이 있었다. <code>대형 게임디자인 조직, 어떻게 운영할까?</code>
내용 중 여러 프로젝트에서 기획의 팀빌딩을 했던 경험에 집중했다. 기능 조직, 목적 조직을 소개할 때는 MSA가 문득 생각났다. </p>
<ul>
<li>기능 조직: 갖고 있는 스킬별로 나누거나 </li>
<li>목적 조직: 만들게 되는 하나의 시스템, 즉 목적 별로 분류</li>
</ul>
<p>전자는 전문적이고 후자는 제네럴리스트라는 내용도 나왔는데, 목적 조직에서 같은 시스템을 지속적으로 맡다보면 다른 방향의 전문성이 생기지 않을까 생각이 들었다.</p>
<p>관리자 급에서는 높은 추상화 단계로 협업을 이렇게 생각하겠구나 싶었다. 그렇다면 로우 레벨에서의 협업은 어떻게 잘할 수 있을까?</p>
<ul>
<li>작업 공유: 글의 초반부에 소개한 의사소통이 여기 속한다.</li>
<li>나, 팀원에 대한 이해: 각자의 능력에 대한 이해를 기반으로 의사결정해야 한다.<ul>
<li>물론 결정을 위한 다른 요인도 존재한다.</li>
</ul>
</li>
</ul>
<p>짧은 경력 그리고 한 두가지의 경험으로 전부 알기는 어렵다. 그럼에도 작업의 공유가 큰 부분을 차지하는 것을 알 수 있다.</p>
<blockquote>
<p>어떻게 공유하고 진행할 것인가?</p>
</blockquote>
<h3 id="나의-학습과-팀의-성장">나의 학습과 팀의 성장</h3>
<p>다른 하나는 학습이다. <code>나의 성장을 팀의 성장으로 이끌어 낼 수 있다면 협업을 잘 한다고 할 수 있지 않을까?</code> 란 생각이 들었다. 그렇다면 <strong>학습으로 실제 문제 중 어떤 것을 해결할 수 있을까?</strong>
실제 업무에서 발생하는 문제를 찾아 정리했다. 프롬프트, 컨텍스트를 포함해 LLM을 잘 쓰는 방법을 찾고 적용 중이다. 회사에서 남는 시간을 모두 여기에 쏟고 있는데, 최근 성과가 나오기 시작했다. 이 나만의 프로젝트가 완료된다면 해당 내용을 공유해보려고 한다. <strong>나의 학습으로 팀을 성장시킨 방법</strong>에 대해</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[A Game Developer's Vision]]></title>
            <link>https://velog.io/@oak_cassia/A-Game-Developers-Vision</link>
            <guid>https://velog.io/@oak_cassia/A-Game-Developers-Vision</guid>
            <pubDate>Sat, 15 Mar 2025 13:58:22 GMT</pubDate>
            <description><![CDATA[<h3 id="a-game-developers-vision">A Game Developer&#39;s Vision</h3>
<p>짧은 일생, 남긴 점을 이어보면 일관되게 <code>서로 공감할 수 있는 세상을 만든 다는 꿈</code>이 보인다. 그리고 <code>배경 상관없이 전 세계 모두가 즐길 수 있는 재밌는 게임</code>이 더해지며 비전은 완성된다. 실현을 위한 목표는 <code>안정적인 서버를 만드는 것</code>이다. 서버가 터지면 유저와 개발자 그 누구도 즐거울 수 없다. 전 세계에서 수 많은 사람들이 믿고 즐길 수 있는 게임과 서버를 만들어내는 인재라는 목표를 <strong>이유</strong>로 성장할 것이다.</p>
<h4 id="personal-dream">Personal Dream</h4>
<p><code>공감하는 세상을 만든다.</code> </p>
<h4 id="game-developers-vision">Game Developer&#39;s Vision</h4>
<p><code>배경 상관없이 공감대를 형성하며 즐길 수 있는 명품 게임을 만든다.</code></p>
<blockquote>
<p>명품 게임은</p>
</blockquote>
<ul>
<li>유저 관점의 재미를 기반해 강력한 IP로 확장할 수 있으며</li>
<li>유저와 연속적인 신뢰 관계를 형성한다.</li>
</ul>
<h4 id="goal">Goal</h4>
<p><code>안정적인 글로벌 서비스의 인재로 성장한다.</code></p>
<ul>
<li>전 세계의 유저가 즐길 수 있는 게임을 위해 안정적인 서버를 만든다.</li>
</ul>
<blockquote>
<p>인재란?</p>
</blockquote>
<ul>
<li>자신의 성장으로 주변까지 함께 성장</li>
<li>대체가 불가능한/어려운 역량 보유</li>
<li>협업으로 혼자 낼 수 있는 성과 이상의 결과를 도출</li>
</ul>
<hr>
<h3 id="developers-way">Developer&#39;s Way</h3>
<h4 id="202301-3학년-2학기">2023.01 3학년 2학기.</h4>
<p><code>스마일게이트 윈터 데브 캠프 2기</code>에 참여했을 때 빠르게 성장했다. 당시 프로젝트를 마치고 든 생각은 <code>왜 성장을 하느냐</code>였다. 성장의 중요성을 말하는 책은 많았지만 그 이유를 서술한 책은 없었다. 하지만 성장은 좋은 동기부여였고 <code>게임 개발자</code>라는 목표에 다가가는 것은 즐거웠다. 성장의 경험을 동아리 후배들에게 나눴다.</p>
<h4 id="202305-4학년-1학기">2023.05 4학년 1학기.</h4>
<p><code>컴투스 서버 캠퍼스 1기</code> 수료 후 7월에 곧바로 취업했다. 회사에서도 성장의 이유는 공백이었다. 먼저 취업한 선배와 만나 개발하는 게임에 대한 이야기를 했다.</p>
<h4 id="202406-다낭">2024.06 다낭.</h4>
<p>일상에서 하지 않았던 일들을 했다. 서핑을 처음 배웠다. 혼자 간 식당에서 음식을 많이 시켜 절반도 먹지 못하고 나왔다. 하루 종일 해변에 누워 있었다. 문득 <code>꿈을 기억하지 못한다는 사실</code>을 깨달았다. 전과와 편입을 하면서, 군시절 밤새 사무실에서 야근을 하며 그렸던 꿈, 기억나지 않았다. 성장에 매몰되어, 한 걸음 나아가는 것에 집중하다 잃어버렸다. 그리고 혼자 다니는 건 즐겁지 않았다.</p>
<h4 id="202409-베를린">2024.09 베를린.</h4>
<p>여행 후, 몇 달간 꿈에 대해 생각했다. <code>가장 인터랙티브한 미디어인 게임으로, 다른 삶을 경험하게 하여 사람들의 공감 능력을 끌어올리겠다.</code> 독일 출발 전, 퇴근 길 떠오른 나의 새로운 꿈, 이게 진짜 내 꿈이 맞을까?
여행 막바지 베를린에서 같이 간 동아리 후배(기획자)와 반나절 동안 텍스트 기반의 게임에 관해 논쟁했다. 세부 내용은 기억나지 않지만 <code>목적과 의도</code>에 관련된 이야기였다. 각 기획에는 <code>목적과 의도</code>가 있어야 한다고 열변을 토한 기억은 난다.</p>
<h4 id="202409-브라이튼">2024.09 브라이튼.</h4>
<p>홀로 영국으로 떠난 뒤, 런던의 남쪽 브라이튼, 뱅크시의 벽화에서 몇 블록 떨어진 작은 카페. 들어서자 마자 나를 반겨준 직원의 안내를 따라 자리에 앉았다. 오른쪽 자리에 있던 노부부의 느닷없는 환대. 작은 도시에서 무엇이 이곳 사람을 행복하게 했을까. 노부부가 떠난 자리에 정신없어 보이는 남성 한 명이 왔다. 그가 펼친 책에서 컴공의 흔적이 보였다. 나보다 경력이 많은 네트워크 프로그래머였다. 일을 하는 이유에 대해서 들었다. <code>즐겁다고 했다.</code></p>
<h4 id="202502-여수">2025.02 여수.</h4>
<p>꿈은 언제 비전이 되는가? 나의 꿈은 간절히 원하는 이상이었지만 현실은 달랐다. 문예창작학과에 진학하며 <code>소외된 사람들의 이야기를 쓰는 작가</code>가 되고 싶었지만 글재주 보다는 개발을 잘했다. 만들고 싶은 게임이 있지만 나는 게임 서버 개발자로 성장하고 싶다. <code>현실과 동떨어진 꿈은 비전이 될 수 없다.</code> 꿈을 잃어버린 이유였다. 비전의 중요성을 동생과 후배들에게 전했다.</p>
<h4 id="2015-어쩌면-2016-마닐라">2015, 어쩌면 2016 마닐라</h4>
<p>해외 봉사로 간 빈민가에서 편견의 벽을 허문 것은 롤이었다. 처음에는 대화하기를 꺼렸지만, 그들은 같은 게임을 즐기는 똑같은 유저였다.</p>
<blockquote>
<p>생각해보면 가장 친한 친구도 배틀그라운드로 인연이 시작되지 않았는가?</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[go interface와 디스패치]]></title>
            <link>https://velog.io/@oak_cassia/go-interface%EC%99%80-%EB%94%94%EC%8A%A4%ED%8C%A8%EC%B9%98</link>
            <guid>https://velog.io/@oak_cassia/go-interface%EC%99%80-%EB%94%94%EC%8A%A4%ED%8C%A8%EC%B9%98</guid>
            <pubDate>Tue, 04 Mar 2025 12:13:06 GMT</pubDate>
            <description><![CDATA[<h2 id="duck-typing">duck typing</h2>
<ul>
<li>특정 인터페이스의 메서드를 모두 구현하면 인터페이스로 사용될 수 있다. (암묵적 구현)</li>
<li>잘못된 타입의 값을 인터페이스를 사용하는 함수로 전달한 경우 컴파일 타임에 오류가 발생한다. (암묵적 변환),(js/ python은 검사 못함)</li>
<li>하지만 특정 인터페이스 값이 다른 인터페이스의 추가 메서드를 갖는지는 동적으로 확인한다.</li>
<li>명시적인 인터페이스 간 변환은 런타임에 메서드 집합에 관한 정보를 확인할 수 있어야 한다.</li>
</ul>
<h2 id="메서드를-찾는-방법">메서드를 찾는 방법</h2>
<ul>
<li>C++의 vtable이나 js의 프로토타입 체인 사이 중간 정도의 방법을 사용한다.<ul>
<li>메서드 테이블을 사용하지만 런타임에 계산한다.<h3 id="interface-value">interface value</h3>
</li>
</ul>
</li>
<li>인터페이스에 저장된 실제 타입에 대한 정보를 가리키는 포인터 (interface table)</li>
<li>실제 데이터를 가리키는 포인터</li>
</ul>
<h4 id="i-table-interface-table">i-table (interface table)</h4>
<ul>
<li>interface(<code>myInterface</code>, <code>myConcrete</code>)<ul>
<li><code>var i myInterface = c</code> 이면 c의 복사본을 저장</li>
</ul>
</li>
<li>실제 타입에 대한 메타데이터와 함수 포인터 리스트로 구성되어 있음</li>
<li><code>myInterface</code>에 해당하는 메서드만 포함하며 다른 <code>myConcrete</code>의 메서드는 포함하지 않음</li>
</ul>
<h4 id="data">data</h4>
<ul>
<li>하나의 워드 안에 포함되는 데이터면 힙 할당을 하지 않음</li>
<li>값이 크면 힙에 메모리 할당 후 포인터를 저장</li>
</ul>
<h3 id="타입-비교-시">타입 비교 시</h3>
<h4 id="타입-확인">타입 확인</h4>
<ul>
<li>interface가 들고 있는 특정 타입을 확인하려면<ul>
<li><code>i.tab-&gt;type</code>으로 실제 타입 포인터 획득</li>
</ul>
</li>
<li>값은 i.data를 역참조하여 복사<blockquote>
<p>tab은 i-table 을 나타내는 멤버</p>
</blockquote>
<h4 id="디스패치">디스패치</h4>
</li>
<li>i-table에서 적절한 함수 포인터를 찾아 인터페이스 값의 data를 첫 번째 인자로 호출<ul>
<li><code>i.tab-&gt;fun[0](i.data)</code></li>
<li><code>i.data</code>는 32비트(워드) 포인터를 인자로 전달 받는 것</li>
</ul>
</li>
<li>호출 지점은 data의 의미나 가리키는 데이터의 크기를 모름<ul>
<li>인터페이스 코드는 itable의 함수 포인터가 인터페이스 값에 저장된 워드를 알고 있음</li>
<li>함수 포인터 <code>(*myConcrete).someFn</code></li>
</ul>
</li>
</ul>
<h3 id="i-table-만드는-과정">i-table 만드는 과정</h3>
<ul>
<li>모든 조건(메서드)에 부합하는 <code>(인터페이스 타입, 구체 타입)</code> 쌍의 itable을 계산하는 것은 비효율적<ul>
<li>숫자가 많으며 실제로 필요하지 않을 수 도 있음</li>
<li>이것도 결국 메모리에 올라가야 함</li>
</ul>
</li>
<li>각 구체 타입에 대해 <code>type description structure</code>를 생성<ul>
<li>다른 메타데이터와 함께 메서드 목록 포함</li>
</ul>
</li>
<li>각 인터페이스에 대해서 같은 작업</li>
<li><code>interface runtime</code>은 두 테이블을 보고(i -&gt; c) itable을 계산하여 캐싱<ul>
<li>이후에는 재사용</li>
</ul>
</li>
<li>모든 가능한 쌍은 <code>O(ni * nc)</code></li>
<li>두 테이블 정렬 후 비교는 <code>O(ni + nc)</code></li>
</ul>
<h3 id="빈-인터페이스-최적화">빈 인터페이스 최적화</h3>
<ul>
<li>메서드가 하나도 없는 경우 i-table은 원래 타입에 대한 포인터를 저장하는 용도이다.<ul>
<li>메서드 존재 여부는 정적으로 파악할 수 있음</li>
</ul>
</li>
<li>i-table을 생략하고 값이 타입의 메타데이터를 직접 가리키도록 한다.</li>
</ul>
<h3 id="값-인라인-최적화">값 인라인 최적화</h3>
<ul>
<li>하나의 워드 안에 포함되는 데이터면 힙 할당을 하지 않음</li>
<li>컴파일러는 타입의 메서드 테이블에 복사된 함수들이 전달받은 워드를 올바르게 처리하도록 조정<ul>
<li>함수 포인터 <code>(myConcrete).someFn</code></li>
</ul>
</li>
</ul>
<h3 id="메서드-조회">메서드 조회</h3>
<ul>
<li>인터페이스 값이 저장되는 시점에 itable을 계산하거나 캐시에서 찾고 메서드 호출 시 간접 호출만 수행<blockquote>
<p>간접호출 <code>indirect call</code>: 함수의 주소를 직접 명시하지 않고, 포인터나 테이블을 통해 함수의 주소를 찾아 호출</p>
</blockquote>
</li>
</ul>
<blockquote>
<p>Go Data Structures: Interfaces</p>
</blockquote>
<ul>
<li><a href="https://research.swtch.com/interfaces">https://research.swtch.com/interfaces</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[import std; 사용하기 (with Clang, Cmake)]]></title>
            <link>https://velog.io/@oak_cassia/import-std-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-with-Clang-Cmake</link>
            <guid>https://velog.io/@oak_cassia/import-std-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-with-Clang-Cmake</guid>
            <pubDate>Wed, 12 Feb 2025 14:01:46 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p><strong>C++</strong>의 <code>modules</code>와 <code>static polymorphism</code>에 관심이 생겼다. 이 글에서는 그 중 <code>modules</code>를 살펴본 후 <code>import std;</code>를 사용하기 위한 과정을 살펴본다.</p>
<ul>
<li>macOS sequoia</li>
<li>CLion</li>
<li>Clang 19.1.7</li>
<li>CMake 3.30</li>
</ul>
<hr>
<h2 id="c-modules">C++ modules</h2>
<h4 id="include의-한계">#Include의 한계</h4>
<p>전처리 과정에서 파일의 내용을 그대로 복사하기 때문에 여러 가지 문제가 발생한다.</p>
<ul>
<li>동일한 헤더가 중복 포함</li>
<li>네임스페이스와 매크로 충돌</li>
<li>불필요한 의존성 발생</li>
</ul>
<blockquote>
<p>문제를 해결하기 위해 include guard나 PCH(Precompiled Header) 같이 별도의 처리가 필요</p>
</blockquote>
<p>modules의 장점은:</p>
<ol>
<li>한 번만 로드: 중복 포함 방지</li>
<li>매크로 충돌 방지: 동일한 매크로가 있는 두 헤더를 포함할 때, 순서에 따라 값이 달라지는 문제 해결</li>
<li>순서 독립적: 모듈은 포함 순서에 영향 받지 않음</li>
<li>불필요한 의존성 제거: 직접 참조하는 모듈만 알면 됨</li>
</ol>
<p>그리고 헤더와 소스를 나눌 필요가 없다. 파일을 복사하는 방식은 컴파일 시간을 단축하기 위해서 분리가 필요했지만, 컴파일러는 <code>export</code>로 명시한 모듈의 인터페이스와 구현을 구분한다. 따라서 코드를 분리하여 복잡하게 만들필요가 없다.</p>
<blockquote>
<p>파일 분리가 필요 없어진 상황에서 어떻게 가독성을 높일지 고민 할만한 것 같다.</p>
</blockquote>
<hr>
<h3 id="모듈-사용법">모듈 사용법</h3>
<p><code>module</code>,<code>import</code>, <code>export</code> 키워드를 통해서 모듈을 사용할 수 있다.</p>
<pre><code class="language-cpp">// Vector.cpp
export module Vector;

export class Vector {
public:
    explicit Vector(int s);

    double &amp;operator[](int i) const;

private:
    double *elem;
    int elemSize;
};

// 구현 부분 ...
</code></pre>
<pre><code class="language-cpp">// main.cpp
import Vector;

int main() {</code></pre>
<hr>
<h2 id="import-std-사용하기">import std; 사용하기</h2>
<p><code>import std;</code>는 표준 라이브러리의 모듈 인터페이스를 가져온다. 하지만 clang c++23 환경에서는 제대로 지원을 하지 않았다.</p>
<ul>
<li>대안으로 <code>#include &lt;iostrema&gt;</code> 한 후 export를 하려고 했다.<ul>
<li><code>export std::cout</code> 개별로 export 하는 것만 동작해서 실패</li>
</ul>
</li>
</ul>
<p>clang에서는 불가능한 것인가 생각하던 찰나 cmake를 사용하는 방법을 찾았다.<a href="https://intellij-support.jetbrains.com/hc/en-us/community/posts/19578452563218-Clion-how-to-enable-std-module-support">Clion how to enable std module support?</a></p>
<p><code>CmakeList.txt</code>를 다음 글을 보고 수정했다. </p>
<ul>
<li><a href="https://www.kitware.com/import-std-in-cmake-3-30/">import std in CMake 3.30</a></li>
</ul>
<pre><code class="language-cmake">cmake_minimum_required(VERSION 3.30)

set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD
  # This specific value changes as experimental support evolves. See
  # `Help/dev/experimental.rst` in the CMake source corresponding to
  # your CMake build for the exact value to use.
  &quot;0e5b6991-d74f-4b3d-a41c-cf096e0b2508&quot;)

project(untitled LANGUAGES CXX)

set(CMAKE_CXX_MODULE_STD 1)

set(CMAKE_CXX_STANDARD 23)


add_executable(untitled src/main.cpp
)

target_sources(untitled
        PUBLIC
        FILE_SET modules TYPE CXX_MODULES FILES
        src/Vector.cppm
        src/Transport/Server.cppm
        src/StdAll.cppm
)
</code></pre>
<hr>
<p>이런 오류가 생겼다.</p>
<ul>
<li><blockquote>
<p>Experimental <code>import std</code> support not enabled when detecting toolchain; it must be set before <code>CXX</code> is enabled (usually a <code>project()</code> call)</p>
</blockquote>
</li>
</ul>
<p>CLion 설정에 CMake option을 직접 넣어줬다.
<code>-DCMAKE_EXPERIMENTAL_CXX_IMPORT_STD=0e5b6991-d74f-4b3d-a41c-cf096e0b2508</code></p>
<hr>
<p>또 오류...</p>
<ul>
<li><blockquote>
<p><code>libc++.modules.json</code> resource does not exist</p>
</blockquote>
</li>
</ul>
<p><a href="https://github.com/NixOS/nixpkgs/issues/370217">https://github.com/NixOS/nixpkgs/issues/370217</a>
같은 고민을 하는 사람이 좀 있었다. CLion 내장 CMake 사용 중이라서 homebrew로 CMake 설치 및 연동하고 아래 파일 수정했다.
<code>/opt/homebrew/share/cmake/Modules/Compiler/Clang-CXX-CXXImportStd.cmake</code></p>
<p>Go to the line 13 and change <code>-print-file-name=libc++.modules.json</code> to <code>-print-file-name=../../c++/libc++.modules.json</code></p>
<hr>
<p>빌드가 됐다. 그런데 문제가 또 남았다.
CLion에서 std모듈을 resolve를 못했다.
<a href="https://youtrack.jetbrains.com/issue/CPP-39632/import-std-CLion-cant-resolve-module-std-in-case-of-clang">&#39;import std&#39;: CLion can&#39;t resolve module &#39;std&#39; in case of clang</a></p>
<p>CMakeList.txt 수정하여 해결</p>
<pre><code>add_library(unused_std_target STATIC)

target_sources(unused_std_target
        PRIVATE
        FILE_SET CXX_MODULES
        BASE_DIRS &lt;path-to-llvm&gt;/share/libc++/v1
        FILES &lt;path-to-llvm&gt;/share/libc++/v1/std.cppm &lt;path-to-llvm&gt;/share/libc++/v1/std.compat.cppm)</code></pre><p>--</p>
<h2 id="개발-환경-세팅이란">개발 환경 세팅이란</h2>
<p>modules 사용 부터 <code>import std;</code> 까지 여러번 막히며 예상보다 많은 시간을 들였다. 중간에 <code>#include</code>로 되돌아갈까 고민도 했다. 하지만 <strong>모던한 C++</strong>을 경험하고 싶고 <strong>개발 환경도 결국 문법과 같은 언어의 일부</strong>라는 생각으로 끝까지 시도해봤다. 결과적으로 <code>import std;</code>는 사용할 수 있었지만 <code>modules</code>에서 아직 cyclic dependency에 대한 처리를 지원하지 않았다. 내가 원하는 방식으로 개발을 하지 못할 것 같아 <code>Go</code>언어를 살펴보기로 했다.</p>
<blockquote>
<p>회사에서 처음 환경 세팅을 했을 때도 비슷한 어려움을 겪었다. 몇 번 반복하며 익숙해졌고 이후에는 귀찮음을 덜기 위해 Docker Compose로 손쉽게 구축했다. 돌아보면, 이 과정은 <strong>언어와 환경뿐 아니라 서비스에 까지 도움이 되는 중요한 준비 단계</strong>였다. 왜냐하면 php의 개발 환경은 실제 서비스의 실행환경과 유사하다. 요청이 처리되는 과정과도 연관되어있다. 코드가 실행 &amp; 요청이 처리 되는 환경의 이해로 <strong>이슈 발생 시에 빠르고 정확하게 대처</strong> 할 수 있다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[C#: async await 내부 구조와 동작]]></title>
            <link>https://velog.io/@oak_cassia/C-async-await-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0%EC%99%80-%EB%8F%99%EC%9E%91</link>
            <guid>https://velog.io/@oak_cassia/C-async-await-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0%EC%99%80-%EB%8F%99%EC%9E%91</guid>
            <pubDate>Sun, 08 Dec 2024 13:25:50 GMT</pubDate>
            <description><![CDATA[<h1 id="c-비동기-프로그래밍">C# 비동기 프로그래밍</h1>
<p>C#에서 비동기 코드는 <code>async</code>와 <code>await</code> 키워드로 간결하게 작성할 수 있다. I/O 중심 작업은 불필요한 대기를 없애고, CPU 중심 작업은 병렬 처리로 실행 시간을 단축한다. 
스레드가 비동기 작업을 수행 중 <code>await</code> 키워드로 다른 비동기 함수를 호출하면 동기적으로 완료되거나 혹은 멈춘다. 그리고 다른 작업을 처리하다가 재개할 수 있는 시점이 오면 멈췄던 위치부터 다시 작업을 시작한다. 이 과정이 어떻게 진행되는지 알아보려 한다.</p>
<hr>
<p>C#의 비동기 프로그래밍을 구현할 수 있는 방법은 다음과 같다.</p>
<ul>
<li>APM(Asynchronous Programming Model)</li>
<li>EAP(Event-based Asynchronous Pattern)</li>
<li>TAP(Task-based Asynchronous Pattern)</li>
</ul>
<p>이 중에서 APM과 EAP는 가볍게 살펴보고 <code>async</code>와 <code>await</code>를 사용하는 TAP을 중점적으로 살펴보겠다.</p>
<h3 id="apm">APM</h3>
<ul>
<li>Begin/End 형태로 비동기 작업을 시작하고 결과를 수집</li>
<li>콜백(AsyncCallback)과 상태 객체를 통해 작업 결과 확인</li>
<li>문제점<ul>
<li>동기적 완료 시 스택 오버플로우 가능성</li>
<li><code>CompletedSynchronously</code> 속성을 사용해 처리 위치를 (콜백/호출자 중에서) 정할 수 있지만 복잡성 증가<h3 id="eap">EAP</h3>
</li>
</ul>
</li>
<li>DoStuffAsync 메서드 호출 후 DoStuffCompleted 이벤트로 완료를 알리는 이벤트 기반<ul>
<li>이벤트 핸들러에서 후속작업</li>
</ul>
</li>
<li><code>SynchronizationContext</code>를 도입하여 비동기 완료 시 특정 스레드에서 후속 작업을 실행하</li>
<li>문제점<ul>
<li>void 반환형으로 개별 작업 추적이 어려움</li>
</ul>
</li>
</ul>
<hr>
<h2 id="tap">TAP</h2>
<p>Task를 기반으로 비동기 작업 상태와 결과를 처리하는 패턴이다.
이전 방식들과 비교하면 다음의 장점이 있다.</p>
<ul>
<li>IAsyncResult 구현을 직접 제공할 필요 없음</li>
<li>시작 시점에 후속 작업을 미리 정의할 필요 없음<ul>
<li>작업 시작 후 continuation 제공 가능</li>
</ul>
</li>
<li>작업이 완료되었을 때 비동기적으로 알림을 받을 수 있다.<h3 id="task">Task</h3>
비동기 작업의 진행 상황과 후속작업, 결과를 캡슐화하는 객체</li>
<li><code>_completed</code>: 완료 여부</li>
<li><code>_error</code>: 예외 정보</li>
<li><code>TResult _result</code>: 작업의 결과</li>
<li><code>_continuation</code>: 작업 완료 후 실행할 후속 동작</li>
<li><code>_ec</code>: ExecutionContext<blockquote>
<p>Task는 소비 목적으로 사용하고 TaskCompletionSource가 Task의 생성과 완료를 제어한다. 이 둘을 분리해서 Task를 외부로 전달할 때 작업을 실수 혹은 의도적으로 완료 상태로 변경하지 못하게 한다.</p>
</blockquote>
</li>
</ul>
<p><code>async</code>와 <code>await</code>를 사용하면 다음 함수들이 묵시적으로 호출된다고 생각하면 된다.</p>
<ol>
<li>Continuation과 ContinueWith
Task는 작업 완료 후 실행할 후속 작업(continuation)을 동적으로 제공할 수 있다. 작업이 완료된 경우 콜백을 큐에 넣고, 필요한 경우 ExecutionContext를 캡처한다.</li>
</ol>
<pre><code class="language-csharp">public void ContinueWith(Action&lt;MyTask&gt; action)
{
    lock (this)
    {
        if (_completed)
        {
            // 이미 완료된 경우 즉시 큐에 입력
            ThreadPool.QueueUserWorkItem(_ =&gt; action(this));
        }
        else if (_continuation is not null)
        {
            throw new InvalidOperationException(&quot;실제 Task랑 다르게 하나의 후속작업만 등록될 수 있음을 가정&quot;);
        }
        else
        {
            _continuation = action;
            _ec = ExecutionContext.Capture(); // 현재 ExecutionContext 캡처
        }
    }
}
</code></pre>
<hr>
<ol start="2">
<li>결과와 예외(TaskCompletionSource)
비동기 작업이 끝나면 <code>SetResult</code> 또는 <code>SetException</code> 메서드를 사용하여 완료 상태로 변경할 수 있다.<pre><code class="language-csharp">public void SetResult() =&gt; Complete(null);
</code></pre>
</li>
</ol>
<p>public void SetException(Exception error) =&gt; Complete(error);</p>
<p>private void Complete(Exception? error)
{
    lock (this)
    {
        if (_completed)
            throw new InvalidOperationException(&quot;Already completed&quot;);</p>
<pre><code>    _error = error;
    _completed = true;

    if (_continuation is not null)
    {
        ThreadPool.QueueUserWorkItem(_ =&gt;
        {
            if (_ec is not null)
            {
                ExecutionContext.Run(_ec, _ =&gt; _continuation(this), null);
            }
            else
            {
                _continuation(this);
            }
        });
    }
}</code></pre><p>}</p>
<pre><code>- `Complete` 메서드는 작업을 완료 상태로 변경하고 `continuation`을 실행

3. Wait
Task의 함수이다. 동기적으로 결과를 기다릴 때 사용하는데 나중에 볼 awaiter의 GetResult에서 사용한다. 
&gt; 변환된 상태머신 코드에서 awaiter가 GetResult를 호출하는데 이때 _task.Wait()이 호출되지만  작업이 완료된 경우에 MoveNext가 호출이 되기 때문에 Blocking되는 일은 없다.

---

### ExecutionContext
ExecutionContext는 비동기 작업 간에 “보안 정보, 문화권, 호출 정보” 등 주변(ambient) 데이터를 일관되게 유지하기 위한 컨테이너이다. 비동기 작업이 여러 스레드를 오갈 때는 TLS만으로는 부족하므로, ExecutionContext를 활용해 데이터를 흐르게(Flow) 한다.

&gt;1. 암묵적 데이터 전달
정적 필드를 수정하고, 호출된 메서드에서 이 값을 사용해 데이터를 전달하는 방식
2. Thread Local Data
\[ThreadStatic] 속성을 가진 정적 필드나 ThreadLocal&lt;T\&gt;를 이용하면 스레드별로 데이터를 분리



```csharp
var number = new AsyncLocal&lt;int&gt;();
number.Value = 1;
ThreadPool.QueueUserWorkItem(_ =&gt; Console.WriteLine(number.Value)); // 작업 수행 시 캡처된 1 출력

number.Value = 2;
Console.WriteLine(number.Value)); // 2 출력</code></pre><p>등록된 비동기 작업은 처리될 때 ExecutionContext이 존재한다면 복원을 한다. 그 이유는 스레드에서 실행했던 다른 작업의 ambient 데이터에 접근하는 것을 막기 위함이다. (각 비동기 작업이 독립적인 환경에서 수행될 수 있다.) </p>
<pre><code class="language-csharp">// 스레드 풀의 워커
if (ec is null)
{
    action();
}
else
{
    ExecutionContext.Run(ec, s =&gt; ((Action)s!)(), action);
}</code></pre>
<hr>
<blockquote>
<p>Iterator는 yield return이 있는 부분마다 다른 상태를 저장하는 코드로 변환된다. MoveNext() 메서드에서 각 상태에 대해 switch 문으로 다른 동작을 수행한다.
마찬가지로 비동기 메서드도 await을 만날 때마다 상태를 저장하고 후속 작업을 구분한다.</p>
</blockquote>
<h3 id="async-함수의-상태-머신-변환">async 함수의 상태 머신 변환</h3>
<p><code>async</code> 함수는 상태머신 구조체로 변환된다. 본래의 시그니처는 유지되고 본문은 <code>MoveNext()</code> 메서드로 재작성된다.</p>
<pre><code class="language-csharp">[AsyncStateMachine(typeof(CopyStreamToStreamAsyncStateMachine))]
public Task CopyStreamToStreamAsync(Stream sourceStream, Stream destinationStream)
{
    // 상태 머신 인스턴스 초기화
    CopyStreamToStreamAsyncStateMachine stateMachine = default;
    stateMachine.methodBuilder = AsyncTaskMethodBuilder.Create();
    stateMachine.source = sourceStream;
    stateMachine.destination = destinationStream;
    stateMachine.state = -1;
    stateMachine.methodBuilder.Start(ref stateMachine);
    return stateMachine.methodBuilder.Task;
}

private struct CopyStreamToStreamAsyncStateMachine : IAsyncStateMachine
{
    public int state;
    public AsyncTaskMethodBuilder methodBuilder;
    public Stream source;
    public Stream destination;
    private byte[] buffer;
    private TaskAwaiter writeAwaiter;
    private TaskAwaiter&lt;int&gt; readAwaiter;

    // MoveNext 메서드 등 나머지 구현부
}</code></pre>
<ul>
<li>Start() 함수는 내부에서 MoveNext를 호출한다. ExecutionContext 캡처/복원 포함</li>
<li>AsyncTaskMethodBuilder는 Task를 관리하고<ul>
<li>AwaitOnCompleted()/AwaitUnsafeOnCompleted()에서 continuation 등록한다.</li>
<li>밑에서 볼 코드는 필드로 ec가 관리되기 때문에 ec를 캡처하지 않는 AwaitUnsafeOnCompleted를 사용</li>
<li>메서드가 중단된 적이 있으면 stateMachine.methodBuilder.Task가 존재하여 이를 반환</li>
<li>중단된 적이 없으면 Task.CompletedTask를 반환해 비용 감소 <a href="#valuetask">ValueTask</a>)</li>
</ul>
</li>
</ul>
<blockquote>
<p>Task의 3가지 상태</p>
</blockquote>
<ul>
<li>success</li>
<li>failure</li>
<li>canceled
failure와 canceled 모두 소비자 코드에서 동일하게 exception을 처리하면 된다.</li>
</ul>
<hr>
<p>아래의 변환된 본문인 MoveNext를 보면 <code>await</code>으로 중단되는 지점에서 state를 변경하고 AwaitUnsafeOnCompleted를 호출한다.</p>
<ul>
<li>ExecutionContext는 암묵적으로 전달되며, await 키워드에서 작업을 재개할 때 유지 </li>
<li><code>state</code>를 통해서 어떤 위치(await)인지 파악하고, awaiter를 통해 비동기 작업의 완료 확인<blockquote>
<p>awaiter: 비동기 작업이 완료될 때 continuation이 실행되게 하는 역할을 가지며 GetResult()로 작업의 결과를 가져옴</p>
</blockquote>
</li>
</ul>
<pre><code class="language-csharp">private void MoveNext()
{
    try
    {
        int currentState = this.state; // 현재 상태를 저장하는 변수

        TaskAwaiter&lt;int&gt; readTaskAwaiter;
        if (currentState != 0)
        {
            if (currentState != 1)
            {
                // 버퍼 초기화
                this.buffer = new byte[4096];
                goto PerformReadAsync;
            }

            // 상태가 1인 경우: 이전에 대기 중이었던 읽기 작업(awaiter)을 복원
            readTaskAwaiter = this.readAwaiter;
            this.readAwaiter = default(TaskAwaiter&lt;int&gt;);
            currentState = (this.state = -1); // 상태를 업데이트
            goto AfterRead;
        }

        // 상태가 0인 경우: 이전에 대기 중이었던 쓰기 작업(awaiter)을 복원
        TaskAwaiter writeTaskAwaiter = this.writeAwaiter;
        this.writeAwaiter = default(TaskAwaiter);
        currentState = (this.state = -1); // 상태를 업데이트

        // 이전 쓰기 작업의 결과를 처리
        writeTaskAwaiter.GetResult();

    PerformReadAsync:
        // 스트림에서 비동기로 읽기 작업 수행
        readTaskAwaiter = source.ReadAsync(this.buffer, 0, this.buffer.Length).GetAwaiter();
        if (!readTaskAwaiter.IsCompleted)
        {
            // 작업이 완료되지 않은 경우 상태를 1로 업데이트하고 대기를 설정
            currentState = (this.state = 1);
            this.readAwaiter = readTaskAwaiter;
            this.methodBuilder.AwaitUnsafeOnCompleted(ref readTaskAwaiter, ref this);
            return;
        }

    AfterRead:
        // 읽기 작업이 완료된 경우 결과 처리
        int bytesRead;
        if ((bytesRead = readTaskAwaiter.GetResult()) != 0)
        {
            // 읽은 데이터를 대상에 비동기로 쓰기
            TaskAwaiter writeAwaiter = destination.WriteAsync(this.buffer, 0, bytesRead).GetAwaiter();
            if (!writeAwaiter.IsCompleted)
            {
                // 작업이 완료되지 않은 경우 상태를 0으로 업데이트하고 대기를 설정
                currentState = (this.state = 0);
                this.writeAwaiter = writeAwaiter;
                this.methodBuilder.AwaitUnsafeOnCompleted(ref writeAwaiter, ref this);
                return;
            }

            // 쓰기 작업이 완료된 경우 결과 처리
            writeAwaiter.GetResult();
            goto PerformReadAsync;
        }
    }
    catch (Exception exception)
    {
        // 예외 발생 시 상태를 -2로 설정하고 Task를 실패 상태로 설정
        this.state = -2;
        this.buffer = null;
        this.methodBuilder.SetException(exception);
        return;
    }

    // 메서드가 성공적으로 완료된 경우
    this.state = -2;
    this.buffer = null;
    this.methodBuilder.SetResult();
}</code></pre>
<ul>
<li>작업이 완료된 경우
완료된 결과를 동기적으로 가져와 코드를 계속 실행</li>
<li>미완료 시
AwaitUnsafeOnCompleted 메서드를 사용하여 continuation을 작업 대기열에 등록하고 메서드를 종료<ul>
<li>state machine이 ExecutionContext를 처리하므로 awaiter가 중복 처리할 필요 없음</li>
</ul>
</li>
<li>상태 전환
비동기 작업 중단 시점마다 state를 업데이트하여, 이후 비동기 작업이 완료되면 MoveNext()에 재진입하여 이어서 실행</li>
<li>예외처리
예외가 발생하면 catch 블록에서 상태를 -2(완료)로 설정하고 SetException을 통해 Task를 Faulted 상태로 설정</li>
</ul>
<blockquote>
<h4 id="상태-머신의-힙-복사">상태 머신의 힙 복사</h4>
</blockquote>
<ol>
<li>상태 머신은 처음에는 스택에 존재 </li>
<li>첫 await에서 중단될 때 힙으로 박싱</li>
<li>continuation이 해당 힙에 있는 상태 머신의 MoveNext 호출</li>
</ol>
<blockquote>
<p>메모리를 절약하기 위해 awaiter와 임시 변수를 줄여서 상태 머신의 필드 수를 줄일 수 있음</p>
</blockquote>
<h3 id="awaitunsafeoncompleted의-동작">AwaitUnsafeOnCompleted의 동작</h3>
<h4 id="net-framework에서의-비동기-동작">.NET Framework에서의 비동기 동작</h4>
<ol>
<li><p>ExecutionContext 캡처
비동기 메서드 중단 시점에 ExecutionContext.Capture()를 호출하여 현재 컨텍스트를 저장</p>
</li>
<li><p>MoveNextRunner 객체 생성
캡처한 ExecutionContext와 박싱된 상태 머신을 감싸는 MoveNextRunner 객체를 할당</p>
<ul>
<li>첫 중단 시점에서 상태 머신은 스택에 있으므로 박싱 필요</li>
</ul>
</li>
<li><p>Action 대리자 생성
MoveNextRunner.Run 메서드를 호출하는 Action 대리자를 생성하여 이후 continuation 실행 시 Action을 통해 상태 머신을 재개</p>
</li>
<li><p>상태 머신 박싱 및 빌더 연결
상태 머신 박싱 후, SetStateMachine 호출을 통해 빌더와 박싱된 상태 머신을 연결</p>
<ul>
<li>빌더는 상태 머신을 추적하고, 이후 중단 시점에서 박싱 여부 파악</li>
</ul>
</li>
<li><p>Continuation 등록</p>
<ul>
<li>생성된 Action 대리자는 awaiter의 UnsafeOnCompleted 메서드에 전달<ul>
<li>Task의 continuation 리스트에 저장</li>
</ul>
</li>
<li>Task 완료 시 Action이 실행<ul>
<li>MoveNextRunner를 통해 MoveNext()를 재개</li>
</ul>
</li>
</ul>
</li>
</ol>
<blockquote>
<p>할당 비용
복잡한 계층(ExecutionContext, MoveNextRunner, Action 등)을 매번 생성하므로 Task를 자주 중단할 경우 대량의 객체 할당 발생
예: 1천 번 Task.Yield() 호출 시 수백만 개 단위의 할당 발생 가능</p>
</blockquote>
<h4 id="net-core에서의-비동기-동작-최적화">.NET Core에서의 비동기 동작 최적화</h4>
<ol>
<li>AsyncStateMachineBox 활용
Task를 상속 받아 상태 머신, ExecutionContext를 저장<ul>
<li>별도의 MoveNextRunner나 Action 대리자 생성 없이 상태와 컨텍스트를 관리</li>
</ul>
</li>
</ol>
<ol start="2">
<li>Continuation 최적화
위 인스턴스가 존재하면 ExecutionContext를 덮어쓴 뒤, 문맥을 복원하고 MoveNext를 호출하는 Action을 생성</li>
</ol>
<p><strong>awaiter 최적화 경로</strong>
비동기 인프라에 속한 awaiter는 Action, QueueUserWorkItemCallback 생성 없이 IAsyncStateMachineBox를 직접:</p>
<ul>
<li>YieldAwaitable:  스레드 풀에 큐잉</li>
<li>TaskAwaiter: Task continuation 리스트에 추가합니다</li>
</ul>
<blockquote>
<p>할당 비용
동일한 예에서 할당이 약 1000개 수준으로 줄어듦</p>
</blockquote>
<ul>
<li>메서드가 중단되지 않으면 할당 없음</li>
<li>중단되더라도 단 한 번의 박싱(또는 AsyncStateMachineBox 생성)으로 상태 관리</li>
</ul>
<p>추가 최적화: .NET 6 및 C# 10
메서드 단위로 빌더를 재정의하여 최적화된 빌더(PoolingAsyncValueTaskMethodBuilder 등)를 사용할 수 있습니다.
이를 통해 극단적으로는 비동기 메서드 실행 중에도 추가 할당 없이 처리 가능
ValueTask 및 IValueTaskSource
ValueTask를 사용하면, 동기 완료 시 Task 할당을 피하거나, IValueTaskSource를 통해 객체 풀링을 활용하여 빈번한 비동기 호출에서도 최소한의 할당으로 높은 성능을 달성할 수 있습니다.</p>
<hr>
<h3 id="valuetask">ValueTask</h3>
<p>동기적으로 결과를 반환하는 경우 <code>Task</code> 객체를 매번 생성하는 것은 비효율적이다.</p>
<ul>
<li>Task.CompletedTask로 Task 기반 메서드가 완료된 상태를 표현할수 있지만</li>
<li>Task&lt;TResult&gt;는 결과가 다양하여 캐싱하기 어렵다.(bool은 가능하지만 int의 경우 가능한 값이 너무 많아 일부 범위로 제한 -1 ~ 8)</li>
<li>ValueTask는 동기적으로 결과를 반환할 수 있을 때 객체 할당 없이 결과를 제공하고, 비동기적으로 전환될 경우에만 객체를 할당한다.<ul>
<li>필드로 Task&lt;TResult&gt;를 참조하거나 TResult 값을 보관</li>
<li>결과를 알고 있다면 바로 값을 반환하고 그렇지 않은 경우 Task를 통해 나중에 결과를 제공</li>
</ul>
</li>
</ul>
<blockquote>
<p>예를 들어 <code>MemoryStream</code>의 경우 동일한 크기의 읽기 작업을 반복할 때, 이전에 성공적으로 완료된 <code>Task&lt;int&gt;</code> 결과를 재사용하는 식으로 최적화할 수 있다.</p>
</blockquote>
<blockquote>
<p>PoolingAsyncValueTaskMethodBuilder로 상태기계를 풀링하여 성능을 높일 수 있다.</p>
</blockquote>
<hr>
<h4 id="systemthreadingtaskssourcesivaluetasksourcetresult"><code>System.Threading.Tasks.Sources.IValueTaskSource&lt;TResult&gt;</code></h4>
<pre><code class="language-csharp">public interface IValueTaskSource&lt;out TResult&gt;
{
    ValueTaskSourceStatus GetStatus(short token);
    // continuation 연결
    void OnCompleted(Action&lt;object?&gt; continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags);
    // 작업 결과 확인
    TResult GetResult(short token);
}</code></pre>
<ul>
<li><code>ValueTask&lt;TResult&gt;</code>를 위한 사용자 정의 소스</li>
<li>소비자는 비동기 결과가 준비될 떄까지 대기하고, 준비되면 GetResult로 결과를 가져옴</li>
</ul>
<pre><code class="language-csharp">public readonly struct ValueTask&lt;TResult&gt;
{
   // private readonly Task&lt;TResult&gt;? _task;
   // Task&lt;TResult&gt; or null만 가능했으나 이제 IValueTaskSource&lt;TResult&gt; 가능
   private readonly object? _obj;
   private readonly TResult _result;
   ...
}</code></pre>
<hr>
<h3 id="synchronizaiontcontext">SynchronizaiontContext</h3>
<p>ExecutionContext와 동일하게 캡처한 뒤 후속 작업이 재개될 때 동일한 컨텍스트에서 실행</p>
<ul>
<li>WPF/Windows Forms: UI 스레드에서 실행 보장</li>
<li>ASP.NET: HTTP 요청 컨텍스트 유지</li>
<li>콘솔 앱: 기본적으로 제공되지 않아 null인 상태<pre><code class="language-csharp">// SynchronizationContext가 없으면 TaskScheduler가 결정
object scheduler = SynchronizationContext.Current;
if (scheduler is null &amp;&amp; TaskScheduler.Current != TaskScheduler.Default)
{
  scheduler = TaskScheduler.Current;
}</code></pre>
SynchronizationContext가 없는 경우 TaskScheduler를 통해 실행 위치를 결정한다. 기본 스케줄러는 스레드 풀을 사용한다.<blockquote>
<p>TaskScheduler는 Task의 실행 시간, 위치 제어</p>
</blockquote>
</li>
</ul>
<p>ConfigureAwait(false)</p>
<ul>
<li>컨텍스트를 캡처하지 않고 스레드 풀에서 후속 실행을 진행</li>
<li>컨텍스트 캡처 및 큐잉 오버헤드 감소<blockquote>
<p>라이브러리나 외부 코드가 내부적으로 커스텀 컨텍스트를 사용하면 생략할 수 없음
그렇지 않다면 생략해도 되기는 함</p>
</blockquote>
</li>
</ul>
<blockquote>
<p>ExecutionContext와 SynchronizationContex의 차이</p>
</blockquote>
<ul>
<li><strong>ExecutionContext</strong>: 비동기 경계를 지나며 ambient data를 지속적으로 유지. 어떤 스레드에서 실행하든 일관적인 정보 유지</li>
<li><strong>SynchronizationContext</strong>: 비동기 작업 재개 시 어떤 스레드(UI 스레드 등)에서 실행할 지 결정</li>
</ul>
<hr>
<blockquote>
<p>다음 내용을 쉽게 정리한 것</p>
</blockquote>
<ul>
<li><a href="https://devblogs.microsoft.com/dotnet/how-async-await-really-works/">https://devblogs.microsoft.com/dotnet/how-async-await-really-works/</a></li>
<li><a href="https://devblogs.microsoft.com/dotnet/configureawait-faq/">https://devblogs.microsoft.com/dotnet/configureawait-faq/</a></li>
<li><a href="https://devblogs.microsoft.com/pfxteam/executioncontext-vs-synchronizationcontext/">https://devblogs.microsoft.com/pfxteam/executioncontext-vs-synchronizationcontext/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JVM: garbage collector - Part 2]]></title>
            <link>https://velog.io/@oak_cassia/JVM-garbage-collector-2</link>
            <guid>https://velog.io/@oak_cassia/JVM-garbage-collector-2</guid>
            <pubDate>Tue, 12 Nov 2024 12:18:53 GMT</pubDate>
            <description><![CDATA[<h3 id="introduction">introduction</h3>
<p>In this post, I will delv into more specific details and examples of GC in the JVM. First, I&#39;ll explain the main concepts of GC and then examine how various garbage collectors operate.</p>
<h2 id="main-concepts">Main Concepts</h2>
<h3 id="root-node-enumeration">Root Node Enumeration</h3>
<p>This process involves searching for reference chains starting from the GC root set.</p>
<ul>
<li>Examples of GC roots include global refereces and variables in the excecution context.</li>
<li>To maintain consistency, this task is performed on a snapshot of the program state.</li>
</ul>
<h3 id="ordinary-object-pointer-map">Ordinary Object Pointer Map</h3>
<p>An OopMap stores information about references in the object&#39;s stack and registers once loading is complete. It includes details about which instructions modify references or OopMap data. Creating an OopMap for every instruction would be highly inefficent. Instead, OopMaps are only created for safe points, which are specific points in the code where the program may run for an extended time, such as during method calls, loops, or exception handling.</p>
<p>There are two methods for reaching safe points:</p>
<ul>
<li>Preemptive Suspension: User threads are interrupted and repeatedly checked until they reach a safe point.</li>
<li>Voluntary Suspension: Threads poll a flag during execution and voluntarily pause at the next safe point.</li>
</ul>
<blockquote>
<p>A polling mechanism can use a single assembly instruction. For example, preventing memory access triggers a memory protection trap, which throws an exception handled by an exception handler to temporarily suspend the thead.</p>
</blockquote>
<h3 id="safe-region">Safe Region</h3>
<p>A safe region is a state or area where:</p>
<ul>
<li>No references to objects are made.</li>
<li>No references to objects are modified.
When a thread is in a state like wating or ready, it cannot respond to interrupts. This is why the concept of a safe region is necessary. threads in a safe region do not need to be considered during GC.
Before a thread exits the safe region, it mus:</li>
</ul>
<ol>
<li>Ensure that root node enumeration has been completed</li>
<li>Check if any GC steps are still running that require the thread to pause.
Once thease checks are done, the thread can either resume execution or remain in a waiting state as needed.</li>
</ol>
<h3 id="memory-set-and-card-table">Memory Set and Card Table</h3>
<p>The goal of these techniques is to reduce the scope of root scans, which can be expensive when scanning references across generations.</p>
<p>Instead of scanning all references between generations, memory sets record only cross-generational references, allowing the GC to efficiently identify live objects.</p>
<p>A common appraoch to optimize this process is by reducing precision mapping records to memory blocks rather than individual objects.</p>
<ul>
<li>Dividing memroy into indexed blocks and implementing a byte array to track references allows the use of simple division operations to calculate indices.</li>
</ul>
<h3 id="write-barrier">Write Barrier</h3>
<p>If an object in another generation references a block in a differenct generation, the refernce is marked in the card table either before or after the reference occurs. While this increases the cost of updating references, it is still lower than scanning the entire generation.
False sharing can occur when physically different varibales are stored in the same cache line. Since the card table manages regions of 512 bytes(the size of card), false sharing can have a significant impact within the range of cache line size multiplied by the card size.</p>
<h3 id="tricolor-marking-algorithm">Tricolor Marking Algorithm</h3>
<p>The Garbage Collector can search the object graph after root node enumeration.</p>
<ul>
<li>White: Unreachable object</li>
<li>Black: Object that doesn&#39;t reference any white object</li>
<li>Gray: Reachable but the objects it references have not been checked yet</li>
</ul>
<ol>
<li>Mark a gray object as black and mark all objects it references as gray</li>
<li>Repeate until there are no gray objects left</li>
<li>White objects are released</li>
</ol>
<p>It is more critical to mistakenly consider a live object as dead than to consider a dead object as live. This situation can occur when a user thread adds a reference form a black object to a white object and simultaneously removes a reference form a gray object to a white object. Preventing just one of these two scenarios is sufficient to avoid this critical problem.</p>
<p>Incremental Update</p>
<ul>
<li>If a black object adds a reference to a white object, the change is recorded</li>
<li>After the concurrent scan is complete, the recorded black objects are scanned again</li>
<li>The white objects are marked as gray</li>
</ul>
<p>Snapshot at the Beginning</p>
<ul>
<li>If a reference from a gray object to a white object is removed, the change is recorded</li>
<li>After the concurrent scan is complete, the recorded gray objects (and their references) are scanned again</li>
<li>The white objects are marked as gray</li>
</ul>
<h2 id="how-garbage-collectors-operate">How Garbage Collectors Operate</h2>
<h3 id="g1">G1</h3>
<ul>
<li>Allocates memory in regions<ul>
<li>Large objects use multiple regions</li>
<li>Objects Larger than half the size of a region are considered huge</li>
</ul>
</li>
<li>Collect the most effective regions<ul>
<li>Effectivness is evaluated with greater weight given to recent data</li>
</ul>
</li>
<li>Adapts collection to allocation speed</li>
</ul>
<ol>
<li>Initial Marking<ul>
<li>User threads are paused</li>
<li>Snapshot at the beginning</li>
<li>Marks objects directly referenced by roots</li>
</ul>
</li>
<li>Concurrent Marking<ul>
<li>Analyze reachability from GC roots</li>
<li>Recursively marks obejcts</li>
</ul>
</li>
<li>Remarking<ul>
<li>User threads are paused</li>
<li>Rescan objects recorded during concurrent marking</li>
</ul>
</li>
<li>Copying and Cleanup</li>
</ol>
<ul>
<li>User thread are paused</li>
<li>Selects the most effective regions within the target pause time</li>
<li>Copies surviving objects and deletes the regions</li>
</ul>
<h3 id="shenandoah">Shenandoah</h3>
<ul>
<li>Improved version of G1</li>
<li>Memory is divided into regions</li>
<li>Use huge regions for large objects</li>
<li>collect regions with effectiveness</li>
<li>Doesn&#39;t pause user threds during collection</li>
<li>Doesn&#39;t distinguish generations</li>
<li>Doesn&#39;t use memory sets, uses a connection matrix<ul>
<li>$n \times m$ matrix tracks references between regions</li>
</ul>
</li>
</ul>
<h4 id="forwarding-pointers">Forwarding Pointers</h4>
<ul>
<li>Indirect pointers</li>
<li>Inefficient existing method<ul>
<li>Sets memory protections traps on original memory during evacuation</li>
<li>Triggers a trap on access and redirects to the new object, involving a costly kernel mode transition</li>
</ul>
</li>
<li>Adds a reference field at the top of the object layout (later moved to the object header)</li>
<li>Points to itself when not evacuated</li>
<li>Updates to the address upon evacuation</li>
<li>Avoids thread pausing when addresses change</li>
<li>Introduces overhead for indirect access</li>
</ul>
<h4 id="read-barriers">Read Barriers</h4>
<ul>
<li>Ensures correctness when user threads modify objects during the copying phase<ul>
<li>Synchronizes access to forwarding pointers, allowing only one thread at a time</li>
</ul>
</li>
<li>Load reference barrier: Triggers when reading writing reference-type data</li>
<li>Doesn&#39;t trigger for object comparisons, or object locks</li>
</ul>
<blockquote>
<p>사용자 스레드에서 읽기 장벽이 트리거 되면 <code>Forwarding pointer</code>를 확인 하고 객체 참조 업데이트</p>
</blockquote>
<h4 id="stack-watermark-and-concurrent-stack-processing">Stack Watermark and Concurrent Stack Processing</h4>
<ul>
<li>Purpose<ul>
<li>The top stack frame is being used by the user thread and shouldn&#39;t be accessed by the GC</li>
<li>The stack frames below it are safe for scanning</li>
</ul>
</li>
<li>At Runtime<ul>
<li>At the start of the GC process, a watermark is set at the top stack frame</li>
<li>The watermark ensures that the GC does not access to the top stack frame</li>
<li>If the user thread destroys top frame, it moves the watermark down by one frame</li>
<li>When the user thread leaves a safe point, the watermark ensures safety</li>
</ul>
</li>
</ul>
<h4 id="operation">Operation</h4>
<ol>
<li>Initial Marking<ul>
<li>marks objects directly referenced by roots</li>
<li>very short pause duration</li>
</ul>
</li>
<li>Concurrent Marking<ul>
<li>Analyzes reachability of objects from root</li>
</ul>
</li>
<li>Final Marking<ul>
<li>Completes pending marks</li>
<li>Rescan GC root set</li>
<li>Short pause duration</li>
</ul>
</li>
<li>Concurrent Cleanup<ul>
<li>Clean immediate garbage regions(regions with all dead objects)</li>
</ul>
</li>
<li>Concurrent Evacuation<ul>
<li>Copies objects for reclaimable regions to other regions</li>
<li>if user thread are paused, proceeds without barriers.</li>
<li>or not(running), uses read barrieres and *forwarding pointers</li>
<li>Time proportional to the number of references in memory</li>
</ul>
</li>
<li>Initial Reference Update<ul>
<li>Updates references pointing old addresses in the heap to new addresses after copying</li>
<li>User threads are paused</li>
<li>Ensure all threads complete evacuation</li>
</ul>
</li>
<li>Concurrent Reference Update<ul>
<li>Perform reference updates concurrently with user threads</li>
<li>Updates references linearly based on physical memory order</li>
</ul>
</li>
<li>Final Reference Update<ul>
<li>Updates references in the GC root set</li>
</ul>
</li>
<li>Reclaims regions in the reclaim set</li>
</ol>
<h3 id="zgc">ZGC</h3>
<ul>
<li>Memory is divided into regions</li>
<li>Region are categorized into large, medium, small<ul>
<li>Large regions can dynamically resize, and they can become smaller than medium regions</li>
</ul>
</li>
</ul>
<h4 id="colored-pointer">Colored Pointer</h4>
<ul>
<li><p>64bit system</p>
<ul>
<li>Pointers don&#39;t use the full address space</li>
<li>ZGC allocates 44 bits for addressing and reserves 4 extra bits for state indicator</li>
<li>Finalizable</li>
<li>Remapped</li>
<li>Marked1</li>
<li>Marked0</li>
</ul>
</li>
<li><p>Advantages</p>
<ul>
<li>Immediate euse of regions with surviving objects upon relocation</li>
<li>Eliminates the need for reference updates to moved objects</li>
</ul>
</li>
<li><p>Read Barriers</p>
<ul>
<li>Trigger: When user threads access an object</li>
<li>Detection: Identifies the remapped state via the color bits</li>
<li>Redirection<ul>
<li>Redirects the pointer to the new memory location</li>
<li>Uses forward mapping to update references seamlessly<h4 id="multi-mapping-memory">Multi Mapping Memory</h4>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Maps the same heap memory to three independent virtual address</p>
<ul>
<li>|(Remapped, Marked1, Marked0)| -----|</li>
</ul>
</li>
<li><p>Once all references to an old memory location are updated</p>
<ul>
<li>The original memory region is safely reclaimed</li>
</ul>
</li>
<li><p>ZGC creates new memory regions for relocated objects by creating new cirtual memory aliases</p>
</li>
<li><p>Original mappings remain accessible, ensuring uninterrupted user thread access</p>
</li>
</ul>
<h4 id="operation-1">Operation</h4>
<ol>
<li>Marking Start<ul>
<li>Same as G1 and Shenandoah</li>
</ul>
</li>
<li>Concurrent Marking<ul>
<li>Pause</li>
<li>Update root set marking to color pointers</li>
<li>Updates the Marked0 and Marked1 bits</li>
</ul>
</li>
<li>Concurrent Relocation Preparation:<ul>
<li>Identifies regions to clean up and forms a relocation set</li>
<li>Scans all regions during every collection cycle</li>
<li>Decides whether to reclaim or retain regions after copying survivors</li>
<li>Handles class unloading and weak reference processing</li>
</ul>
</li>
<li>Concurrent Relocation<ul>
<li>Copies survivors from the relocation set to new memory locations</li>
<li>Records old and new object addresses in the forward table for the region</li>
<li>Determines relocation based on colored pointer reference to the relocation set</li>
<li>Implements self-healing:</li>
<li>Application threads accessing relocated objects read the forward table via memory barriers</li>
<li>Updates references to point to the new address (occurs only on the first access)</li>
<li>Colored pointers enable immediate region reuse post-copying, though forward tables remain until references are updated</li>
</ul>
</li>
<li>Concurrent Remapping<ul>
<li>Updates all references to old objects within the heap to point to relocated objects</li>
<li>Deferred to the next collection cycle, leveraging marking scans for efficiency</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JVM: garbage collector]]></title>
            <link>https://velog.io/@oak_cassia/JVM-garbage-collector</link>
            <guid>https://velog.io/@oak_cassia/JVM-garbage-collector</guid>
            <pubDate>Tue, 10 Sep 2024 14:26:47 GMT</pubDate>
            <description><![CDATA[<h3 id="introduction">introduction</h3>
<p>I didn&#39;t like garbage collection before i started working. i thought GC made programs slower. However, after getting a job and gaining more experience with coding, i realized how convenient GC actually is. Not every program needs to run at top speed, and GC helps improve productivity by allowing me to focus on more important tasks instead of manually managing memory. Nowadays, I appreciate GC because it simplifies code and improves readability. While i no longer need to release memory manually, it&#39;s still important to understand what happens when objects become unused.</p>
<h3 id="how-to-check-if-objects-become-unused">How to check if objects become unused</h3>
<p>There are two main algorithms for dertermining if objects are no longer in use: reference counting and reachability analysis.</p>
<h4 id="reference-counting">reference counting</h4>
<p>this method is similar to smart pointers in C++.</p>
<ul>
<li>When a reference is created the counter increases by one.</li>
<li>When a reference is removed, the counter decreases by one.</li>
<li>If the counter is reaches 0, the object is consider unused.</li>
<li>Just like in C++, circular references can cause issues.<h4 id="reachability-analysis">reachability analysis</h4>
</li>
<li>The process starts from GC roots and traverses references, creating a reference chain.<ul>
<li>GC roots are objects referenced by the JVM stack</li>
<li>or JNI, internal JVM references, objects locked for synchronization, objects reflecting the JVM state via JMXBEAN</li>
</ul>
</li>
<li>Objects that do not form a reference chain are considered unused
Objects without a reference chain are marked to determine if they need the <code>finalize</code> method to be called. If so, they are placed in the <code>f-queue</code>. After <code>finalize</code> is called, if they still have no refernce chain, they are collected. Objects that do not need finalization are also collected.</li>
</ul>
<h4 id="typse-of-references">typse of References</h4>
<ol>
<li>Strong Reference: A direct reference to an object.</li>
<li>Soft Reference: If only soft references remain, the object is added to the list for collection right berfore memroy overflow occurs.</li>
<li>Weak Reference: The object is collected in the next garbage collection cycle.</li>
<li>Phantom Reference: Used for notification purposes.</li>
</ol>
<blockquote>
<p>In the method area, which is similar to the text area, constants and classes can be collected. String objects referncing literals are collected when no longer referenced. Classes are collected when three conditions are met: there are no instances of the class, the class loader has been collected, and there are no references to the class(including <code>java.lang.Class</code> refereces or reflection).</p>
</blockquote>
<h4 id="generation">generation</h4>
<ul>
<li>Every object dies early</li>
<li>The objects that survive tend to live longer</li>
</ul>
<p>Then, we separate them into new and old objects. New objects are frequently collected, while old objects are only collected when necessary. GC needs to check for references from old objects to new ones, even when it intends to collect only the new generation. However, since cross-generational references between old and new generations are rare, it&#39;s inefficient to scan the entire old generation or record every reference. Instead, it typically checks for references from the old generation to specific memory sets.</p>
<h4 id="basic-algorithm-for-garbage-collection">basic algorithm for garbage collection</h4>
<ol>
<li><p>Mark-Sweep
Mark objects that should either be collected or survive, then collect the rest. If the heap contains many objects, this approach can be inefficient, and it may lead to significant memory fragmentation.</p>
</li>
<li><p>Mark-Copy
Separate memory into two blocks. Record objects in one block. After collection, copy surviving objects to the other block. 
The new generation garbage collection uses this algorithm. For efficiency, memory is divided into one large block and two smaller blocks. During memory allocation, only the large block and one small block are used. After collection, survivors are copied to the other small block. However, if the surviving objects do not fit into the available space, they are directly added to the old generation.</p>
</li>
<li><p>Mark-Compact
Similar to Mark-Sweep, objects are marked for collection. However, after marking, surviving objects are compacted to one side of memory.
The old-generation garbage collection uses this algorithm. The compaction process is expensive, so it isn&#39;t done every time. Instead, it is deferred until memory fragmentation becomes severe enough to impact memory allocation.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JVM: object life cycle]]></title>
            <link>https://velog.io/@oak_cassia/JVM-object-life-cycle</link>
            <guid>https://velog.io/@oak_cassia/JVM-object-life-cycle</guid>
            <pubDate>Mon, 09 Sep 2024 13:53:55 GMT</pubDate>
            <description><![CDATA[<h3 id="introduction">introduction</h3>
<p>I prefer to study topics in the following order. First, I like to learn about memory structure, understanding what happens form the moment an object is created untill is deleted. Once i grasp the basics of memory, I naturally become curious about how objects are created and destroyed. After that, it leads to an exploration of how to garbage collection works to handle unused object.</p>
<h3 id="create-object">create object</h3>
<p>When the <code>new</code> keyword is encountered</p>
<ol>
<li>The system chekcs if the parameter references a class that is a symbolic instance in the constant pool.</li>
<li>it verifies whether the class has been loaded, resolved and initailized. if not, the system peforms these steps.</li>
</ol>
<p>Once the class is loaded, the memory size required is determined. there are two ways to handle memory alloation:</p>
<ol>
<li>Usign a pointer that points to the boundary between used and available memory.</li>
<li>Managing a free list of memory blocks to allocate space.</li>
</ol>
<p>In a multi-threaded environment, concurrency problems can occur during object creation. To handle this, there are two approaches:</p>
<ol>
<li>Synchronize memory allocation using atomic operations</li>
<li>Use Thread-Local Allocation Buffers (TLAB), where each thread manages its own private memory space.</li>
</ol>
<p>After memory is allocated, the follwing steps occur:</p>
<ul>
<li>The allocated memory is initialized by filling it with zeros.</li>
<li>The object header is set.</li>
<li>the constructor is called.</li>
</ul>
<h3 id="object-layout">object layout</h3>
<p>An object consists of three parts: the header, instance data, and alignment padding.</p>
<h4 id="header">header</h4>
<p>the header includes the mark word and klass word:</p>
<ul>
<li>mark word<ul>
<li>Its size is 32 or 64 bits.</li>
<li>The data structure can change dynamically to reuse space depending on the object&#39;s stae</li>
<li>It stores the object&#39;s hash code in <strong>25 bits</strong>.</li>
<li><strong>4 bits</strong> are used to indicate the object&#39;s generation</li>
<li><strong>1 bit</strong> is just reserved.</li>
<li>The last <strong>2 bits</strong> represent the lock status flag.</li>
</ul>
</li>
<li>klass word:<ul>
<li>A pointer that refers to the class&#39;s metadata.</li>
<li>The JVM uses this class pointer to recognize the object&#39;s class and determine its size from the metadata</li>
<li>For arrays, it stores the array length, which helps in calculating the memory size.<h4 id="instance-data">instance data</h4>
The instance data contains the fields of the object, including fields inherited from the parent class.The order of fields typically follows this pattern</li>
</ul>
</li>
</ul>
<ol>
<li>long, double</li>
<li>int</li>
<li>short, char</li>
<li>byte, boolean</li>
<li>pointer<blockquote>
<p>If the lengths of the fields are the same, the fields of the parent class are placed first.</p>
</blockquote>
</li>
</ol>
<h4 id="alignment-padding">alignment padding</h4>
<p>The size of every object must be a multiple of 8 bytes.</p>
<h3 id="access-to-object">access to object</h3>
<p>Objects can be accessed either through a handle or a direct pointer.</p>
<h4 id="handle">handle</h4>
<ul>
<li>A reference in the stack points to a handle in the handle pool.</li>
<li>The handle pool exists in the heap.</li>
<li>The handle contains references to both the instacne data and the type data.</li>
<li>The reference in the stack remains the same, even after garbagte collection, as the handle adderess does not change.</li>
<li>However, the references in the handle pool may change after GC.<h4 id="pointers">pointers</h4>
</li>
<li>Similar to a v-table in C++, the direct pointer holds a reference to the type data and the instace data.</li>
</ul>
<blockquote>
<p>Next, I&#39;ll study garbage collection</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JVM: runtime data areas]]></title>
            <link>https://velog.io/@oak_cassia/JVM-runtime-data-areas</link>
            <guid>https://velog.io/@oak_cassia/JVM-runtime-data-areas</guid>
            <pubDate>Thu, 05 Sep 2024 15:11:33 GMT</pubDate>
            <description><![CDATA[<h3 id="introduction">introduction</h3>
<p>I don&#39;t like the Java (I don&#39;t hate it either), but I enjoy learning about low-level workings. So, I decided to study JVM.</p>
<p>In university, I learned about memory concepts such as heap, stack, data, and text. Recently, while studying the V8 engine, I realized that JavaScript, which I previously underestimated (it was my misunderstanding), operates in a more complex way. Understanding the V8 engine helped me see similarities between its garbage collection and memory structure and that of the CLR. This insight led me to explore the JVM further; if I learn how specific things work, it will help me understand other concepts as well. Additionally, I plan to create a server for a service using Kotlin.</p>
<h3 id="runtime-data-areas">runtime data areas</h3>
<p>The JVM&#39;s memory structure consists of several key components: the program counter (PC), JVM stack, native method stack, heap, and method area (including the runtime constant pool).</p>
<h4 id="program-counter">program counter</h4>
<ul>
<li>The PC indicates the address of the next instruction to be executed</li>
<li>Each thread has its own independent PC</li>
<li>During context switches, the current execution point is saved</li>
<li>When executing a native method, the PC is set to <code>Undefined</code></li>
</ul>
<h4 id="jvm-stack">JVM stack</h4>
<ul>
<li>A new stack frame is created each time a Java method is invoked, contaning the local variable table, operand stack, dynamic link, and return value information</li>
<li>The JVM stack is thread-private and has the same lifespan as the thread</li>
<li>The local variable table holds basic data types, object references, and return types. These are stored in local variable slots, each 32 bit in size. if a data type exceeds 32 bits, it uses multiple slots</li>
<li>The size of the local variable table is determined at compile time and remains fixed during method execution<h4 id="native-method-stack">native method stack</h4>
</li>
<li>Similar to the JVM stack, the native method stack is used for executing native methods.
Heap<h4 id="heap">heap</h4>
</li>
<li>The heap is shared among all threads</li>
<li>It stores object instances and arrays</li>
<li>Memory allocation in the heap mus be logically consecutive<blockquote>
<p>A more detailed examination of garbage collection will be covered later.</p>
</blockquote>
</li>
</ul>
<h4 id="method-area">method area</h4>
<ul>
<li>The method area is also shared among threads</li>
<li>It holds type data, constants, static variable, and code cached by the JIT compiler</li>
<li>it resemble the text area in some respects</li>
<li>Garbage Collection is infrequent in this area, focusing mainly on the constants pool and types</li>
</ul>
<h4 id="method-arearuntime-constant-pool">method area.runtime constant pool</h4>
<ul>
<li>This pool stores meta data about class files when they are loaded by the JVM</li>
</ul>
<h4 id="direct-memory">direct memory</h4>
<ul>
<li>Direct memory is not part of JVM runtime</li>
<li>It allocates physical memory directly</li>
<li>It allows sharing of memory without copying data between the Java heap and native heap</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[js study #객체가 프로퍼티를 저장하는 방식]]></title>
            <link>https://velog.io/@oak_cassia/js-study-%EA%B0%9D%EC%B2%B4%EA%B0%80-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0%EB%A5%BC-%EC%A0%80%EC%9E%A5%ED%95%98%EB%8A%94-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@oak_cassia/js-study-%EA%B0%9D%EC%B2%B4%EA%B0%80-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0%EB%A5%BC-%EC%A0%80%EC%9E%A5%ED%95%98%EB%8A%94-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Thu, 22 Feb 2024 15:15:59 GMT</pubDate>
            <description><![CDATA[<p>다음의 데이터는 각각 분리된 공간에 저장</p>
<ul>
<li>다양한 사용 패턴에 대한 속성/요소 효율적 추가/액세스 가능</li>
<li>Elements: <code>[1, 2, 3]</code><ul>
<li>정수형 인덱스로 위치 파악 가능</li>
<li>메모리 절약 위해 sparse dictionary 사용 가능</li>
<li>Array-indexed properties: elements store에 저장</li>
</ul>
</li>
<li>Properties: <code>[a: &#39;hello&#39;, b: &#39;world&#39;]</code><ul>
<li>키로 위치 파악 어려움</li>
<li>HiddenClass 사용</li>
<li>Named properties: properties store에 저장</li>
</ul>
</li>
<li>Elements와 properties는 각각 배열이나 딕셔너리(해쉬 테이블)에 저장될 수 있음<h3 id="hidden-class">Hidden Class</h3>
</li>
<li>객체의 구조와 속성의 메타 데이터를 저장하는 구조<blockquote>
<p>V8 엔진에서 객체의 첫 번째 필드는 히든 클래스를 가리키는 포인터</p>
</blockquote>
</li>
<li>히든 클래스에 포함되는 정보<ul>
<li>프로퍼티 개수 (세 번째 필드)</li>
<li>프로토타입 참조</li>
<li>Descriptor Array 포인터 </li>
</ul>
</li>
<li>Descripitor Array는 프로퍼티 이름과 위치 저장<ul>
<li>length</li>
<li>EnumCache</li>
<li>key</li>
<li>details (enumerable, configurable, writeable)</li>
</ul>
</li>
<li>동일한 구조를 가진 객체는 동일한 히든 클래스 공유</li>
<li>새로운 속성이 추가 시 히든 클래스 변경<ul>
<li>기존 히든 클래스는 보존되어 transition tree 형성</li>
<li>같은 transition tree 내의 히든 클래스는 Descriptor Array 공유</li>
<li>Transition Array에는 형제 히든 클래스로 분기하기 위한 property name을 저장</li>
</ul>
</li>
</ul>
<h3 id="named-properties">Named Properties</h3>
<ul>
<li>딕셔너리는 인라인 캐시를 방해<h4 id="in-object-properties">In-object properties</h4>
</li>
<li>객체 내에 저장되는 프로퍼티<ul>
<li>개수는 초기 크기에 의해 결정</li>
<li>객체의 공간을 초과하는 추가 속성은 properties store에 저장<h4 id="fast-properties">Fast Properties</h4>
</li>
</ul>
</li>
<li>선형 시간에 접근 가능한 경우 일반적으로 빠르다고 판단</li>
<li>properteis store 내의 Fast properties는 인덱스로 접근 가능</li>
<li>히든 클래스의 descriptor array를 참조하여 실제 위치에 접근</li>
</ul>
<h4 id="slow-properties">Slow Properties</h4>
<ul>
<li>객체에 많은 속성이 추가되거나 삭제되면 히든 클래스 및 descriptor array의 유지에 오버헤드가 발생</li>
<li>이런 경우에는 딕셔너리가 사용</li>
<li>메타데이터는 descriptor array가 아니라 직접 딕셔너리에 저장<ul>
<li>각 항목은 key, value, details로 구성<ul>
<li>details (enumerable, configurable, writeable)</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>인라인 캐시?</p>
</blockquote>
<h3 id="elements-array-indexed-properties">Elements, Array-indexed Properties</h3>
<h4 id="packed--holey-elements">Packed &amp; Holey Elements</h4>
<ul>
<li>배열에서 중간 원소를 삭제하거나 정의하지 않은 경우 빈 공간 존재</li>
<li>비어 있는 element가 있으면 프로토타입 체인을 확인하여 속성 검색</li>
<li><code>_hole</code> 값은 존재하지 않는 속성을 표시하는 특별한 값<ul>
<li><code>_hole</code> 값이 없는 경우 배열은 packed된 상태<ul>
<li>프로토타입 체인을 확인할 필요 없음</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="fast--dictionary-elements">Fast &amp; Dictionary Elements</h4>
<ul>
<li><strong>Fast elements</strong>: 요소 인덱스가 elements store의 인덱스와 직접 매핑되어 빠른 접근 속도를 제공</li>
<li><strong>Dictionary elements</strong>: 메모리를 절약하기 위해 사용되는 방식이며, 큰 희소 배열이나 구멍이 많은 배열에 적합</li>
<li>키 - 값 - 디스크립터 저장</li>
<li>사용자 정의 설명자를 정의할 때 히든 클래스에 설명자 세부 정보를 저장할 수 없어 dictionary(slow) elements를 사용</li>
</ul>
<ul>
<li><p>정수만 배열에 저장하면 GC는 array를 탐색할 필요 없음 (small integers로 인코딩 됨)</p>
<blockquote>
<p>Smis: small integer, 최하위 비트를 0로 두어 포인터와 구분 하는 정수
32비트 환경에서는 [31비트 signed int] 0
64비트 환경에서는 [32비트 signed int] [31비트 0 패딩] 0</p>
</blockquote>
</li>
<li><p>Double만 배열에 저장하면 raw double 값으로 저장됩니다.</p>
<blockquote>
<p>Doubles: 부동 소수점 숫자 및 Smi로 표현할 수 없는 정수
기존 부동 소수점 숫자는 몇 개의 워드를 차지하는 객체</p>
</blockquote>
</li>
</ul>
<h4 id="elementaccessor">ElementAccessor</h4>
<ul>
<li>20가지의 요소 유형에 대해 Array 함수를 개별적으로 작성하는 것을 방지</li>
<li>Array 함수 호출 시 ElementAccessor를 통해 특정 유형에 맞는 함수 버전으로 디스패치</li>
<li>backing store에서 elements에 접근하는 간단한 함수만 구현</li>
</ul>
<blockquote>
<p>typed array, string wrapper에 해당하는 배열은 어떻게?</p>
</blockquote>
<h3 id="hidden-class에-덧-붙이는-내용">hidden class에 덧 붙이는 내용</h3>
<ul>
<li>JavaScript에서 class는 syntax sugar</li>
<li>Hidden class는 어떻게 구현되고 동작할까<pre><code class="language-javascript">function Peak(name, height, extra) {
  this.name = name;
  this.height = height;
  if (isNaN(extra)) {
      this.experience = extra;  
  } else {    
      this.prominence = extra;  
  }
}
</code></pre>
</li>
</ul>
<p>m1 = new Peak(&quot;Matterhorn&quot;, 4478, 1040);
m2 = new Peak(&quot;Wendelstein&quot;, 1838, &quot;good&quot;);</p>
<pre><code>이 때 히든 클래스의 transition은
1. 프로퍼티가 없는 hidden class
2. name
3. name, height
4. name, heigt, promonence / name, height, experience

이 상태에서 7개의 peak 객체를 만들면 3개의 in-object property가 있고 추가적인 프로퍼티는 in-object로 둘 수 없다. 객체의 프로퍼티 backing store에 저장될텐데 이건 위에서 본 프로퍼티 value들의 배열이다. 인덱스는 앞서 봤듯 Descriptor Array에서 가져온다.

이 때 m2 변수에 프로퍼티를 추가를 한 뒤 V8의 관점에서 hidden class들을 보면 프로퍼티들은 모두 const로 보일 텐데 이는 생성자 이후로 아무것도 변경되지 않았기 때문이다.
TurboFan은 이런 상황을 좋아한다. 
어떤 함수가 m2를 constant global로 참조한다면 m2.cost 는 컴파일 타임에 알 수 있다.
cost는 backing stroe에 저장되어 있을 것이다. (0번 인덱스에)
``%DebugPrint(m2)`:`

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[js study # JS(V8) Set, Map 동작 원리]]></title>
            <link>https://velog.io/@oak_cassia/js-study</link>
            <guid>https://velog.io/@oak_cassia/js-study</guid>
            <pubDate>Thu, 22 Feb 2024 15:15:17 GMT</pubDate>
            <description><![CDATA[<h1 id="map">Map</h1>
<ul>
<li><p>key - value 쌍의 자료구조</p>
</li>
<li><p>어떻게 동작할까?</p>
<blockquote>
<p>ES6: Map object must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements in the collection.</p>
</blockquote>
<ul>
<li>상수 시간복잡도를 갖는 <strong>해시 테이블</strong> 또는 다른 메카니즘으로 구현</li>
</ul>
</li>
</ul>
<h2 id="node의-경우">Node의 경우</h2>
<ul>
<li>21.6.1(release)<ul>
<li>V8(11.8.172.17)</li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">// v8/src/objects/js-collection.h

// The JSMap describes EcmaScript Harmony maps
class JSMap : public TorqueGeneratedJSMap&lt;JSMap, JSCollection&gt; {
 public:
  static void Initialize(Handle&lt;JSMap&gt; map, Isolate* isolate);
  static void Clear(Isolate* isolate, Handle&lt;JSMap&gt; map);
  void Rehash(Isolate* isolate);

  // Dispatched behavior.
  DECL_PRINTER(JSMap)
  DECL_VERIFIER(JSMap)

  TQ_OBJECT_CONSTRUCTORS(JSMap)
};</code></pre>
<ul>
<li><code>Rehash</code> 함수를 보아 해시 테이블을 사용한 것을 알 수 있습니다.</li>
</ul>
<h3 id="orderedhashmap"><a href="https://github.com/v8/v8/blob/11.8.172/src/objects/ordered-hash-table.cc">OrderedHashMap</a></h3>
<ul>
<li><a href="https://wiki.mozilla.org/User:Jorend/Deterministic_hash_tables">deterministic hash tables</a> 알고리즘을 구현한 컨테이너</li>
<li>각 항목은 힙에서 별도로 할당 X<ul>
<li>삽입 순서로 dataTable에 저장</li>
<li>Cpp의 unordered_map 과 대조</li>
</ul>
</li>
<li>삭제 시 체인은 건드리지 않고 키의 값만 수정</li>
</ul>
<p><strong>구조</strong></p>
<ul>
<li>Entry : 저장 단위<ul>
<li>key: 키</li>
<li>Valeu: valeu</li>
<li>chain: 다음 요소의 주소 (체이닝에 사용)</li>
</ul>
</li>
<li>CloseTable : 해시 테이블<ul>
<li>hashTable: 버킷 주소</li>
<li>dataTable: Entry가 저장되는 곳</li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">struct Entry {
    Key key;
    Value value;
    Entry *chain;
}

class CloseTable {
    Entry*[] hashTable;  // array of pointers into the data table
    Entry[] dataTable;
}</code></pre>
<h3 id="삽입">삽입</h3>
<ul>
<li>삽입된 순서로 dataTable에 저장<ul>
<li>Map의 순회는 결정적이게 됨</li>
</ul>
</li>
<li>해시 함수: modulo 2</li>
<li>삽입 순서: 1, 2, 3, 4</li>
</ul>
<pre><code class="language-json">closeTable : {
    hashTblae: [4, 3], // key 가라키는 주소
    dataTable: [
        {
            // bucket: 1, key로 계산하여 산출하기 때문에 주석 처리
            key: 1,
            value: &quot;1&quot;,
            chain: -1,
        },
        {
            // bucket: 0,
            key: 2,
            value: &quot;2&quot;,
            chain: -1,
        },
        {
            // bucket: 1,
            key: 3,
            value: &quot;3&quot;,
            chain: 1,
        },
        {
            // bucket: 0,
            key: 4,
            value: &quot;4&quot;,
            chain: 2,
        },
    ],
    live_element_size: 4,
  deleted_element_size: 0
}</code></pre>
<h3 id="크기-초과-시">크기 초과 시</h3>
<ul>
<li>초기값<ul>
<li>버킷 크기 = 2</li>
<li>capacity = 4 = 2 * 버킷 크기</li>
</ul>
</li>
<li>capacity 가 다 차면 + 삭제된 요소가 절반 미만이면<ul>
<li>버킷 크기 * 2</li>
</ul>
</li>
<li>capctiy 가 다 차면 + 삭제 요소 절반 이상<ul>
<li>삭제 요소 삭제 (ㅋㅋ)</li>
</ul>
</li>
<li>테이블 재할당</li>
<li>리해싱</li>
</ul>
<h3 id="삭제">삭제</h3>
<ul>
<li>체인은 건드리지 않고 키 값 수정</li>
<li>삭제 순서: 1, 2</li>
</ul>
<pre><code class="language-json">closeTable : {
    hashTblae: [4, 3],
    dataTable: [
        {
            // bucket: 1,
            key: 1,
            value: &quot;1&quot;,
            chain: -1,
        },
        {
            // bucket: 2,
            key: 2,
            value: &quot;2&quot;,
            chain: -1,
        },
        {
            // bucket: 3,
            key: 3,
            value: &quot;3&quot;,
            chain: 1,
        },
        {
            // bucket: 1,
            key: 4,
            value: &quot;4&quot;,
            chain: 2,
        },
    ],
    live_element_size: 2,
  deleted_element_size: 2
}</code></pre>
<h2 id="참고용-코드">참고용 코드</h2>
<ul>
<li><p>알고리즘</p>
<pre><code class="language-cpp">  void CloseTable::set(KeyArg key, ValueArg value)
  {
      // 1. 주어진 키의 해시값 계산
      hashcode_t h = hash(key);

      // 2. 키를 이용하여 해시 테이블에서 해당 키의 엔트리 찾기
      Entry *e = lookup(key, h);

      // 3. 찾은 엔트리가 존재하면 값을 업데이트하고 종료
      if (e) {
          e-&gt;value = value;
      } else {
          // 4. 찾은 엔트리가 없으면 새 엔트리를 추가
          if (entries_length == entries_capacity) {
              // 5. 테이블의 삭제된 엔트리가 1/4 이상이면 재해시, 아니면 테이블 확장
              rehash(live_count &gt;= entries_capacity * 0.75 ? (table_mask &lt;&lt; 1) | 1 : table_mask);
          }

          // 6. 해시값을 현재 테이블 크기에 맞게 조정
          h &amp;= table_mask;

          // 7. 새로운 엔트리 생성 및 초기화
          live_count++;
          e = &amp;entries[entries_length++];
          e-&gt;key = key;
          e-&gt;value = value;
          e-&gt;chain = table[h];

          // 8. 새로운 엔트리를 해시 테이블에 추가
          table[h] = e;
      }
  }

  bool CloseTable::remove(KeyArg key)
  {
      // 1. 주어진 키를 이용하여 엔트리 찾기
      Entry *e = lookup(key, hash(key));

      // 2. 찾은 엔트리가 없으면 false 반환
      if (e == NULL)
          return false;

      // 3. 찾은 엔트리가 있으면 엔트리 제거 및 빈 엔트리로 만들기
      live_count--;
      makeEmpty(e-&gt;key); // key = 0 

      // 4. 삭제된 엔트리가 많으면 테이블 축소
      if (table_mask &gt; initial_buckets() &amp;&amp; live_count &lt; entries_length * min_vector_fill())
          rehash(table_mask &gt;&gt; 1);

      // 5. 성공적으로 엔트리 제거되었음을 반환
      return true;
  }

  void CloseTable::rehash(size_t new_table_mask)
  {
      // 1. 새로운 테이블 용량 계산
      size_t new_capacity = size_t((new_table_mask + 1) * fill_factor());

      // 2. 새로운 해시 테이블 및 새로운 엔트리 배열 생성
      EntryPtr *new_table = new EntryPtr[new_table_mask + 1];
      memset(new_table, 0, (new_table_mask + 1) * sizeof(EntryPtr));
      Entry *new_entries = new Entry[new_capacity];

      // 3. 기존 엔트리를 새로운 엔트리 배열로 이동
      Entry *q = new_entries;
      for (Entry *p = entries, *end = entries + entries_length; p != end; p++) {
          if (!isEmpty(p-&gt;key)) {
              hashcode_t h = hash(p-&gt;key) &amp; new_table_mask;
              q-&gt;key = p-&gt;key;
              q-&gt;value = p-&gt;value;
              q-&gt;chain = new_table[h];
              new_table[h] = q;
              q++;
          }
      }

      // 4. 기존 테이블 및 엔트리 배열 삭제 및 새로운 테이블 및 엔트리 배열로 교체
      delete[] table;
      delete[] entries;
      table = new_table;
      table_mask = new_table_mask;
      entries = new_entries;
      entries_capacity = new_capacity;
      entries_length = live_count;
  }</code></pre>
</li>
<li><p><a href="https://github.com/v8/v8/blob/main/src/objects/ordered-hash-table.h">V8</a></p>
<pre><code class="language-cpp">  MaybeHandle&lt;OrderedHashSet&gt; OrderedHashSet::Add(Isolate* isolate,
                                                  Handle&lt;OrderedHashSet&gt; table,
                                                  Handle&lt;Object&gt; key) {
    // 1. 해시값 계산 및 중복 체크를 위해 GC 비활성화
    int hash;
    {
      DisallowGarbageCollection no_gc;
      Tagged&lt;Object&gt; raw_key = *key;
      Tagged&lt;OrderedHashSet&gt; raw_table = *table;
      // 키의 해시값 계산 또는 기존 해시값 가져오기
      hash = Object::GetOrCreateHash(raw_key, isolate).value();
      // 테이블에 이미 버킷이 있는 경우 중복 체크 수행
      if (raw_table-&gt;NumberOfElements() &gt; 0) {
        int raw_entry = raw_table-&gt;HashToEntryRaw(hash);
        // 버킷 체인을 따라가며 중복된 키가 있는지 확인
        while (raw_entry != kNotFound) {
          Tagged&lt;Object&gt; candidate_key =
              raw_table-&gt;KeyAt(InternalIndex(raw_entry));
          // 이미 존재하는 키라면 추가하지 않고 현재 테이블 반환
          if (Object::SameValueZero(candidate_key, raw_key)) return table;
          raw_entry = raw_table-&gt;NextChainEntryRaw(raw_entry);
        }
      }
    }

    // 2. 테이블 용량 조절을 위해 GC 비활성화
    MaybeHandle&lt;OrderedHashSet&gt; table_candidate =
        OrderedHashSet::EnsureCapacityForAdding(isolate, table);
    // 용량 조절에 실패한 경우 예외 처리
    if (!table_candidate.ToHandle(&amp;table)) {
      CHECK(isolate-&gt;has_pending_exception());
      return table_candidate;
    }

    // 3. GC 비활성화 및 테이블 수정을 위해 원시 테이블 가져오기
    DisallowGarbageCollection no_gc;
    Tagged&lt;OrderedHashSet&gt; raw_table = *table;
    // 기존 버킷 값 및 엔트리 정보 읽기
    int bucket = raw_table-&gt;HashToBucket(hash);
    int previous_entry = raw_table-&gt;HashToEntryRaw(hash);
    int nof = raw_table-&gt;NumberOfElements();

    // 4. 새로운 엔트리 추가
    // 새 엔트리는 테이블의 끝에 위치하게 됨
    int new_entry = nof + raw_table-&gt;NumberOfDeletedElements();
    int new_index = raw_table-&gt;EntryToIndexRaw(new_entry);
    raw_table-&gt;set(new_index, *key);
      // kChainOffset = entrysize : Set(1), Map(2)
    raw_table-&gt;set(new_index + kChainOffset, Smi::FromInt(previous_entry));

    // 5. 버킷을 새로운 엔트리로 지정
    raw_table-&gt;set(HashTableStartIndex() + bucket, Smi::FromInt(new_entry));

    // 6. 전체 엘리먼트 수 증가
    raw_table-&gt;SetNumberOfElements(nof + 1);

    // 7. 수정된 테이블 반환
    return table;
  }

  template &lt;class Derived, int entrysize&gt;
  MaybeHandle&lt;Derived&gt;
  OrderedHashTable&lt;Derived, entrysize&gt;::EnsureCapacityForAdding(
      Isolate* isolate, Handle&lt;Derived&gt; table) {
    // 1. 테이블이 오래된 상태가 아닌지 확인
    DCHECK(!table-&gt;IsObsolete());

    // 2. 테이블의 현재 요소, 삭제된 요소, 용량 정보 가져오기
    int nof = table-&gt;NumberOfElements();
    int nod = table-&gt;NumberOfDeletedElements();
    int capacity = table-&gt;Capacity(); // bucket * 2

    // 3. 테이블에 추가 요소를 수용할 만큼의 용량이 있다면 현재 테이블 반환
    if ((nof + nod) &lt; capacity) return table;

    int new_capacity;
    // 4. 테이블이 비어있는 경우 초기 용량 할당
    if (capacity == 0) {
      // 비어있는 상태에서 초기 최소 용량(4)으로 증가
      new_capacity = kInitialCapacity; 
    } else if (nod &gt;= (capacity &gt;&gt; 1)) {
      // 5. 삭제된 요소가 용량의 절반이상인 경우, 새 테이블을 할당하지 않고 삭제된 엔트리만 제거
      // 단, 제거된 엔트리를 그대로 유지할 수 없기 때문에 항상 새로운 테이블을 할당
      new_capacity = capacity;
    } else {
      // 6. 그 외의 경우, 현재 용량의 두 배로 용량 증가
      new_capacity = capacity &lt;&lt; 1;
    }

    // 7. 새로운 용량으로 리해싱을 수행하고 결과를 반환
    return Derived::Rehash(isolate, table, new_capacity);
  }

  bool OrderedHashTable&lt;Derived, entrysize&gt;::Delete(Isolate* isolate,
                                                    Tagged&lt;Derived&gt; table,
                                                    Tagged&lt;Object&gt; key) {
    // 가비지 컬렉션 비활성화 블록
    DisallowGarbageCollection no_gc;

    // 1. 테이블에서 키에 해당하는 엔트리 찾기
    InternalIndex entry = table-&gt;FindEntry(isolate, key);
    if (entry.is_not_found()) return false;

    // 2. 테이블의 요소 및 삭제된 요소 수 얻기
    int nof = table-&gt;NumberOfElements();
    int nod = table-&gt;NumberOfDeletedElements();

    // 3. 엔트리 인덱스를 실제 테이블 인덱스로 변환
    int index = table-&gt;EntryToIndex(entry);

    // 4. 엔트리를 비우기 (The Hole로 설정)
    Tagged&lt;Object&gt; hole = ReadOnlyRoots(isolate).the_hole_value();
      // entrysize : Set(1), Map(2)
    for (int i = 0; i &lt; entrysize; ++i) { 
      table-&gt;set(index + i, hole);
    }

    // 5. 테이블의 요소 및 삭제된 요소 수 갱신
    table-&gt;SetNumberOfElements(nof - 1);
    table-&gt;SetNumberOfDeletedElements(nod + 1);

    // 6. 삭제 성공을 나타내는 true 반환
    return true;
  }

  // 주어진 해시값에 대한 엔트리의 인덱스를 반환하는 함수
  int HashToEntryRaw(int hash) {
      // 1. 주어진 해시값으로부터 버킷을 계산
      int bucket = HashToBucket(hash);

      // 2. 해시 테이블의 시작 인덱스로 부터 해당 버킷의 엔트리를 가져옴
      Tagged&lt;Object&gt; entry = this-&gt;get(HashTableStartIndex() + bucket);

      // 3. 엔트리를 Smi(작은 정수)로 변환
      int entry_int = Smi::ToInt(entry);

      // 4. 디버깅을 위한 확인: 엔트리는 kNotFound이거나 0 이상의 값이어야 함
      DCHECK(entry_int == kNotFound || entry_int &gt;= 0);

      // 5. 엔트리의 정수값 반환
      return entry_int;
  }

  // 주어진 해시값에 대한 버킷을 반환하는 함수
  int HashToBucket(int hash) const {
      // 버킷의 수는 2의 제곱 수 이기 때문에 -1을 해주면 1111 같은 마스크가 생성
          // 결론적으로 나머지 연산과 같음
      return hash &amp; (NumberOfBuckets() - 1); //NumberOfBuckets 초기 값은 2
  }

  template &lt;class Derived&gt;
  Handle&lt;Derived&gt; SmallOrderedHashTable&lt;Derived&gt;::Rehash(Isolate* isolate,
                                                         Handle&lt;Derived&gt; table,
                                                         int new_capacity) {
    // 1. 새로운 용량이 최대 용량을 초과하는지 확인
    DCHECK_GE(kMaxCapacity, new_capacity);

    // 2. 새로운 테이블 할당
    Handle&lt;Derived&gt; new_table = SmallOrderedHashTable&lt;Derived&gt;::Allocate(
        isolate, new_capacity,
        Heap::InYoungGeneration(*table) ? AllocationType::kYoung
                                        : AllocationType::kOld);
    int new_entry = 0;

    {
      // 3. GC 비활성화 블록
      DisallowGarbageCollection no_gc;

      // 4. 기존 테이블의 엔트리를 순회하면서 처리
      for (InternalIndex old_entry : table-&gt;IterateEntries()) {
        Tagged&lt;Object&gt; key = table-&gt;KeyAt(old_entry);

        // 5. 빈 엔트리 (The Hole)는 무시하고 계속 진행
        if (IsTheHole(key, isolate)) continue;

        // 6. 키의 해시값 계산
        int hash = Smi::ToInt(Object::GetHash(key));

        // 7. 새로운 테이블에서의 버킷 및 체인 정보 얻기
        int bucket = new_table-&gt;HashToBucket(hash);
        int chain = new_table-&gt;GetFirstEntry(bucket);

        // 8. 새로운 테이블에서의 체인 및 엔트리 정보 설정
        new_table-&gt;SetFirstEntry(bucket, new_entry);
        new_table-&gt;SetNextEntry(new_entry, chain);

        // 9. 엔트리의 데이터 복사
        for (int i = 0; i &lt; Derived::kEntrySize; ++i) {
          Tagged&lt;Object&gt; value = table-&gt;GetDataEntry(old_entry.as_int(), i);
          new_table-&gt;SetDataEntry(new_entry, i, value);
        }

        // 10. 다음 새로운 엔트리 인덱스로 증가
        ++new_entry;
      }

      // 11. 새로운 테이블의 요소 수 설정
      new_table-&gt;SetNumberOfElements(table-&gt;NumberOfElements());
    }

    // 12. 새로운 테이블 반환
    return new_table;
  }</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[js study #coercion & conversion]]></title>
            <link>https://velog.io/@oak_cassia/js-study-coercion-conversion</link>
            <guid>https://velog.io/@oak_cassia/js-study-coercion-conversion</guid>
            <pubDate>Thu, 22 Feb 2024 15:13:47 GMT</pubDate>
            <description><![CDATA[<h3 id="type-coercion">type coercion</h3>
<ul>
<li>타입을 암시적으로 변환</li>
<li>엔진이 필요에 따라 자동으로 변환 </li>
<li>개발자가 예상치 못한 경우 버그 초래</li>
</ul>
<hr>
<h4 id="to-string">to string</h4>
<pre><code class="language-js">let num = 1;
let str = &quot;one :&quot; + num; // number to string

let str_nan = NaN + &#39;&#39; // &quot;NaN&quot;
let str_true = true + &#39;&#39; // &quot;true&quot;

let str_obj = ({}) + &#39;&#39; // &quot;[object Object]&quot;
let str_func = (function(){}) + &#39;&#39; // &quot;function(){}&quot;</code></pre>
<hr>
<h4 id="to-number">to number</h4>
<pre><code class="language-js">let str = &quot;1999&quot;;
let num = +str; // string to number

console.log(num == str) // str is converted to number

let str2 = &quot;Hi&quot;;
let num2 = +str2; // NaN

let num = +{} // NaN
let num = +[] // 0
let num = +[10, 20] // NaN 
let num = +(function(){}) // NaN</code></pre>
<h4 id="boolean">boolean</h4>
<pre><code class="language-js">let truthyValue = &quot;Hello&quot;; // true 
let falsyValue = &quot;&quot;; // false, 0, null, undefined, Nan, &quot;&quot; </code></pre>
<hr>
<h3 id="explicit-type-conversion">explicit type conversion</h3>
<ul>
<li>명시적인 변환</li>
</ul>
<hr>
<h3 id="to-string-1">to string</h3>
<pre><code class="language-js">let value = String(NaN); // &quot;NaN&quot;
let value = ((Infinity).toString()); // &quot;Infinity&quot;</code></pre>
<h4 id="to-number-1">to number</h4>
<pre><code class="language-js">let num = Number(&quot; 1000 &quot;); // 1000 문자열 양 끝의 공백은 제거됨
let num = Number(null); // 0
let num = Number(undefined); // NaN
let num = parseInt(&#39;-1&#39;);</code></pre>
<h3 id="to-boolean">to boolean</h3>
<pre><code class="language-js">let truthyValue = Boolean(&quot;Hello&quot;); // true 
let falsyValue = Boolean(&quot;&quot;); // false, 0, null, undefined, NaN, &quot;&quot; </code></pre>
<hr>
<h2 id="object">object</h2>
<ul>
<li>세 가지 hint<ul>
<li>string</li>
<li>number</li>
<li>default</li>
</ul>
</li>
<li>반환 타입은 원시 타입<ul>
<li>단 hint가 string 이어도 number를 반환할 수 있음</li>
</ul>
</li>
</ul>
<hr>
<table>
<thead>
<tr>
<th>조건</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td>1. <code>obj[Symbol.toPrimitive](hint)</code> 메서드가 있는지 확인</td>
<td>- 있다면 메서드 호출</td>
</tr>
<tr>
<td>2. <code>hint</code>가 &quot;string&quot;</td>
<td>- <code>obj.toString()</code> 호출 (존재하지 않으면 <code>obj.valueOf()</code> 호출)</td>
</tr>
<tr>
<td>3. <code>hint</code>가 &quot;number&quot; 또는 &quot;default&quot;이면</td>
<td>- <code>obj.valueOf()</code> 호출 (존재하지 않으면 <code>obj.toString()</code> 호출)</td>
</tr>
</tbody></table>
<hr>
<ol>
<li><code>obj[Symbol.toPrimitive](hint)</code> 메서드가 있는지 확인, 있다면 메서드 호출<pre><code class="language-js">let obj = {
[Symbol.toPrimitive](hint) {
 if (hint === &#39;string&#39;) {
   return &quot;Hello&quot;;
 } else if (hint === &#39;number&#39;) {
   return 99;
 } else {
   return &quot;Default&quot;;
 }
}
};
</code></pre>
</li>
</ol>
<p>// 힌트가 &#39;string&#39;일 때
console.log(String(obj)); // 출력: &quot;Hello&quot;</p>
<p>// 힌트가 &#39;number&#39;일 때
console.log(Number(obj)); // 출력: 99</p>
<p>// 힌트가 &#39;default&#39;일 때
console.log(obj + 2); // 출력: &quot;Default2&quot;</p>
<pre><code>---
1. `obj[Symbol.toPrimitive](hint)` 가 없고
2. `hint`가 &quot;string&quot; 이면 `obj.toString()` 호출 
3. 존재하지 않으면 `obj.valueOf()` 호출
```js
let obj = {
  toString() {
    return &quot;Hello, World!&quot;;
  }
};

// 힌트가 &#39;string&#39;일 때
console.log(String(obj)); // 출력: &quot;Hello!&quot;

// 힌트가 &#39;number&#39;일 때
console.log(Number(obj)); // 출력: NaN

// 힌트가 &#39;default&#39;일 때
console.log(obj + 2); // 출력: &quot;Hello2&quot;</code></pre><hr>
<ol>
<li><code>obj[Symbol.toPrimitive](hint)</code> 가 없고</li>
<li><code>hint</code>가 &quot;number&quot; 또는 &quot;default&quot;  <code>obj.valueOf()</code> 호출 </li>
<li>존재하지 않으면 <code>obj.toString()</code> 호출<pre><code class="language-js">let obj = {
valueOf() {
 return 42;
}
};
</code></pre>
</li>
</ol>
<p>// 힌트가 &#39;string&#39;일 때
console.log(String(obj)); // 출력: &quot;42&quot;</p>
<p>// 힌트가 &#39;number&#39;일 때
console.log(Number(obj)); // 출력: 42</p>
<p>// 힌트가 &#39;default&#39;일 때
console.log(obj + 2); // 출력: 44</p>
<pre><code>---
```js
let obj = {};

// 힌트가 &#39;string&#39;일 때
console.log(String(obj)); // 출력: &quot;[object Object]&quot;

// 힌트가 &#39;number&#39;일 때
console.log(Number(obj)); // 출력: NaN

// 힌트가 &#39;default&#39;일 때
console.log(obj + 2); // 출력: [object Object]2</code></pre><hr>
<ul>
<li>원시 타입 반환은 보장</li>
<li>원하는 hint 타입이 반환되지는 않음<pre><code class="language-js">let obj = {
[Symbol.toPrimitive](hint) {
  if (hint === &#39;string&#39;) {
    // 힌트가 &#39;string&#39;일 때에도 숫자 반환
    return 42;
  } else if (hint === &#39;number&#39;) {
    return 100;
  } else {
    return &quot;Default Value&quot;;
  }
}
};</code></pre>
</li>
</ul>
<hr>
<ul>
<li>변환이 두 번 발생하는 예시<pre><code class="language-js">let obj = {
  toString() {
      return &quot;99&quot;;
  }
};
</code></pre>
</li>
</ul>
<p>console.log(obj * 10); // 문자열 99 로 바뀐뒤 숫자 99로 변환 후 계산
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[js study #객체, GC]]></title>
            <link>https://velog.io/@oak_cassia/js-study-with-rust-%EA%B0%9D%EC%B2%B4</link>
            <guid>https://velog.io/@oak_cassia/js-study-with-rust-%EA%B0%9D%EC%B2%B4</guid>
            <pubDate>Sat, 25 Nov 2023 13:01:47 GMT</pubDate>
            <description><![CDATA[<h2 id="js">js</h2>
<ul>
<li><p>plain object, Array, Date, Error ...</p>
<h2 id="객체-plain-object">객체 (plain object)</h2>
</li>
<li><p>원시값</p>
<ul>
<li>문자형, 숫자형, Bigint, boolean, symbol, null, undefined</li>
</ul>
</li>
<li><p>객체</p>
<ul>
<li><p>프로퍼티(key value 쌍)로 구성</p>
<ul>
<li>key: 문자형</li>
<li>value: 모든 자료형</li>
<li>순서<ul>
<li>정수 프로퍼티는 오름차순</li>
<li>나머지 프로퍼티는 추가된 순</li>
</ul>
</li>
</ul>
</li>
<li><p>생성</p>
<ul>
<li><code>let obj = new Object();</code></li>
<li><code>let obj ={}; // 객체 리터럴</code></li>
</ul>
<p>``` js
let hwi = {</p>
<pre><code>name: &quot;hi&quot;,
age: 20,
&quot;is human&quot;: true, </code></pre><p>};</p>
</li>
<li><p>수정</p>
<ul>
<li><code>hwi.name = &#39;hwi&#39;;</code></li>
<li><code>hwi[&quot;is human] = false</code></li>
</ul>
</li>
<li><p>삭제</p>
<ul>
<li><code>delete hwi.name</code></li>
<li><code>delete hwi[&quot;is human&quot;]</code></li>
</ul>
</li>
<li><p>동적인 키</p>
<ul>
<li><p>변수 입력된 값으로 접근할 수 있음</p>
<pre><code class="language-js">let key = &quot;is human&quot;;
 hwi[&quot;is human&quot;] = true;</code></pre>
</li>
<li><p>동적으로 key를 사용할 때 대괄호 입력</p>
</li>
</ul>
<p>```js
function dog(name) {
   let obj = {</p>
<pre><code>   [name]: 5,</code></pre><p>   };
}</p>
<ul>
<li>key와 value 값이 같으면  단축 가능
```js
return {<pre><code>name, 
age,</code></pre>}</li>
</ul>
</li>
<li><p>key는 문자형이기 때문에 예약어와 같은 이름 가능</p>
<ul>
<li><code>__proto__</code> 는?</li>
</ul>
</li>
<li><p>in</p>
<ul>
<li>객체에 프로퍼티가 있는지 확인<ul>
<li>존재하지 않는 프로퍼티에 접근하면 undefined 반환</li>
<li>in 연산자를 사용하여 확실하게 프로퍼티가 있는지 확인</li>
</ul>
</li>
<li>for 문에 써서 foreach 처럼 반복 할 수 있음<ul>
<li><code>for (key in obj) {  }</code></li>
</ul>
</li>
</ul>
</li>
<li><p>메서드</p>
<ul>
<li>함수는 값으로 간주되어 프로퍼티로 추가 가능</li>
<li>this<ul>
<li>메서드 내부에서 객체 접근</li>
<li>runtime에 결정되어 일반 함수 내에 사용되어도 객체를 통해 호출하면 참조될 수 있음<ul>
<li>점 앞의 객체 참조</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>생성자</p>
<ul>
<li>new 이후 첫글자 대문자 <code>new Dog();</code><ul>
<li>this에 빈 객체 할당 (암시적)</li>
<li>생성자 실행 및 this에 프로퍼티 추가</li>
<li>this 반환 (암시적)</li>
</ul>
</li>
<li>함수 앞에 new 를 붙여서 실행하면 위 알고리즘 진행</li>
<li>리터럴은 코드 재사용성이 낮음</li>
<li>new.target : 함수 내부에서 사용, boolean 반환, new로 호출되었으면 true</li>
<li>return<ul>
<li>객체 반환 시 this 대신 반환</li>
<li>원시형(및 return;) 반환 시 return 문 무시 (this 반환)</li>
</ul>
</li>
<li>this.val에 함수를 할당해 메서드로 사용</li>
</ul>
</li>
<li><p>optional chaining</p>
<ul>
<li><code>?.</code></li>
<li>?앞의 대상이 undefined, null 이라면 즉시 undefined 반환</li>
<li>null 참조를 막을 수 있음</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="심볼형">심볼형</h3>
<ul>
<li><p>문자형과 함께 객체 프로퍼티의 키로 사용</p>
</li>
<li><p>유일한 키</p>
<ul>
<li><code>Symbol(&quot;key&quot;)</code>: 같은 문자열로 심볼을 만들어도 다른 심볼이면 서로 다름</li>
</ul>
</li>
<li><pre><code class="language-js">let key1 = Symbol(&quot;key&quot;);
 let key2 = Symbol(&quot;key&quot;);
 // key1 == key2  (flase)

 let dog = {
   name: &quot;arrr&quot;,
   [key2]: 55,
 };

 dog[key1] = 1; // 사용 예시</code></pre>
</li>
<li><p>심볼은 확장을 위한 기능</p>
</li>
<li><p>심볼형은 암시적 변환 X</p>
</li>
<li><p>심볼을 키로 만든 프로퍼티는 hidden property</p>
<ul>
<li>key 값이 같아서 생기는 충돌 방지</li>
<li><code>for let key in obj</code>: 반복문에 포함되지 않음</li>
<li>Object.keys(): 배열에 포함되지 않음</li>
<li>Object.assign(): 객체에 포함됨</li>
</ul>
</li>
<li><p>global symbol registry</p>
<ul>
<li>이름이 같을 때 같은 심볼을 가리키려면<ul>
<li>Symbol.for(&quot;key&quot;): 심볼이 없으면 생성, 있으면 읽음</li>
<li>Symbol.keyFor(symbol): 심볼을 넘겨주고 문자열 반환, 전역 심볼이 아니면 undefined</li>
</ul>
</li>
</ul>
</li>
<li><p>system symbol: 내장 심볼</p>
<h3 id="참조">참조</h3>
</li>
<li><p>객체는 대입 연산을 하면 참조로 작동</p>
</li>
<li><p>복사하려면</p>
<ul>
<li>순회</li>
<li>Object.assign<ul>
<li>객체 프로퍼티는 얇은 복사<h3 id="객체---원시형-캐스팅">객체 -&gt; 원시형 캐스팅</h3>
</li>
</ul>
</li>
</ul>
</li>
<li><p>boolean</p>
<ul>
<li>항상 true</li>
</ul>
</li>
<li><p>숫자</p>
</li>
<li><p>문자</p>
</li>
<li><p>hint</p>
<ul>
<li>string, number, default</li>
<li>객체에 <code>Symbol.toPrimitive(hint)</code>메서드가 있으면 호출 (시스템 심볼)</li>
<li>없고 string이면 <code>toString</code> 또는 <code>valueOf</code> 호출 (toString이 없으면 valueOf)</li>
<li>위 사항이 모두 아니고 number, default면 <code>valueOf</code> 또는 <code>toString</code> 호출</li>
</ul>
</li>
<li><p>Symbol.toPrimitive 함수는 hint를 받아 이에 따른 적절한 행동</p>
</li>
<li><p>기본 toString, valueOf 반환값</p>
<ul>
<li><code>[object Object]</code></li>
<li>valueOf는 객체 자신을 반환 (호출 무시?)</li>
</ul>
</li>
<li><p>와일드카드?</p>
<ul>
<li>Symbol.toPrimitive와 valueOf가 없으면 toString이 모든 형 변환 처리</li>
</ul>
</li>
<li><p>항상 hint에 명시된 자료형을 반환해주는 것은 아님(항상 원시값 반환은 맞음)</p>
</li>
<li><p>객체가 피연산자일 때</p>
<ol>
<li>앞서 본 규칙에 따라 원시형으로 변환</li>
<li>변환 후 타입이 적절치 않은 경우 한 번 더 형변환<h2 id="gc">GC</h2>
</li>
</ol>
</li>
<li><p>reachability 기준으로 메모리 관리</p>
</li>
<li><p>root 가 될 수 있는 값</p>
<ul>
<li>항상 도달할 수 있는 것<ul>
<li>지역, 매개, 전역 변수 등...</li>
</ul>
</li>
</ul>
</li>
<li><p>스택에서 루트를 검색</p>
<ul>
<li>지역 변수가 가리키는 객체는 루트 객체</li>
<li>전역 객체는 루트 객체</li>
</ul>
</li>
<li><p>mark and sweep</p>
<ul>
<li>root를 mark</li>
<li>root가 참조하는 것 mark</li>
<li>mark 한 객체가 참조한느 것 mark</li>
<li>갱신되지 않을 때 까지 반복</li>
<li>mark 안된 것 삭제</li>
</ul>
</li>
<li><p>최적화</p>
<ul>
<li>generational collection: 오래 남은 것이 지속적으로 사용된 것</li>
<li>incremental collection: 작업량 줄이기 위해 GC를 여러 부분으로 분리 후 각 부분 별도 수행</li>
<li>idle-time collection: cpu가 idle 일 때 수집</li>
</ul>
</li>
</ul>
<h3 id="힙-구조">힙 구조</h3>
<ul>
<li>V8<ul>
<li>new-space: 대부분의 객체. 작고 GC 되기 쉽게 설계, 다른 공간과 독립적</li>
<li>old-pointer-space: 다른 객체에 대한 포인터를 가질 수 있는 대부분의 객체. new-space에서 살아남으면 옮겨짐</li>
<li>old-data-space: 원시 값. 다른 객체에 대한 포인터 없음. new-space에서 살아남으면 옮겨짐</li>
<li>large-object-space: 다른 공간의 크기를 넘어선 객체 포함. 옮겨지지 않음 (오버헤드 크기 때문에)</li>
<li>code-sapce: JIT 컴파일된 코드 객체. 유일한 실행 가능한 메모리(large-object-space에도 코드가 할당되고 실행될 수 있다.)</li>
<li>cell-space, property-cell-space, map-space: Cell, PropertyCell, Map 모두 같은 크기의 오브젝트이고 가리키는 객체에 제약이 있어 collection 단순화</li>
</ul>
</li>
<li>각 공간은 페이지로 구성 (large-object-space 제외 1MB)</li>
<li>페이지는 객체를 저장하기 위해 header(flags, meta-data), marking bitmap(위에서 다룬 mark 객체 표현) 포함<ul>
<li>slot buffer: 각 페이지에 할당되어 저장된 개체를 가리키는 리스트 만들 때 사용</li>
</ul>
</li>
</ul>
<h3 id="포인터-식별">포인터 식별</h3>
<ul>
<li>GC는 포인터와 데이터를 구별할 수 있어야 함<ul>
<li>Conservative: word 들을 포인터라고 가정.<ul>
<li>데이터를 포인터로 잘못 분류할 수 있음</li>
<li>메모리 압축과 객체 이동을 제한<ul>
<li>포인터라고 생각하고 데이터를 변경할 수 있기 때문</li>
</ul>
</li>
</ul>
</li>
<li>Compiler hints: 정적 언어는 컴파일러가 정보를 제공할 수 있음<ul>
<li>각 클래스 내의 포인터를 찾음</li>
<li>js는 동적이라 불가능 (모든 필드에 포인터나 데이터가 포함될 수 있음)</li>
</ul>
</li>
<li>Tagged pointers: word에 tag bit를 추가하여 포인터인지 데이터 인지 명시적으로 표현</li>
</ul>
</li>
<li>V8은 Tagged pointers 사용<ul>
<li>정수는 32비트 워드와 낮은 비트가 0으로 설정</li>
<li>포인터는 1</li>
</ul>
</li>
</ul>
<h3 id="generational-collection">Generational collection</h3>
<ul>
<li>대부분의 객체는 수명이 짧고 적은 수의 오브젝트많이 수명이 길다.</li>
<li>new space: 공간이 다 차면 scavenge라 불리는 빠른 GC 사이클이 객체 정리, 할당 비용이 작음, (1MB ~ 8MB)<ul>
<li>to-space from-space로 한 번 더 나눔</li>
<li>to가 가들 차면 from과 교체</li>
<li>모든 객체는 from space에 있음</li>
<li>살아있는 객체는 to-space로 옮기거나 old로 승격</li>
</ul>
</li>
</ul>
<h3 id="write-barrier">Write Barrier</h3>
<ul>
<li>old-space 포인터 -&gt; new-space 객체<ul>
<li>new-space 객체를 지우기 위해 old-space 를 검색하는 것은 비효율적</li>
</ul>
</li>
<li>write barrier는 store 후 실행되는 작은 코드</li>
<li>write barrier가 포인터를 기록하여 new-space가 collected 될 때 사용<ul>
<li>비용이 크지만 GC가 live 객체를 식별하는 데 필요</li>
<li>최적화 기법<ul>
<li>정적으로 new-space에 있는 것을 증명할 수 있다면 (write barrier 생략)</li>
<li>로컬 참조가 아닌 참조가 없을 때 스택에 객체 할당 (write barrier 생략)</li>
<li>페이지 헤더에 어느 space에 있는지 체크</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="gc-pauses">GC Pauses</h3>
<ul>
<li>incremental marking<ul>
<li>힙이 5~10ms의 작은 pause로 보이게 함</li>
<li>힙이 임계값에 도달하면 활성화</li>
<li>활성화되면 </li>
</ul>
</li>
<li>lazy sweeping<ul>
<li>도달 가능한 객체를 파악 후 바로 collect 하지 않고 페이지가 교체될 때 collect</li>
</ul>
</li>
</ul>
<blockquote>
<p>참조
<a href="https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection">https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[js 스터디 #기본 개념 with rust]]></title>
            <link>https://velog.io/@oak_cassia/js-%EC%8A%A4%ED%84%B0%EB%94%94-with-rust-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@oak_cassia/js-%EC%8A%A4%ED%84%B0%EB%94%94-with-rust-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Thu, 23 Nov 2023 12:52:30 GMT</pubDate>
            <description><![CDATA[<h2 id="둘러보기">둘러보기</h2>
<h3 id="js란">js란?</h3>
<ul>
<li>javascript는 웹페이지를 위한 프로그래밍 언어</li>
<li>js로 작성한 프로그램인 script는 html 안에서 작성할 수 있음<ul>
<li>브라우저 및 JavaScript engine이 포함된 디바이스에서 동작 가능</li>
<li>브라우저는 자바스크립트 가상 머신(엔진)을 내장<ul>
<li>chrome: V8</li>
<li>firefox: SpiderMonkey</li>
<li>MS edge: ChakraCore</li>
<li>safari: SquirrelFish</li>
<li>엔진은 스크립트를 파싱 후 기계어로 컴파일, 이후 실행<ul>
<li>각 단계 마다 최적화 수행</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="브라우저-보안">브라우저 보안</h4>
<ul>
<li>저수준 조작 허용 X <ul>
<li>메모리, CPU 등</li>
<li>브라우저를 대상으로 만들었기 때문</li>
<li>OS가 지원하는 기능을 브라우저가 직접 쓰지 못함</li>
<li>디바이스와의 인터랙션은 사용자의 허가 안에서 수행 </li>
<li>Same Origin Policy</li>
<li>페이지 간의 데이터 교환은 상호 동의 하에 가능</li>
<li>한 창에서 다른 창을 열 때 (도메인, 프로토콜, 포트)가 같으면 접근 가능</li>
</ul>
</li>
<li>서버, 모바일에서도 쓸 수 있음</li>
</ul>
<h4 id="장점">장점</h4>
<ul>
<li>HTML/CSS 와의 통합</li>
<li>단순함</li>
<li>브라우저의 지원 (기본 언어로 쓰임)</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li>동적 타이핑</li>
<li>속도</li>
</ul>
<h2 id="rust">Rust</h2>
<h3 id="rust란">Rust란?</h3>
<ul>
<li>(메모리)안정성과 성능, 쾌적한 사용성을 목표로한 언어.<ul>
<li>undefined behavior를 컴파일 과정에서 발견<ul>
<li>오버플로, 허가받지 않은 메모리 접근(더블 프리, 널 역참조), divided by zero 등</li>
</ul>
</li>
<li>컴파일을 통과한 언어는 안전하다고 할 수 있음</li>
</ul>
</li>
</ul>
<h4 id="장점-1">장점</h4>
<ul>
<li>C/C++에서 발생할 수 있는 (메모리, 포인터 관련) 문제를 컴파일 타임에 잡음</li>
<li>안전한 병렬 프로그래밍 (메모리 안정성에 쓰이는 제약을 동시성 문제에도 적용)</li>
<li>빠른 성능</li>
</ul>
<h4 id="단점-1">단점</h4>
<ul>
<li>러닝 커브</li>
<li>느린 컴파일 시간</li>
</ul>
<h2 id="webassembly">WebAssembly</h2>
<h3 id="webassembly란">WebAssembly란?</h3>
<ul>
<li>js로 충분하지 않은 속도를 해결하기 위해 등장<ul>
<li>모바일 등 리소스가 제한된 상황</li>
<li>게임, AI 등 성능이 필요한 경우</li>
</ul>
</li>
<li>여러 언어로 작성된 코드를 컴파일 하여 네이티브에 가까운 속도로 실행될 수 있게 함</li>
<li>여러 플랫폼에서 실행 가능</li>
<li>샌드박싱된 실행 환경에서 안전하도록 설계</li>
<li>js와 상호 보완</li>
</ul>
<h4 id="핵심-개념">핵심 개념</h4>
<ul>
<li>모듈<ul>
<li>브라우저에서 실행가능한 기계어로 컴파일된 웹 어셈블리 바이너리</li>
<li>stateless</li>
<li>winodws와 workers 간의 명시적 공유 가능</li>
</ul>
</li>
<li>필수 섹션<ul>
<li>Type<ul>
<li>모듈에 정의되거나 import한 함수들의 시그니처</li>
</ul>
</li>
<li>Function<ul>
<li>모듈에 정의된 함수를 참조할 수 있는 인덱스</li>
</ul>
</li>
<li>Code <ul>
<li>함수의 본문</li>
</ul>
</li>
</ul>
</li>
<li>선택 섹션<ul>
<li>export<ul>
<li>다른 어셈블리 모듈, js에서 사용 가능한 인스턴스 생성</li>
</ul>
</li>
<li>Import<ul>
<li>다른 어셈블리 모듈, js에서 사용 가능한 인스턴스 지정</li>
</ul>
</li>
<li>Memory<ul>
<li>모듈이 사용하는 메모리</li>
<li>WebAssembly의 저수준 메모리 접근 명령에 의해 읽거나 쓸수 있는 크기 조절 가능한 바이트 ArrayBuffer</li>
<li>c/c++, rust에서 사용하는 heap을 시뮬레이션하기 위해 배열 버퍼 사용</li>
<li>배열의 인덱스들은 메모리의 주소처럼 쓰임</li>
</ul>
</li>
<li>Start<ul>
<li>모듈 로드 시 수행되는 함수</li>
</ul>
</li>
<li>Global<ul>
<li>전역 변수</li>
</ul>
</li>
<li>Table<ul>
<li>안정성과 이식성을 위해 raw 바이트로 저장되지 않는 A resizable typed array of references</li>
<li>js 객체 같이 wasm 모듈 외부의 값을 맵핑<ul>
<li>간접적으로 함수 호출 시 유용</li>
</ul>
</li>
</ul>
</li>
<li>Data<ul>
<li>import 된 또는 로컬 메모리 초기화</li>
</ul>
</li>
<li>Element<ul>
<li>import 된 또는 로컬 테이블을 초기화</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>인스턴스</p>
</blockquote>
<ul>
<li>WebAssembly 모듈이 실행 시간에 사용하는 상태 (메모리, 테이블, impoted values)</li>
</ul>
<ul>
<li>js api를 사용해 wasm의 모듈, 메모리, 테이블, 인스턴스 생성 가능<ul>
<li>wasm 코드의 실행 상태 관리</li>
<li>wasm 코드를 js로, js 코드를 wasm으로 내보낼 수 있음</li>
</ul>
</li>
</ul>
<h4 id="js">js</h4>
<ul>
<li>js 파일은 텍스트로 되어있어 전부 읽은 후 컴파일 해야 실행 가능<ul>
<li>parse - decode - compile &amp; optimize - (reoptimize) - exe - GC</li>
</ul>
</li>
<li>JIT<ul>
<li>js 엔진에 모니터 추가<ul>
<li>코드 실행 빈도 파악 (warm -&gt; hot)</li>
</ul>
</li>
<li>Baseline compiler<ul>
<li>함수가 warm 해지면 컴파일 후 정보 저장 (모니터가 파악)</li>
<li>함수는 stub으로 컴파일 (<strong>줄번호</strong>와 타입을 인덱스로 사용)<ul>
<li>같은 타입이면 같은 stub 사용</li>
</ul>
</li>
</ul>
</li>
<li>optimization compiler<ul>
<li>hot인 코드를 모니터가 최적화하여 컴파일러에게 전송</li>
<li>컴파일러는 더 빠른 코드를 생성하여 저장</li>
<li>컴파일된 코드는 실행 전 유효성 체크<ul>
<li>유효하지 않다면 최적화된 코드 폐기</li>
<li>폐기되었다면 역최적화 (인터프리터 or base compiler 이후 상태</li>
</ul>
</li>
</ul>
</li>
<li>js의 최적화는 성능 저하를 일으킬 수 있음<ul>
<li>JIT가 같은 코드에 대해 최적화-역최적화를 반복한다면 최적화를 안하는 게 이득</li>
</ul>
</li>
<li>작업 중 오버헤드 및 메모리 낭비가 있을 수 있음<h4 id="wasm">wasm</h4>
</li>
</ul>
</li>
<li>wasm도 컴파일 해야하지만 앞서 이진 파일로 컴파일된 상태<ul>
<li>decode - compile &amp; optimize - execute</li>
<li>WebAssembly<ul>
<li>실제 기계가 아닌 가상(개념적) 기계를 위한 언어<ul>
<li>wasm의 명령어를 virtual instruction 라고도 함</li>
<li>HW 기계어에 맵핑되지는 않음</li>
</ul>
</li>
<li>js 보다 기계어에 가까움</li>
<li>브라우저는 wasm을 갖고 기계의 어셈블리를 위한 hop을 만든다.</li>
</ul>
</li>
<li>IR: 작성한 코드를이식성을 위해 LLVM을 위한 IR로 변환<ul>
<li>IR을 wasm 으로 변환하여 wasm 파일을 최종적으로 얻음<ul>
<li>형태는 어셈블리와 비슷(hexadecimal)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="속도가-빠른-이유">속도가 빠른 이유</h4>
<ul>
<li>js<ul>
<li>parsing: source를 인터프리터가 실행할 수 있는 형태로 변환<ul>
<li>브라우저에서 AST로 변환</li>
<li>일부만 해석하고 사용하지 않는 코드는 stub으로 만들어 둠</li>
</ul>
</li>
<li>compile &amp; optimization: baseline, optimization compiler 수행시간<ul>
<li>코드 실행 중 컴파일</li>
</ul>
</li>
<li>re-optimization: 역최적화, 기본코드로 되돌리는 작업</li>
<li>execute: 실행 시간( 타입이 항상 동일 하다고 가정하고 코드 최적화, 이 동작에 맞춰주면 빠른 js 코드 가능 )</li>
<li>GC: 불필요한 메모리 삭제</li>
<li>위 작업은 작은 단위로 수행 (한 번에 전체 X)</li>
</ul>
</li>
<li>wasm<ul>
<li>fetch: js 보다 간결하여 데이터 가져오는 속도가 빠름</li>
<li>parsing: wasm은 중간 표현 형식이기 때문에 변환 필요 없음, decode 및 에러 체크</li>
<li>compile &amp; optimization: 앞서 살펴본 여러 최적화 작업이 필요 없어 빠름<ul>
<li>어떤 타입인지 확인학 위해 실행해 볼 필요 없음</li>
<li>정적 타이핑 되어 컴파일 된 상태</li>
</ul>
</li>
<li>re-optimization: 필요 없는 단계</li>
<li>excute: 일반적으로 빠름</li>
<li>GC: 없음. 메모리는 프로그래머의 역량</li>
</ul>
</li>
</ul>
<blockquote>
<p>참조
js: <a href="https://ko.javascript.info/">https://ko.javascript.info/</a>
rust: <a href="https://www.yes24.com/Product/Goods/116789691">짐 블랜디 &amp; 제이슨 오렌도프 &amp; 리어노라 틴달, 프로그래밍 러스트 (2nd ed.)</a>
wasm</p>
</blockquote>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/WebAssembly/Concepts">https://developer.mozilla.org/en-US/docs/WebAssembly/Concepts</a></li>
<li><a href="https://dongwoo.blog/2017/06/06/%eb%b2%88%ec%97%ad-%ec%9b%b9%ec%96%b4%ec%85%88%eb%b8%94%eb%a6%ac%ec%9d%98-%ed%98%84%ec%9e%ac-%ec%9c%84%ec%b9%98%ec%99%80-%eb%af%b8%eb%9e%98/">https://dongwoo.blog/2017/06/06/%eb%b2%88%ec%97%ad-%ec%9b%b9%ec%96%b4%ec%85%88%eb%b8%94%eb%a6%ac%ec%9d%98-%ed%98%84%ec%9e%ac-%ec%9c%84%ec%b9%98%ec%99%80-%eb%af%b8%eb%9e%98/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[서버캠퍼스 1기] 회고]]></title>
            <link>https://velog.io/@oak_cassia/%EC%84%9C%EB%B2%84%EC%BA%A0%ED%8D%BC%EC%8A%A4-1%EA%B8%B0-%ED%9A%8C%EA%B3%A0-%EC%9E%91%EC%84%B1-%EC%A4%91</link>
            <guid>https://velog.io/@oak_cassia/%EC%84%9C%EB%B2%84%EC%BA%A0%ED%8D%BC%EC%8A%A4-1%EA%B8%B0-%ED%9A%8C%EA%B3%A0-%EC%9E%91%EC%84%B1-%EC%A4%91</guid>
            <pubDate>Fri, 26 May 2023 14:55:11 GMT</pubDate>
            <description><![CDATA[<h2 id="목표-설정">목표 설정</h2>
<p>혼자서 공부하려고 계획 중이었던 <code>웹 서버 방식의 게임서버</code>를 <code>컴투스 서버캠퍼스 1기</code>에서 체계적으로 배울 수 있었습니다.
그리고 하나의 목표를 정했는데 나의 상태를 점검해보고 새롭게 배운 것을 잘 정리해 성장의 발판으로 삼는 것입니다.</p>
<h2 id="나는-성장을-했을까">나는 성장을 했을까</h2>
<p>이전에 참여했던 프로젝트에서 지식, 인사이트를 얻은 것 같기는 한데, 내가 정말 성장했는지, 생각했던 것이 옳았는지 검증할 방법이 없었습니다. 다행히 이번 기회에 성과를 실제로 확인하는 경험을 할 수 있었습니다. </p>
<h3 id="무엇을-만들지에-대한-이해">무엇을 만들지에 대한 이해</h3>
<p>이전 프로젝트에서는 게임 서버의 구조에 대한 이해가 부족하다 보니 만들 대상을 명확하게 정의할 수 없었습니다. 따라서 개발 중간에 전체 구조를 수정해야 했고 기능 하나 하나를 구현하기 전 고민하는 시간이 길었습니다.</p>
<p>그래서 이번 개발에서는 무엇을 만들지, 어떻게 만들지 상대적으로 철저히 고민하는 과정을 거쳤습니다. 특히 기획서를 보고 모호하거나 불명확한 부분이 있을 때 실장님께 질문을 하였습니다.</p>
<p>매 화요일, 컴투스에서 전주로 돌아가는 기차 안에서, 기획에 필요한 기능과 그에 따른 구현 방법에 대해 생각했습니다. 수요일은 강의가 1~2시간 간격으로 진행되는 구조여서 개발에 집중하기 어려웠습니다. 대신에, 무엇을 어떻게 개발할지 보다 상세한 계획을 세웠습니다. </p>
<blockquote>
<p>항상 수요일 저녁이 되서야 기능을 구현하기 시작했습니다.</p>
</blockquote>
<p>프로젝트 초기에는 API 서버를 처음 개발하는 것이였기 때문에 <code>이전 프로젝트</code>와 비슷한 어려움을 겪었지만, 10일 정도 지나면서 <code>요청 처리 과정</code>과 <code>기능 구현 방식</code>에서 <code>일정한 패턴</code>이 있다는 것을 인식했습니다. 이를 정리하여 하나의 프로세스로 만들면서, &#39;어떻게 만들까?&#39;하며 고민하는 시간이 줄었고, 이는 작업 효율성을 향상시켰습니다.</p>
<p>이러한 개선이 가능했던 이유는, 구현한 기능들이 주로 데이터베이스 접근을 포함하고 비슷한 맥락으로 진행되는 유사한 작업이었기 때문입니다. 일관된 틀을 정립한 뒤에는, 약간의 수정만으로도 새로운 기능에 맞는 프로세스로 개발을할 수 있었고, 이는 빠르고 효율적인 진행을 가능케 했습니다.</p>
<blockquote>
<p>무엇을 만들지에 대한 명확한 이해가 있을 때와 없을 때의 차이는 컸습니다. 기존에는 구현을 시작하기 까지 시간이 굉장히 오래 걸렸지만 이번 프로젝트는 효율적인 작업을 위한 방법 까지 고안할 수 있었습니다.</p>
</blockquote>
<h3 id="기록하는-습관과-성찰">기록하는 습관과 성찰</h3>
<p>개발하는 것 이상으로, 회고하고 경험을 소화하는 과정에서 더 큰 성장을 할 수 있다고 생각합니다.</p>
<blockquote>
<p><del>개발이 사냥이라면 회고는 퀘스트...?</del></p>
</blockquote>
<p>이전 프로젝트에서 문서 작성과 목표 설정이 힘들었습니다. 하지만 기록한 내용은 회고와 포트폴리오 작성에 큰 도움이 되었습니다.</p>
<p>이번에도 같은 효과를 얻기 위해 개발 과정 중에 기록을 꾸준히 남겼습니다. 깃허브의 이슈 탭을 활용하여 고민이나 이슈를 거칠게 정리하고 마지막 주차에 이 내용을 블로그에 옮겨 다시 한 번 검토한 뒤 공유하였습니다.</p>
<p>이 과정은 5주 동안의 경험을 소화하고, 놓친 부분을 찾거나 잘못된 점을 수정하는 데 도움이 되었습니다.</p>
<h2 id="배운-것">배운 것</h2>
<p>데이터베이스, 웹 서버, MySQL, Redis 익숙하지 않았던 기술을 활용하고 멘토링 중 값진 것들을 많이 얻었지만 그 중 두 가지를 꼽고 싶습니다.
하나는 리팩토링, 다른 하나는 데이터베이스 입니다.</p>
<blockquote>
<p>이번 서버 캠퍼스 기간 중에 내가 얻은 가장 값진 것은 실장님의 멘토링을 통해 깨달았던 코드를 작성하는 것에 대한 자세입니다.</p>
</blockquote>
<h3 id="리팩토링">리팩토링</h3>
<p>실장님께서 좋은 코드에 관하여 첫 주차 부터 언급하셨습니다. 처음에는 잘 와닿지 않고 &quot;이름을 잘 지어보자&quot; 정도로만 생각했지만 3주차에 와서 멘토링 중 깨달을 수 있었습니다.</p>
<blockquote>
<p>이미 이전 게시물에서 언급했지만, 자소서나 포트폴리오와 같이 수차례 작성하고 수정한 것에 비해, 왜 코드는 동작하면 더 이상 신경쓰지 않았을까?</p>
</blockquote>
<p>그 날의 멘토링 이후로 반성하는 시간을 가졌고 프로젝트를 진행하면서 리팩토링에 많은 노력을 기울였습니다. 실제로 커밋 메시지를 보면 리팩토링과 기능 추가가 비슷한 비율로 존재합니다. (리팩토링 커밋을 rebase로 squash 했음에도 불구하고...)</p>
<p>이름을 어떻게 지을지 부터, 폴더 계층, 코드 분리, 중복 코드 제거, 로직에 이르기까지 여러 번 검토하고 고민하면서 코드를 개선하였습니다. 이 과정에서 로직에 대한 이해도가 깊어지고, 코드의 개선을 보며 만족감을 느꼈습니다. 개발 초기에 부여한 이름들과 현재 깃허브에 남아있는 이름들을 비교하면, 확실히 발전했다는 것을 알 수 있습니다.</p>
<blockquote>
<p>데이터베이스에 접근하여 수정 또는 데이터를 읽는 일을 담당하는 클래스
1주차 : GameDatabase
3주차 : UserService, MailService, ...   (분리)
5주차 : UserDataCRUD, MailDataCRUD, ...</p>
</blockquote>
<hr>
<h3 id="데이터베이스">데이터베이스</h3>
<p>기술 및 지식 중에서 가장 큰 발전을 보인 것을 하나 꼽자면 데이터베이스입니다. 컴투스 서버캠퍼스에 지원하면서 데이터베이스를 수강취소 하게 되었는데 </p>
<blockquote>
<p>화요일 수업 이었기에..</p>
</blockquote>
<p>서버 캠퍼스 준비를 위해 도서관에서 전공 도서를 빌려 1주간 읽었습니다. 또한 동기분이 추천해준 유튜브를 통해 책에서 다루지 않은 인덱스와 다른 세부적인 내용을 공부했습니다.</p>
<p>프로젝트의 첫 주, ERD를 작성하는 과정에서 다소 어려움을 겪었습니다. 유저가 소유한 아이템의 저장 방식을 결정하는 데 많은 시간을 썼습니다.
처음에는 유저 정보와 함께 <code>user_data</code>에 저장했으나, 새로운 아이템이 생길 때 마다 최대치를 수정해야 하는 비합리적인 상황이기 때문에 <code>owned_item</code>으로 분리하였습니다.
&#39;Item Code&#39;, &#39;Enhancement Count&#39; 등 여러 속성을 종합하여 아이템을 식별하고 있었는데, 해당 방법 역시  동일한 수치를 갖는 아이템을 구분하지 못해서 <code>auto increment</code>로 &#39;ItemId&#39;를 부여하는 방식을 최종적으로 사용했습니다.</p>
<p>이처럼 간단한 문제에도 시간이 오래 걸렸고 프로젝트를 진행하면서 ERD와 그에 따른 코드를 수정하는 경우도 10회 이상 되었습니다. 하지만 이런 시행착오와 데이터베이스 게시물에서 언급했던 고민들 끝에, <code>요청에 필요한 단위</code>라는 기준을 세울 수 있었고 효율성에 대한 고민을 더해 수정했습니다. 그리고 그 결과 나름 괜찮은 구조가 완성됐습니다.</p>
<h2 id="마치며">마치며</h2>
<p>최종적으로, <code>컴투스 서버캠퍼스 1기</code>에서의 경험은 지난 프로젝트에서의 성장을 확인하고 더 나아가 새로운 성장을 할 수 있는 기회였습니다.</p>
<p>열정적으로 기술, 취업 등 유용한 지식을 알려주시고 세심하게 챙겨주신 실장님과 과장님, 보이지 않는 곳에서 서버캠퍼스를 원활하게 운영해주신 모든 분들께 진심으로 감사를 전합니다.</p>
<h3 id="아쉬운-점">아쉬운 점</h3>
<p>서버 캠퍼스 기간 중 하나 아쉬운 것을 꼽자면 5주라는 짧은 기간 진행되었다는 것입니다. 시작 전에는 실시간 요소를 넣어보고 싶었는데 학업 병행 및 시간적 한계로 시도하지 못했습니다.
또 마지막 주차에 테스트에 대한 설명을 들으면서 그 동안 효율적인 테스트를 해보고 싶은 마음이 있었기에 유닛 테스트를 시도해 보고 싶었지만 남은 시간이 얼마 없었기에 리팩토링을 진행한 뒤 글을 작성하는 것에 집중하였습니다.</p>
<h3 id="앞으로">앞으로</h3>
<p>좋은 코드를 위한 고민, 코드를 검토하고 리팩토링을 하는 것은 정말 값진 경험이었습니다. 앞으로 진행할 프로젝트에서도 좋은 코드를 위한 고민을 지속적으로 하여 한 눈에 알아보기 쉬운 코드를 작성할 것입니다.</p>
<p>실시간 요소와 테스트가 못내 아쉬운데 C# 소켓을 사용한 턴제 전투와 유닛 테스트를 구현할 것입니다. </p>
<blockquote>
<p>훗날 서버 캠퍼스 n기에 참여하려는 분들이 계신다면, 개인적인 경험을 바탕으로 강력하게 추천드립니다. 
모니터, 식사, 간식, 카페 등 개발에만 집중할 수 있는 환경과 편의 시설 그리고 수료 시 지급되는 장학금!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[서버캠퍼스 1기] 개발 중 고민했던 것들 3 (Rollback)]]></title>
            <link>https://velog.io/@oak_cassia/%EC%84%9C%EB%B2%84%EC%BA%A0%ED%8D%BC%EC%8A%A4-1%EA%B8%B0-%EA%B0%9C%EB%B0%9C-%EC%A4%91-%EA%B3%A0%EB%AF%BC%ED%96%88%EB%8D%98-%EA%B2%83%EB%93%A4-3-Rollback</link>
            <guid>https://velog.io/@oak_cassia/%EC%84%9C%EB%B2%84%EC%BA%A0%ED%8D%BC%EC%8A%A4-1%EA%B8%B0-%EA%B0%9C%EB%B0%9C-%EC%A4%91-%EA%B3%A0%EB%AF%BC%ED%96%88%EB%8D%98-%EA%B2%83%EB%93%A4-3-Rollback</guid>
            <pubDate>Mon, 22 May 2023 12:08:33 GMT</pubDate>
            <description><![CDATA[<h3 id="서버에서-롤백을-하는-이유">서버에서 롤백을 하는 이유</h3>
<p>진행했던 프로젝트 <code>DungeonWar API</code>는 특정한 환경을 가정하고 있습니다.</p>
<ol>
<li>Scale Out 가능한 서버</li>
<li>샤딩 불가능한 데이터베이스</li>
</ol>
<p>이 때 데이터베이스의 부하가 증가하면 성능 저하가 크게 발생할 수 있습니다. 또한, 데이터베이스 샤딩이 가능하더라도, 한 번의 요청에 서로 다른 데이터베이스를 접근해야 한다면 서버에서 롤백을 수행하는 것이 합리적일 것입니다</p>
<p>데이터베이스의 부하를 줄이기 위해, 서버에서 트랜잭션 처리를 하게 구성하였습니다. 요청 처리 중 오류가 발생하는 경우, 그 동안의 작업은 서버에서 롤백 합니다.</p>
<pre><code class="language-csharp">public async Task&lt;ReceiveMailItemResponse&gt; Post(ReceiveMailItemRequest request)
    {
        var authenticatedUserState = HttpContext.Items[nameof(AuthenticatedUserState)] as AuthenticatedUserState;
        var response = new ReceiveMailItemResponse();

        if (authenticatedUserState == null)
        {
            response.Error = ErrorCode.WrongAuthenticatedUserState;
            return response;
        }

        var gameUserId = authenticatedUserState.GameUserId;
        var mailId = request.MailId;

        // 메일 상태를 &#39;받음&#39;으로 바꾸고
        var errorCode = await _mailDataCRUD.UpdateMailStatusToReceivedAsync(gameUserId, mailId);
        if (errorCode != ErrorCode.None)
        {
            response.Error = errorCode;
            return response;
        }

        (errorCode, var items) = await _mailDataCRUD.LoadMailItemsAsync(gameUserId, mailId);
        if (errorCode != ErrorCode.None)
        {
            // 원상태로 롤백
            await _mailDataCRUD.RollbackMailStatusAsync(gameUserId, mailId);
            response.Error = errorCode;
            return response;
        }

        //아래에서 살펴볼 함수
        errorCode = await _itemDataCRUD.InsertItemsAsync(gameUserId, items);
        if (errorCode != ErrorCode.None)
        {
            // 원상태로 롤백
            await _mailDataCRUD.RollbackMailStatusAsync(gameUserId, mailId);
            response.Error = errorCode;
            return response;
        }

        _logger.ZLogInformationWithPayload(new { GameUserId = gameUserId, MailId=mailId }, &quot;ReceiveMailItem Success&quot;);

        response.Error = errorCode;
        return response;
    }</code></pre>
<hr>
<p>한 번에 여러 요청이 발생하여 사용자의 데이터 일관성이 무너지는 상황도 예측해 볼 수 있습니다.
이 경우는 개발된 게임 서버에서는 자신의 데이터만 접근 가능하며, 자신의 데이터가 아닌 경우 요청이 거부됩니다. 따라서 이 문제에 대해 걱정할 필요는 없습니다.</p>
<ol>
<li><p>인증된 클라이언트만 데이터 수정이 가능합니다. 이를 통해 외부로부터의 무단 수정을 방지하였습니다.</p>
</li>
<li><p>redis에 요청 처리 시작 시 키값을 저장하고 응답 후 해제하는 방식의 락을 활용하여 한 번에 하나의 사용자 요청만 처리하도록 설정했습니다. </p>
</li>
</ol>
<hr>
<h3 id="조금-더-복잡한-롤백">조금 더 복잡한 롤백</h3>
<p>앞서 본 코드로 일반적인 상황에서의 롤백을 수행하였습니다. 하지만 개발 중 더 복잡한 상황에 직면하였습니다.</p>
<pre><code class="language-csharp">// InsertItemsAsync 코드 중 일부
List&lt;Func&lt;Task&gt;&gt; rollbackActions = new List&lt;Func&lt;Task&gt;&gt;();

foreach (var item in items)
{
    ErrorCode errorCode= await AddItemBasedOnCodeAsync(gameUserId,item.ItemCode,item.ItemCount,rollbackActions);

    if (errorCode != ErrorCode.None)
    {
        await RollbackReceiveItemAsync(rollbackActions);

        //로깅 생략

        return ErrorCode.InsertItemFailInsert;
    }

}
</code></pre>
<hr>
<p>여러 아이템을 동시에 수령해야 할 때 <code>ItemCode</code>에 따라 동작이 달라집니다. <code>ItemCode</code>는 동적인 상황에서 결정되므로 예측이 불가능하며, 이 때문에 하나의 동일한 <code>Rollback</code>으로 처리할 수 없습니다.</p>
<pre><code class="language-csharp">private async Task&lt;ErrorCode&gt; AddItemBasedOnCodeAsync(Int32 gameUserId, Int32 itemCode ,Int32 itemCount, List&lt;Func&lt;Task&gt;&gt; rollbackActions)
{
    ErrorCode errorCode= ErrorCode.None;
    if (itemCode == (int)ItemCode.Gold)
    {
        errorCode = await IncreaseGoldAsync(gameUserId, itemCount,rollbackActions);
    }
    else if (itemCode == (int)ItemCode.Potion)
    {
        errorCode = await IncreasePotionAsync(gameUserId, itemCount,rollbackActions);
    }
    else
    {
        errorCode = await InsertOwnedItemAsync(gameUserId, itemCode, itemCount,    0,rollbackActions);
    }

    return errorCode;
}</code></pre>
<hr>
<p>따라서, 각 함수가 성공적으로 동작했을 때 롤백을 리스트에 등록하는 방식을 사용했습니다.</p>
<pre><code class="language-csharp">private async Task&lt;ErrorCode&gt; IncreaseGoldAsync(Int32 gameUserId, Int32 itemCount, List&lt;Func&lt;Task&gt;&gt; rollbackActions)
{
    var count = await _queryFactory.Query(&quot;user_data&quot;).Where(&quot;GameUserId&quot;, &quot;=&quot;, gameUserId)
        .IncrementAsync(&quot;Gold&quot;, itemCount);

    if (count != 1)
    {
        // 로깅, 반환
    }

    // 성공 시 롤백 등록
    rollbackActions.Add(async () =&gt;
    {
        var rollbackCount = await _queryFactory.Query(&quot;user_data&quot;).Where(&quot;GameUserId&quot;, &quot;=&quot;, gameUserId)
            .DecrementAsync(&quot;Gold&quot;, itemCount);
        if (rollbackCount != 1)
        {
            _logger.ZLogErrorWithPayload(
                new
                {
                    ErrorCode = ErrorCode.RollbackIncreaseGoldFail,
                    GameUserId = gameUserId,
                    ItemCount = itemCount
                }, &quot;RollbackIncreaseGoldFail&quot;);
        }
    });
    return ErrorCode.None;
}</code></pre>
<hr>
<p><code>Insert</code> 과정 중 오류가 발생하더라도, 현재까지 성공적으로 수행된 작업에 대응하는 롤백이 이미 등록되어 있으므로, 다음과 같은 방법으로 처리할 수 있습니다.</p>
<pre><code class="language-csharp">private async Task RollbackReceiveItemAsync(List&lt;Func&lt;Task&gt;&gt; rollbackActions)
{
    // 필요 시 역순
    foreach (var action in rollbackActions)
    {
        await action();
    }
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>