<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Instrumentation on monkshark.dev</title><link>https://monkshark.github.io/tags/instrumentation/</link><description>Recent content in Instrumentation on monkshark.dev</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Thu, 21 May 2026 10:00:00 +0900</lastBuildDate><atom:link href="https://monkshark.github.io/tags/instrumentation/index.xml" rel="self" type="application/rss+xml"/><item><title>#10 - 다음 작업이 둘일 때, startup 측정기부터 깐 이유</title><link>https://monkshark.github.io/p/page-ide-perf-tracing-first/</link><pubDate>Thu, 21 May 2026 10:00:00 +0900</pubDate><guid>https://monkshark.github.io/p/page-ide-perf-tracing-first/</guid><description>&lt;p&gt;파일 트리, 탭, 분할, 검색, find references, rename, run/stop, 터미널, 다중 선택, 클립보드, drag and drop — 하나씩 잡아 가다 보니 빠진 자리가 손에 꼽혔다. 그리고 옆 책상에는 한 번 갈아엎힐 작업이 줄을 서 있었다. code intelligence 를 tree-sitter + LSP + SCIP 의 세 층으로 다시 짜는 계획. 두 작업이 동시에 &amp;ldquo;다음&amp;rdquo; 이라고 부르고 있었다.&lt;/p&gt;
&lt;h2 id="두-작업이-충돌하는-지점"&gt;&lt;a href="#%eb%91%90-%ec%9e%91%ec%97%85%ec%9d%b4-%ec%b6%a9%eb%8f%8c%ed%95%98%eb%8a%94-%ec%a7%80%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;두 작업이 충돌하는 지점
&lt;/h2&gt;&lt;p&gt;PAGE 의 LSP 통합은 지금 KLS (Kotlin Language Server) 만을 보고 짜여 있다. spawn 도, lifecycle 도, 메시지 라우팅도 한 서버를 가정한다. 자바도, 파이썬도, 다른 무엇도 같은 통로로 들어오려면 그 통로 자체가 추상화돼야 한다. code intelligence 를 다시 짜는 작업의 첫 자리가 그 자리다 — LanguageBackend 라는 한 층을 깔고, 지금의 KLS 라우팅을 그 한 층 밑으로 옮기는 일.&lt;/p&gt;
&lt;p&gt;문제는 이 작업이 본체 IDE 의 LSP 관련 자리를 한 번 다시 만진다는 점이다. find references 의 클라이언트 우회 로직, rename 의 KLS 호출, diagnostics 의 수집 — 다 LanguageBackend 가 깔리면 모서리가 한 번씩 옮겨 앉는다. 그러니 본체 작업이 그 자리들에 새 기능을 더 깔아 두면, 다음 작업이 도착했을 때 그 새 기능들도 같이 옮겨 앉아야 한다. 두 작업을 동시에 굴리면 매 PR 마다 한 번씩 충돌하고, 한 작업을 먼저 다 끝내면 다른 작업의 도착 시점에 재작업이 생긴다.&lt;/p&gt;
&lt;h2 id="선택지-셋"&gt;&lt;a href="#%ec%84%a0%ed%83%9d%ec%a7%80-%ec%85%8b" class="header-anchor"&gt;&lt;/a&gt;선택지 셋
&lt;/h2&gt;&lt;p&gt;쓸 수 있는 길은 셋이었다.&lt;/p&gt;
&lt;p&gt;첫째, 본체 IDE 의 남은 기능을 마저 깔고 그 다음 code intelligence 를 다시 짠다. 손에 잡히는 결과까지의 거리가 가장 짧다. 단, 다시 짤 때 옮겨 앉을 자리가 가장 많다.&lt;/p&gt;
&lt;p&gt;둘째, 둘을 인터리브한다. PR 단위로 번갈아 가며 진행. context switch 비용이 매 전환마다 든다. PAGE 처럼 한 사람이 짜는 코드베이스에서는 그 비용이 다른 비용보다 훨씬 무겁다.&lt;/p&gt;
&lt;p&gt;셋째, 둘 다 끄기 전에 그 둘이 같이 기댈 공통 기반부터 깐다. LanguageBackend 추상화 한 층, 그리고 두 작업 모두 자기 작업의 효과를 grade 할 수 있는 측정 인프라 한 층.&lt;/p&gt;
&lt;p&gt;셋째 길을 골랐다. 이유는 한 줄로 적으면 — 측정 없는 결정이 측정 있는 결정보다 비쌌다.&lt;/p&gt;
&lt;h2 id="왜-측정이-다음-작업들의-공통-기반인가"&gt;&lt;a href="#%ec%99%9c-%ec%b8%a1%ec%a0%95%ec%9d%b4-%eb%8b%a4%ec%9d%8c-%ec%9e%91%ec%97%85%eb%93%a4%ec%9d%98-%ea%b3%b5%ed%86%b5-%ea%b8%b0%eb%b0%98%ec%9d%b8%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;왜 측정이 다음 작업들의 공통 기반인가
&lt;/h2&gt;&lt;p&gt;code intelligence 다시 짜기의 끝자리에 도착했을 때, 그게 잘 짜였는지 어떻게 판단할까. 자바 파일이 열리는 데 걸리는 시간, find references 가 응답하는 시간, 첫 진단 (diagnostics) 이 떠오르는 시간 — 다 시간이다. 그 시간이 지금 어떻게 생겼는지를 모르고 새 작업으로 갈아엎으면 새 작업이 빠른지 느린지조차 모른다.&lt;/p&gt;
&lt;p&gt;그리고 본체 IDE 가 &amp;ldquo;쓸 만하다&amp;rdquo; 라고 불리는 자리도 결국 시간이다. 콜드 스타트가 5초인 IDE 는 기능이 다 들어 있어도 쓸 만하다고 부르기 어렵다. 1초 미만이면 부를 만하다 — 어떤 자리가 그 둘 사이의 어디인지는 측정해야 안다.&lt;/p&gt;
&lt;p&gt;같은 자 가 두 작업 모두에 쓰인다는 점이 셋째 길의 가운데였다. 하나의 측정 인프라를 한 번 깔아 두면 둘 다 그걸 baseline 으로 쓴다. 그래서 측정 부터 깔기로 했다 — 둘 중 어느 작업을 다음 끄기로 결정하든.&lt;/p&gt;
&lt;h2 id="perftracer-의-모양"&gt;&lt;a href="#perftracer-%ec%9d%98-%eb%aa%a8%ec%96%91" class="header-anchor"&gt;&lt;/a&gt;PerfTracer 의 모양
&lt;/h2&gt;&lt;p&gt;코드 자체는 짧다. ConcurrentHashMap 하나, CopyOnWriteArrayList 하나, begin/end 한 쌍, inline trace 블록 하나.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PerfTracer&lt;/span&gt; &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StartupKind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;processStartMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Long&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;open&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;finished&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CopyOnWriteArrayList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PerfMark&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;open&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nowSinceStart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;start&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;open&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PerfMark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nowSinceStart&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;inline&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;end&lt;/code&gt; 가 begin 없을 때 no-op 인 점, throw 가 나도 trace 블록의 end 가 finally 로 호출되는 점, snapshot 이 startMs 순으로 정렬되는 점 — 단위 테스트가 그 가장자리들을 잡는다.&lt;/p&gt;
&lt;p&gt;흥미로운 건 측정기 자체가 아니라 그걸 어디에 박을지였다. Compose Desktop 의 lifecycle 위에서 startup 의 세 phase 를 어떻게 자르느냐.&lt;/p&gt;
&lt;h2 id="compose-desktop-의-함정--첫-번째-시도"&gt;&lt;a href="#compose-desktop-%ec%9d%98-%ed%95%a8%ec%a0%95--%ec%b2%ab-%eb%b2%88%ec%a7%b8-%ec%8b%9c%eb%8f%84" class="header-anchor"&gt;&lt;/a&gt;Compose Desktop 의 함정 — 첫 번째 시도
&lt;/h2&gt;&lt;p&gt;처음 박은 모양은 이랬다. &lt;code&gt;main()&lt;/code&gt; 에서 &lt;code&gt;COMPOSE_INIT&lt;/code&gt; 를 begin, &lt;code&gt;application{}&lt;/code&gt; 안의 LaunchedEffect 에서 그걸 end + &lt;code&gt;WINDOW_SHOWN&lt;/code&gt; 을 begin, Window 안의 LaunchedEffect 에서 &lt;code&gt;WINDOW_SHOWN&lt;/code&gt; 을 end + &lt;code&gt;FIRST_FRAME&lt;/code&gt; 을 측정.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tracer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PerfRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupPhases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COMPOSE_INIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupPhases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COMPOSE_INIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupPhases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WINDOW_SHOWN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;AppContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// AppContent 안의 Window {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nc"&gt;PerfRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupPhases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WINDOW_SHOWN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nc"&gt;PerfRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupPhases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FIRST_FRAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;withFrameNanos&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nc"&gt;PerfRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupPhases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FIRST_FRAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;부모 컴포저블의 LaunchedEffect 가 자식의 LaunchedEffect 보다 먼저 fire 한다고 직관적으로 가정했다. 그래야 &lt;code&gt;WINDOW_SHOWN&lt;/code&gt; 의 begin 이 자식의 end 보다 먼저 일어난다. 첫 실행 결과:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[perf:cold] total 991ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; startup.compose_init 3ms -&amp;gt; 985ms (delta 982ms)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; startup.first_frame 878ms -&amp;gt; 991ms (delta 113ms)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (pending: startup.window_shown)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;window_shown&lt;/code&gt; 이 pending. 다시 말해 begin 이 호출되기 전에 end 가 호출됐다는 뜻이고, 그건 자식 Window 의 LaunchedEffect 가 부모 application 의 LaunchedEffect 보다 먼저 fire 했다는 뜻이다. 시간 값을 보면 더 분명하다 — &lt;code&gt;first_frame&lt;/code&gt; 이 878ms 에 시작해서 991ms 에 끝났고, &lt;code&gt;compose_init&lt;/code&gt; 은 985ms 에 끝났다. 자식 effect 가 시간상 먼저 돌았다.&lt;/p&gt;
&lt;p&gt;Compose Desktop 의 effect ordering 이 부모-자식의 트리 위치에 정해진다는 가정 자체가 틀렸다. application 컴포저블과 Window 컴포저블은 서로 다른 frame clock 위에 올라가 있고, 효과의 진입 순서는 그 둘의 스케줄링이 정한다. 그래서 자식이 부모보다 먼저 fire 할 수 있다 — 실측에서 그랬다.&lt;/p&gt;
&lt;h2 id="두-번째-시도--부모-자식-가정-자체를-뺀-길"&gt;&lt;a href="#%eb%91%90-%eb%b2%88%ec%a7%b8-%ec%8b%9c%eb%8f%84--%eb%b6%80%eb%aa%a8-%ec%9e%90%ec%8b%9d-%ea%b0%80%ec%a0%95-%ec%9e%90%ec%b2%b4%eb%a5%bc-%eb%ba%80-%ea%b8%b8" class="header-anchor"&gt;&lt;/a&gt;두 번째 시도 — 부모-자식 가정 자체를 뺀 길
&lt;/h2&gt;&lt;p&gt;길은 둘이었다.&lt;/p&gt;
&lt;p&gt;하나는 부모 effect 와 자식 effect 사이의 ordering 을 강제하는 길 — 어떤 신호를 만들어 두고 자식 effect 가 그 신호를 기다리게 하기. 깨끗하지만 측정을 위해 lifecycle 에 동기화 코드를 추가하는 셈이라 측정기보다 측정 인프라가 더 무거워진다.&lt;/p&gt;
&lt;p&gt;다른 하나는 부모 effect 를 아예 빼고 자식 Window 의 LaunchedEffect 안에서 세 phase 를 모두 순차로 측정하는 길. &lt;code&gt;withFrameNanos&lt;/code&gt; 를 두 번 부르면 &lt;code&gt;WINDOW_SHOWN&lt;/code&gt; 과 &lt;code&gt;FIRST_FRAME&lt;/code&gt; 의 경계 한 번, 그리고 &lt;code&gt;FIRST_FRAME&lt;/code&gt; 의 종료 한 번이 잡힌다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;frameRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;perf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PerfRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupPhases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COMPOSE_INIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupPhases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WINDOW_SHOWN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;withFrameNanos&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupPhases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WINDOW_SHOWN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupPhases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FIRST_FRAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;withFrameNanos&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StartupPhases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FIRST_FRAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;부모-자식 가정 자체가 빠지니 ordering 이 한 코루틴 안의 줄 순서로 결정된다. 두 작업 모두 자기 작업의 효과를 measure 하는 도구가 코드 한 줄 순서로 정확해진다.&lt;/p&gt;
&lt;p&gt;다시 실행한 결과.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[perf:cold] total 1025ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; startup.compose_init 1ms -&amp;gt; 887ms (delta 886ms)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; startup.window_shown 887ms -&amp;gt; 994ms (delta 107ms)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; startup.first_frame 994ms -&amp;gt; 1025ms (delta 31ms)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;세 phase 가 순차로 잡혔다. pending 없음. baseline 한 줄이 잡힌 자리다.&lt;/p&gt;
&lt;h2 id="첫-baseline--886ms-가-어디서-나왔는가"&gt;&lt;a href="#%ec%b2%ab-baseline--886ms-%ea%b0%80-%ec%96%b4%eb%94%94%ec%84%9c-%eb%82%98%ec%99%94%eb%8a%94%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;첫 baseline — 886ms 가 어디서 나왔는가
&lt;/h2&gt;&lt;p&gt;콜드 스타트 1025ms 중 886ms 가 &lt;code&gt;compose_init&lt;/code&gt;. 이 phase 는 &lt;code&gt;main()&lt;/code&gt; 호출부터 Window 의 첫 LaunchedEffect 진입까지 — 즉 JVM 부팅 + Compose Desktop 런타임 초기화 + AppContent() 컴포지션 + Window 컴포저블의 진입까지 다 포함한다. window_shown 107ms 와 first_frame 31ms 는 그 다음 두 frame 의 길이.&lt;/p&gt;
&lt;p&gt;886ms 가 한 자리 phase 라는 게 첫 인상에 좀 많다. JVM 부팅이 그 중 얼마인지, Compose 런타임이 얼마인지, AppContent() 의 remember/mutableStateOf 가 얼마인지 — 그 안을 더 잘게 자르면 답이 나온다. 다음 작업들이 도착하기 전에 그 안을 한 번 더 갈라 둘지, 아니면 그대로 두고 두 작업의 입출력만 비교할지는 두 작업이 시작하는 모양을 보고 정할 자리다.&lt;/p&gt;
&lt;p&gt;지금 이 자리의 결정은 — 더 잘게 자르지 않는다. baseline 한 줄이 잡혔고, 그게 두 작업의 grade 도구로 충분하다.&lt;/p&gt;
&lt;h2 id="돌아보면"&gt;&lt;a href="#%eb%8f%8c%ec%95%84%eb%b3%b4%eb%a9%b4" class="header-anchor"&gt;&lt;/a&gt;돌아보면
&lt;/h2&gt;&lt;p&gt;두 작업이 동시에 자기가 다음 이라고 부르는 자리에서 한 쪽을 고르지 않고 둘이 같이 기댈 자 부터 깐 결정이었다. 그 결정 자체보다 측정기를 박는 동안 Compose Desktop 의 effect ordering 가정이 한 번 깨진 게 더 기억에 남는다. 부모 컴포저블이 자식보다 먼저 fire 한다 는 직관이 한 줄짜리 실측 데이터 — &lt;code&gt;(pending: startup.window_shown)&lt;/code&gt; — 앞에서 무너졌다. 측정기를 측정 가능한 자리에 박는 일 자체가 작은 회로 한 번을 풀어야 했다.&lt;/p&gt;
&lt;p&gt;또 하나는 측정 인프라의 가치가 인프라 그 자체가 아니라 그 인프라가 잡는 첫 숫자에 있었다는 것. 886ms 라는 숫자가 한 줄 출력된 순간, 다음에 어디를 더 잡을지에 대한 우선순위가 한 번 흔들렸다. 측정 없으면 그 흔들림 자체가 없다 — 어디가 더 비싼지조차 모르니까. 다음 작업으로 가기 전에 측정부터 깔자 라는 결정은 측정의 결과 한 줄을 보고 나니 더 분명해졌다.&lt;/p&gt;
&lt;p&gt;그리고 마지막으로 — 다음 한 발의 방향은 측정이 정한다. 두 작업 중 어느 쪽이 먼저든, 그 작업이 끝났을 때 자가 다시 같은 자리에 닿는다. 자가 자기 자리에 있는 한, 두 작업이 도착하는 순서는 부수적인 결정이 된다.&lt;/p&gt;</description></item></channel></rss>