Raku 성능 최적화

Raku 성능 최적화 (Performance)

이 페이지는 Raku 맥락에서의 컴퓨터 성능을 다뤄요. 성능을 올리겠다고 마음먹었다면, 그 전에 이 문서의 핵심 교훈 하나를 먼저 새겨두면 좋아요.

출처: Raku 공식 문서 — Performance and Raku

본문

먼저, 프로파일링부터 (First, profile your code)

엉뚱한 코드에 시간을 낭비하지 않도록 먼저 프로파일링으로 코드에서 "결정적인 3%"를 찾아내세요. 이 문서의 나머지 부분은 그 방법을 보여줘요.

now - INIT now로 시간 재기 (Time with now - INIT now)

now - INIT now 형태의 표현은 코드 조각의 실행 시간을 재는 멋진 관용구예요. 여기서 INITRaku 프로그램 실행의 phase 중 하나예요.

m: your code goes here 형식의 #raku 채널 evalbot을 쓰면 이렇게 시간을 바로 확인할 수 있어요:

m: say now - INIT now
rakudo-moar abc1234: OUTPUT«0.0018558␤»

INIT 왼쪽의 nowINIT phase 동안 실행되는 오른쪽 now보다 0.0018558초 나중에 실행돼요. 그래서 그 차이가 곧 실행 시간이 되죠.

로컬에서 프로파일링 (Profile locally)

MoarVM 백엔드를 쓴다면 Rakudo 컴파일러의 --profile 명령줄 옵션으로 프로파일 데이터를 HTML 파일로 쓸 수 있어요.

이 파일을 열면 "Overview" 섹션이 나오는데, 프로그램이 어떻게 돌았는지 전체적인 데이터(총 실행 시간, 가비지 컬렉션에 쓴 시간 등)를 보여줘요. 여기서 중요한 정보 중 하나가 전체 호출 프레임(블록) 중 해석(interpret)된 것(가장 느림, 빨간색), speshed(더 빠름, 주황색), jit된 것(가장 빠름, 초록색)의 비율이에요.

두 번째 섹션 "Routines"가 아마 가장 자주 들여다볼 곳이에요. 루틴(블록)의 이름·파일·줄 번호, 실행 횟수, 포함 시간(inclusive, 그 루틴 + 그 루틴이 호출한 모든 루틴의 시간), 배타 시간(exclusive, 그 루틴 자체의 시간), 그리고 해석/spesh/jit 여부(Overview와 같은 색 코드)가 들어 있는 정렬·필터 가능한 표예요. 배타 시간으로 정렬하면 어디서부터 최적화를 시작할지 좋은 단서를 얻어요. 파일명이 SETTING::src/core/gen/moar/로 시작하는 루틴은 컴파일러에서 온 것이니, 내 코드만 보고 싶다면 프로파일한 스크립트의 파일명을 "Name" 검색 상자에 넣으면 돼요.

"Call Graph" 섹션은 "Routines" 섹션의 정보 대부분을 불꽃 그래프(flame graph)로 보여줘요.

"Allocations" 섹션은 할당된 여러 타입의 양과 어떤 루틴이 할당했는지 알려줘요.

"GC" 섹션은 발생한 모든 가비지 컬렉션의 상세 정보를 담아요.

"OSR / Deopt" 섹션은 OSR(On Stack Replacement, 루틴이 해석에서 spesh/jit로 "승격"되는 것)과 Deopt(반대로 spesh/jit 코드가 해석으로 "강등"되는 것)에 대한 정보를 줘요.

프로파일 데이터가 너무 크면 브라우저가 파일을 여는 데 오래 걸릴 수 있어요. 그럴 땐 --profile=filename 옵션으로 .json 확장자 파일에 쓴 다음 Qt 뷰어로 여세요.

더 큰 프로파일을 다뤄야 한다면 .sql 확장자로 출력해요. 프로파일 데이터를 일련의 SQL 문으로 써서 SQLite에서 열 수 있게 해 주죠.

# create a profile
raku --profile=demo.sql -e 'say (^20).combinations(3).elems'

# create a SQLite database
sqlite3 demo.sqlite

# load the profile data
sqlite> .read demo.sql

# the query below is equivalent to the default view of the "Routines" tab in the HTML profile
sqlite> select
      case when r.name = "" then "<anon>" else r.name end as name,
      r.file,
      r.line,
      sum(entries) as entries,
      sum(case when rec_depth = 0 then inclusive_time else 0 end) as inclusive_time,
      sum(exclusive_time) as exclusive_time
    from
      calls c,
      routines r
    where
      c.routine_id = r.id
    group by
      r.id
    order by
      inclusive_time desc
    limit 30;

개발 중인 차세대 프로파일러는 moarperf로, .sql이나 SQLite 파일을 받고 기존 프로파일러보다 새로운 기능이 많아요. 다만 의존성이 더 많아서 쓰기 전에 모듈 몇 개를 설치해야 해요.

프로파일 정보를 해석하는 법을 배우려면 prof-m: your code goes here evalbot(위에서 설명)을 쓰고 IRC 채널에 질문하세요.

컴파일 프로파일링 (Profile compiling)

코드를 컴파일하는 데 걸리는 시간과 메모리를 프로파일링하고 싶다면 Rakudo의 --profile-compile 또는 --profile-stage 옵션을 사용하세요.

벤치마크 만들기·보기 (Create or view benchmarks)

perl6-bench를 사용하세요. 여러 컴파일러(보통 Perl, Raku, NQP 버전)로 돌리면 각 결과가 같은 그래프에 겹쳐져서 빠르고 쉽게 비교할 수 있어요.

문제 공유하기 (Share problems)

위 기법들로 개선할 코드를 찾았다면, 그 문제를 다른 사람과 다루고 공유하기 시작하면 돼요.

  • 각 문제를 한 줄이나 핵심(gist)으로 압축하고, 성능 수치를 제공하거나 prof-m: your code or gist URL goes here로 프로파일할 수 있을 만큼 작게 만들어요.
  • 필요한 최소 속도 향상(또는 RAM 절감 등)과 그 목표를 이루는 비용을 생각해요. 그 개선이 사람들의 시간과 에너지로 치면 얼마나 가치가 있나요?
  • 프로덕션 환경에서 쓰는 Raku인지 그냥 재미로 하는 건지 다른 사람에게 알려주세요.

문제 해결하기 (Solve problems)

반복해서 강조하지만: 엉뚱한 코드에 시간을 낭비하지 마세요. 먼저 코드의 "결정적인 3%"를 찾으세요.

한 줄씩 (Line by line)

코드를 한 줄씩 개선하는 빠르고 재미있고 생산적인 방법은 #raku evalbot camelia로 다른 사람들과 협업하는 거예요.

루틴별로 (Routine by routine)

다중 디스패치(multi-dispatch)를 쓰면 기존 루틴 "옆에" 새로운 변형을 끼워 넣을 수 있어요:

# existing code generically matches a two arg foo call:
multi foo(Any $a, Any $b) { ... }

# new variant takes over for a foo("quux", 42) call:
multi foo("quux", Int $b) { ... }

foo 정의가 여러 개인 것 자체의 호출 오버헤드는 대개 무시할 만해요(아래 where 논의 참조). 그래서 새 정의가 기존 정의들보다 특정 경우를 더 효율적으로 처리한다면, 그 경우 코드가 그만큼 더 효율적이 된 거예요.

타입 검사·호출 해석 빠르게 하기 (Speed up type-checks and call resolution)

대부분의 where 절 — 따라서 대부분의 subset — 은 매치될 가능성이 있는 모든 호출에 대해 동적(런타임) 타입 검사와 호출 해석을 강제해요. 이는 컴파일 타임보다 느리거나 최소한 늦어요.

메서드 호출은 대개 최대한 늦게(런타임에 동적으로) 해석되고, 서브 호출은 대개 컴파일 타임에 정적으로 해석돼요.

더 나은 알고리즘 고르기 (Choose better algorithms)

언어·컴파일러와 무관하게 큰 성능 향상을 내는 가장 확실한 기법 중 하나는 더 적절한 알고리즘을 고르는 거예요.

대표적인 예가 Boyer-Moore예요. 큰 문자열에서 작은 문자열을 찾으려고 할 때, 가장 단순한 방법은 두 문자열의 첫 문자를 비교하고, 같으면 두 번째 문자를 비교하고, 다르면 작은 문자열의 첫 문자를 큰 문자열의 두 번째 문자와 비교하면서 진행하는 거예요. 반면 Boyer-Moore 알고리즘은 작은 문자열의 마지막 문자부터 큰 문자열의 해당 위치 문자와 비교하기 시작해요. 대부분의 문자열에 대해 Boyer-Moore는 작은 문자열 길이를 N이라 했을 때 알고리즘적으로 N배에 가깝게 빨라요.

다음 두 섹션은 Raku에서 특히 쉽게 할 수 있는 알고리즘 개선의 두 큰 범주를 다뤄요. 이 주제 전반에 대해 더 알고 싶다면 위키백과의 알고리즘 효율성 문서를 읽어 보세요. 특히 마지막 부분의 'See also' 섹션을요.

순차/블로킹 코드를 병렬/비블로킹으로 (Change sequential/blocking code to parallel/non-blocking)

이것은 또 하나의 매우 중요한 알고리즘 개선 부류예요. Raku의 병렬성·동시성·비동기성 슬라이드와 대응하는 동영상을 참고하세요.

기존의 고성능 코드 활용하기 (Use existing high performance code)

Raku 안에서 쓸 수 있는 고성능 C 라이브러리가 많고, NativeCall은 그 래퍼를 쉽게 만들어 줘요. C++ 라이브러리 지원도 실험적으로 있어요.

Raku에서 Perl 모듈을 쓰고 싶다면 Raku 타입과 Metaobject Protocol을 섞어 쓰세요.

더 일반적으로, Raku는 다른 언어와 매끄럽게 상호운용하도록 설계됐고 다른 언어의 라이브러리 사용을 돕는 모듈이 여럿 있어요.

Rakudo 컴파일러가 더 빠른 코드를 만들게 하기 (Make the Rakudo compiler generate faster code)

지금까지 컴파일러의 초점은 코드 생성 속도나 생성된 코드가 얼마나 빠르고 가벼운지가 아니라 정확성이었어요. 하지만 언젠가는 바뀔 것으로 예상돼요. libera.chat IRC 채널 #raku와 #moarvm에서 컴파일러 개발자들과 기대 사항을 이야기해 보세요. 더 좋은 건 직접 기여하는 거예요:

  • Rakudo는 대부분 Raku로 작성돼요. Raku를 쓸 수 있다면 컴파일러를 해킹할 수 있고, 여러분 코드(그리고 모두의 코드) 속도에 영향을 주는 기존 고수준 코드의 방대한 몸집을 최적화할 수도 있어요.
  • 컴파일러의 나머지 대부분은 NQP이라는 Raku의 부분집합과 비슷한 작은 언어로 쓰여요. Raku를 쓸 수 있다면 중간 수준의 NQP 코드도 (순수 언어 관점에서라면) 꽤 쉽게 배워 쓰고 개선할 수 있어요. NQP와 Rakudo 내부를 파고들려면 NQP and internals course부터 시작하세요.
  • 저수준 C 해킹이 재미있다면 MoarVM을 살펴보고 libera.chat IRC 채널 #moarvm(로그)을 방문하세요.

더 필요한 아이디어가 있다면? (Still need more ideas?)

이 페이지에서 아직 다루지 않은 알려진 Rakudo 성능 약점으로는 gather/take, junction, 정규식, 그리고 전반적인 문자열 처리가 있어요.

어떤 주제가 이 페이지에서 더 다뤄졌으면 한다면 PR을 보내거나 누군가에게 아이디어를 알려주세요. 감사합니다. :)

원하는 결과를 얻지 못했나요? (Not getting the results you need/want?)

이 페이지의 모든 방법을 시도했는데도 안 된다면, #raku에서 컴파일러 개발자와 이야기해 보는 걸 고려하세요. 그래야 여러분의 사용 사례와 그동안 알아낸 것에서 배울 수 있어요.

개발자가 상황을 알게 됐다면, 정보에 근거한 답변을 기다릴 충분한 시간(문제와 해결책의 성격에 따라 며칠에서 몇 주)을 주세요.

그것도 안 된다면, 넘어가기 전에 우리 사용자 경험 저장소에 경험에 대한 이슈를 남겨 주세요.

감사합니다. :)

더 알아보기 (Learn more)