본문 바로가기

PS

PS 잡기술 1장 - 세팅

PS를 하는 사람은 각자 자신의 컴퓨터에 자신만의 세팅이 있다. 윈도우에서 Codeblock을 쓰는 사람도 많고, 맥 + VSC 조합을 선호하는 사람도 있으며, 심지어 그런거 없이 온라인 컴파일러만으로 높은 퍼포먼스를 보여주는 사람도 있다. 정말 다양한 세팅 환경이 있는만큼, 어떤 것이 정답이라고 단정하기는 어렵다.

 

다만 나는 더 쾌적한 환경에서 PS와 ICPC를 하기 위해 세팅에 대해 많은 연구를 했었고, 그 결과 상당히 효율적인 세팅 체제가 갖출 수 있었다. 본 글은 그동안 ICPC를 준비하며 터득한 세팅 잡기술들을 다룬다. 팀 대회와 개인 대회 모두 유용하게 써먹을 게 많으니 끝까지 읽어보는 것을 추천한다.

0. OS

한국 PS러들의 경우 나를 포함한 대다수가 Windows나 macOS를 사용할 것이다. 그렇기에 저 두 환경에 맞는 세팅을 다루면 좋겠지만, 안타깝게도 이 글의 세팅은 Linux를 기준으로 한다. 일단 본인 주변 기준으로는 PS판이 Linux에 그닥 친숙하지는 않다고 생각하기에, 이 글은 Linux 초심자를 기준으로 작성할 것이다. 그러니 당신이 Linux를 아예 모른다고 해도 딱히 걱정하지 않아도 되고, 반대로 당신이 Linux를 능숙하게 사용할 수 있는 고능인이라면 적당히 자명한 내용은 넘기면 고맙겠다.

 

“야이 포국노야 윈도우 냅두고 왜 굳이 Linux를 씀?” 이라는 의문이 들 수 있는데, Linux는 크게 2가지 장점이 있다.

  1. 메이저 대회는 Linux를 기준으로 한다.
    대표적으로 ICPC와 IOI가 있으며, 그 외 정말 많은 대회가 Linux를 기본 환경으로 사용한다. 실전의 환경과 내 환경이 일치한다는 것은 심리적으로 큰 안정감을 준다. 반대로 환경이 다를 경우 사람에 따라 엄청나게 불편하게 느낄 수도 있고, 이상한 억까를 당할 가능성 또한 매우 높다. 이 때문에 Phoking 팀의 경우 최대한 실전과 같은 환경을 컴퓨터에 맞춰두고 팀연습을 진행했고, 이 덕분에 실제 월파 때 환경으로 인한 불편함은 어쩔수없는키보드이슈를제외하면 거의 느끼지 못했다.
  2. shell 명령어를 통한 실행이 용이하다.
    아니 Codeblock은 F9 딸깍하면 실행되는데 이게 왜 장점임-?? 이런 생각이 들 수 있다. 하지만 shell 위에서 직접 돌리는게 압도적으로 좋은 문제들이 몇몇 있다. 대표적으로 Interactive 문제들이 그런데, 이런 문제들은 보통 사용자가 편하게 실행할 수 있게 testing_tool(로컬 interactor)를 같이 제공해주는 경우가 많다. (특히 ICPC WF나 IOI에서 자주 준다) 보통 # Usage: python3 testing_tool.py -f inputfile <program> 이런 식으로 주는데, shell에선 저걸 그냥 그대로 복붙해서 실행시키면 되는 반면 다른 IDE에서 저걸 스무스하게 실행하는 건, 흠... 난 안해봐서 모르겠지만, 무척 힘들 것이라고 생각된다. 그 외에도 내가 shell에서 직접 특정 컴파일 옵션을 넣는다거나, stress test를 엄청 쉽게 돌리는 유용한 잡기술(뒤에 더 자세하게 다룬다) 같은 것도 다 shell 위에서 가능한 일이다.

이것들 말고도 말할게 많지만, 그건 나중에 알아보자.

아무튼 Windows 유저들이여, WSL 설치가 완료되었는가? 그럼 이제 WSL을 Visual Studio Code와 연동해보자. 컴퓨터에 VSC가 깔려있다면 이건 매우 쉬운데 그냥 cmd에서 WSL을 접속한 후 code . 를 치면 알아서 짜잔 하고 열린다. 혹시 잘 안되면 아무튼 필자의 잘못은 아닌 듯 하니 구글링이나 GPT한테 물어보자.

mac은 Linux랑 별 차이 없으니 그냥 평소 하듯이 VSC를 키면 된다.

 

여기까지 왔다면 이제 뒤쪽 내용으로 가보자.

1. 컴파일 & 실행

성공적으로 VSC를 열었는가? 이제 Terminal 메뉴에서 새 터미널을 열고 아무거나 쳐보자. 뭔가 상호작용이 잘 된다 싶으면 잘 따라온거다.

linux 명령어는 꽤 다양하지만, PS를 할 때는 lscd만 알면 충분하다고 생각한다. 사용법은 생략한다. 구글링을 해보자.

이제 PS의 기본 초식이라고 할 수 있는 컴파일과 실행을 해보자.

1.1 컴파일

내 기억상 Windows에서 c++ 컴파일 세팅을 하려면 MinGW로 고통스러운 무언가를 하거나, 오로지 컴파일 하나를 위해 CodeBlock을 통째로 설치해야 하는 부조리한 상황을 겪게 된다. 반면, linux(+ Mac)은 컴파일이 매우 쉽다.

 

우선 폴더에 solve.cpp를 만들고 코드를 짰다고 해보자. 이제

g++ solve.cpp -o solve_exefile

를 치면 컴파일이 완료되며, solve_exefile 라는 실행 파일이 생기게 된다. 매우 직관적이다.

여기서 g++이 없는 경우가 간혹 있는데, 그럴땐 apt-get update && apt install g++을 하면 설치가 된다. 혹시 막히면 그냥 gpt한테 물어보자.

1.2 실행

실행은 더욱 간단하다. 그냥

./solve_exefile

를 치면 끝이다. 코드블럭 감성으로 컴파일과 실행을 동시에 하고 싶다면, && 를 통해 명령어를 이어서 붙여보자.

g++ solve.cpp -o solve_exefile && ./solve_exefile

참고로 이해의 편의를 위해 파일 이름들을 길게 썼는데, 실제로 할때는 그냥 자기가 편한 파일 이름 맘대로 쓰자.

2. sanitizer

챕터 1의 내용이 단순한 컴파일+실행의 기본이고, 아마 여기까지는 꽤 많은 사람들이 이미 아는 내용들이라 좀 지루하게 느꼈을지도 모르겠다. 허나 이번 chapter 2. sanitizer는 비교적 알려지지 않았으나 엄청나게 강력한 잡기술이니 꼭 알아가길 바란다.

 

아마 여러분이 PS를 좀 해봤다면 Segmentation fault (core dumped) 이 뜨는걸 필히 겪어봤을 것이다. (windows는 뭐라고 뜨는지 몰루) 대부분의 경우 이는 undefined behavior(UB) 때문이다. 아마 다들 다양한 UB를 경험해봤을 것이라고 생각하는데, 배열 arr에서 arr[-10]을 참조하는 코드를 짤 수 있고, 8을 0으로 나눈 나머지를 구하려는 블런더를 할 수도 있으며, 그 외에 온갖 수많은 신박한 방법으로 컴퓨터를 아프게 만들어봤을 것이다. 여기서 소개하는 sanitizer는 이런 UB를 매우 효과적으로 잡을 수 있는 기술이다.

 

위에 적어둔 컴파일 명령어에 다음 -fsanitize=undefined 를 추가해보자. 아래 명령어를 복붙하면 된다.

g++ solve.cpp -fsanitize=undefined -o solve_exefile && ./solve_exefile

저걸 넣으면 무슨 일이 생기느냐. solve.cpp에 이런 코드가 적혔다고 해보자.

눈썰미가 좋은 사람들은 cnt가 최종적으로 5050이 되고, 크기 5000짜리 배열에서 5050번째 원소를 참조하는 UB를 바로 눈치챘을 것이다. 허나 당장 i <= 99로 범위가 1만 줄어도 cnt는 허용 범위 안으로 들어오고, 저런 코드가 100줄만 넘어가도 저런 ub를 바로바로 눈치채기는 은근히 힘들다. 하지만 저 sanitizer를 키고 실행을 하면?

짠. UB를 바로 잡아준다. 그냥 단순히 UB를 잡는 것뿐만 아니라, UB의 원인, 그리고 UB가 난 코드의 위치마저 바로 잡아준다.

 

로컬에서 런타임 에러가 났을때 대처법이 다들 어떨지는 모르겠으나, 본인의 경우 sanitizer를 알기 전에는 그냥 cout<<"HI\n";를 코드 여러곳에 치면서 이분탐색으로 세그폴트가 난 곳을 찾아다녔고, 이는 O(log코드길이)의 시간이 소요됨을 수학적으로 알 수 있다. 저걸 너무 많이 해봐서 순식간에 에러를 찾아내는 사람도 있겠지만, 나 같은 범부들은 에러의 원인을 아는데 은근히 많이 시간이 소모되고, 이는 일반적인 느긋한 PS를 할 때도 피곤한 과정이며, 1분 1초가 소중한 대회에서는 재앙이다. 

 

하지만 sanitizer를 쓴다면 그냥 1초만에 런타임 에러가 발생한 곳이 몇 번째 줄인지 알려주고, 심지어 에러의 원인도 알려준다. 위에서 로그를 보면 알겠지만, out of bound의 경우 index 5050를 정확히 짚는 걸 볼 수 있다. 그 외에도 다양한 UB를 정확한 이유와 함께 분류를 해주니 디버깅하는 입장에서 매우 편해진다.

 

그리고 중요한 사실이 있는데, UB 중에는 실행 자체에는 문제가 없는, segfault 없이 조용히 거지같은 답만 띡 내놓는 암살형 UB들이 여럿 있다. PS 유구한 전통인 mod 998244353 문제를 생각해보자. 아마 다들 한번쯤은 코드를 다 짜고 설레는 마음으로 예제를 똭 넣었는데 ans: -198471281 막 이런 trash가 튀어나오는 경험을 해봤을 것이다. 분명 어디선가 int overflow가 발생한 것 같은데, 어딘지 모르겠고. 벌써 머리가 아프지 않나? 하지만 sanitizer와 함께라면 저런 암살형 UB들을 잡는데도 엄청나게 유용하다. overflow 말고도 (래오씨가정말많이저지른) 변수 초기화 안하고 쓰기, arr[배열크기+1] 참조했는데 에러없이 실행되기넘어가기 등 다양한 암살형 UB를 모두 잡아내준다. 다른 IDE의 디버거를 봐도 저거까지 해주는 건 잘 못봐서, 여러모로 sanitizer가 강력하다고 할 수 있다.

 

지금까지 로컬에서 발생하는 UB를 다뤘는데, sanitizer는 맞왜틀을 해치우는 데도 꽤 용이하다. 맞왜틀의 원인으로는 크게 1) 풀이에오류가있음 과 2) 코드에서실수함 정도가 있는 것 같은데, 1)은 구조적으로 답이 없고, 대신 2)를 잘 찾아준다. 실제 정답을 몰라도 되니, input으로 대충 큰 데이터를 막 넣어보자. 코드를 잘못 짰다면 예제의 작은 데이터보다는 저런 큰 데이터에서 UB가 꽤 잘 뜨고(특히 overflow), 그 지점으로부터 탐색을 해서 잘못된 부분을 찾으면 된다. 물론 막 if(true and false) cout << ans; 처럼 너무 지능이 낮은 코드는 저런걸로도 못 잡고, 그럴땐 그냥 짠 사람 머리를 후리는게 더 효과적이다.

 

아무튼 저런 이유들로, 개인적으로는 sanitizer의 유무 하나로 구현 퍼포먼스가 200점 이상 차이날 수 있다고 생각한다.

 

sanitizer의 또 다른 사기성 중 하나는 그 사용법이 너무나도 간단하다는 것이다. 위에서 언급했듯, 그냥 평소 컴파일 구문에 -fsanitize=undefined 옵션 하나를 더 쓰는게 끝이다. ICPC에서 cph나 기타 다른 확장 프로그램들을 사용 못해서 자신의 컴퓨터와 괴리감을 느끼는 경우가 은근히 많은데, 저건 그냥 대회 시간 5시간 중 10초 정도를 투자하여 미리 세팅해두면 내 컴퓨터에서 느끼는 구현의 편안함을 실제 대회에서도 그대로 느낄 수 있다. 계속 강조중인데, 로컬과 대회장이 비슷한 건 엄청 큰 강점이다.

 

sanitizer의 단점은, 사실 거의 없고 결정적으로 딱 하나 있는데, Windows에서 못 쓴다. 기본적으로 Linux gcc에 내장된 기능이라 MinGW에서 꽤 에러가 많고, 쓰려면 뭔가 엄청 빌드를 잘 해야 한다고 알고있다. Windows에서 쓰려면 WSL을 통해 Linux 환경을 만들어야 제대로 쓸 수 있으니 쓰려면 WSL로 갈아타길 권장한다. 참고로 Mac은 Linux랑 별 차이 없으니 걍 해도 된다. 대 황 맥.

 

그리고 각 operation마다 UB인지 아닌지 검사를 하면서 실행을 하기 때문에 실행 속도가 느려진다는 단점이 있다. 체감상 2배 정도 실행시간이 느려지는 것 같다. 다만 로컬에서 돌리는 예제들은 50ms 이내에 도는 작은 것들이기에 체감이 크게 안 되고, 혹시 서울C마냥 TL이 너무나도 빡빡해 큰 데이터의 실행시간을 정밀하게 봐야 하는 경우에는 문제가 될 수 있다. 저런 상황이 오면 일단 sanitizer를 킨 채로 UB 유무를 확인 후, 그 다음에 옵션을 지우고 다시 돌려서 실행 시간을 측정해보는 센스를 발휘해보자.

2.1 sanitizer 심화 - MLE 잡기

위에서 열심히 sanitizer 찬양글을 썼는데, 사실 sanitizer는 무적이 아니다. 아쉽게도 -fsanitize=undefined 옵션은 메모리 이슈로 생기는 UB들을 전부 잡아주지는 않는다. MLE로 인한 segfault는 애초에 UB는 아니기에 잡히지 않으며, 그 외에 vector에서 out of bound가 나는 것도 잡아주지 않는다. (근데 그냥 배열에서 난 oob는 또 잡아주는데, 정확히 어떤 기준인지 모르겠다) 이 이슈들까지도 해결하기 위해서는 undefined에 address 옵션까지 붙여주면 된다. 이를 실제로 치면 저렇게 되겠다.

g++ solve.cpp -fsanitize=undefined,address -o solve_exefile && ./solve_exefile

다만 이 방법은 뭔가 이슈가 있는데, 로컬 ASLR이랑 충돌해서 실행을 거부하는 상황이 자주 발생한다. 정확히는 AddressSanitizer:DEADLYSIGNAL -> 얘가 무한히 뜨는 버그가 걸린다. 다행히도 저것도 해결 방법이 있긴 한데,

sudo sysctl -w vm.mmap_rnd_bits=28

를 실행하면 일시적으로 해결이 된다. (porofix라고 부르도록 하겠다) 그런데 porofix는 sudo(관리자권한)가 반드시 필요해서, 로컬에서는 내 컴퓨터니 상관 없지만 대회장에서는 쓸 수 없다는 치명적인 단점이 있다. 실제로 올해 ICPC WF 예비소집때도 porofix를 할 수가 없어서 address 옵션을 포기했고 undefined 옵션만 넣고 대회를 치뤘다. 고로 address 옵션은 자기 컴퓨터에서만 쓰도록 하고, 팀연습에서는 쓰지 말 것을 권장한다. (혹시 sudo 없이 저거 고치는 법 아시면 댓글로 따로 알려주시면 감사하겠습니다!)

3. alias

사람들이 codeblock을 애용하는 이유는 길고 현학적인 shell 명령어 대신 F9 딸깍으로 실행을 할 수 있기 때문이라고 생각한다. 나 또한 적극 동의한다. 코드포스에서는 1분 1초가 아까운데 언제 저걸 다 치나?

 

이런 당신을 위한 alias를 소개한다. alias는 일종의 단축명령어 기능인데, 자주 쓰는 긴 명령어를 짧게 줄여서 쓸 수 있게 해준다. 바로 위 2.1에 말한 porofix도 사실 alias로 등록이 되어있는 명령어다. 가령, 앞에서 나온 컴파일 명령어를 다음 alias로 줄일 수 있다.

alias porosolve='g++ solve.cpp -fsanitize=undefined -o solve_exefile && ./solve_exefile'

이제 터미널에서 길고 현학적인 컴파일 문구 대신, 그냥 porosolve만 치고 누르면 컴파일+실행이 된다. 단축키의 기능을 명령어로 하는 것이다. F9 버튼 하나 누르기보다는 조금 더 할게 많지만, 이정도면 충분히 합의가 봐지는 정도의 편의성이라고 생각한다. 저것마저 귀찮다면 VSC를 뜯어서 F9에 porosolve를 연동해두는 방법도 있으나, 매우 귀찮고 실제 대회에서도 하기 어렵기에 권장하지 않는다.

 

참고로 porosolve 명령어는 팀 대회에서도 매우 유용하다. ICPC PhoKing 팀의 경우, 항상 내가 대회 첫 시작 2분 동안 컴퓨터 로그인, VSC 켜기, 문제 폴더 만들기, 그리고 위 alias porosolve=.... 를 치는 것까지 마친 후 문제를 읽는다. 그러면 남은 시간동안 팀원들은 porosolve(실제 대회에서는 "sol" 처럼 더 짧은 단어를 사용한다)만 쳐서 별 시행착오 없이 실행을 할 수 있게 되고, 결과적으로 시간과 신경량에서 이득을 볼 수 있다. VSC를 주로 쓰는 팀의 경우 정말 좋은 루틴이라고 생각해서, 다른 팀들도 해보는 걸 추천한다.

 

저 명령어에는 잡기술이 하나 있는데, input 파일을 따로 만들고 실행하고 싶을때 별도의 명령어 수정이 필요하지 않다. 명령어 순서 구조상 porosolve < in 을 해도 in 파일이 입력으로 들어가지기에 매우 편리하다.

3.1 alias 심화

alias는 다 좋으나, 작업 중이던 터미널이 꺼지면 애써 만든 단축어들이 다 초기화된다는 단점이 있다. 대회장에서는 터미널을 끌 이유가 없으니 상관없으나, 본인 컴퓨터에서 VSC 창을 평생 켜진 않을테니 이건 꽤 귀찮은 부분이다. 

 

다행히도 Linux 개발자는 인간미가 넘치기에, 단축어들을 영구 저장할 수 있는 방법이 있다. 로컬의 .bashrc 파일에서 명령어를 입력하고 source ~/.bashrc를 실행하면 되는데, 이건 여기 글보다 구글링 후 다른 글을 참조하는 걸 추천한다. 본인의 경우 poroacmicpc, porosolve, porosolvefast, porotemplate, porofix를 저장해 사용 중이다. 이중 porotemplate이 엄청 유용해서 소개를 해보고자 한다.

alias porotemplate='cp ~/님로컬에서template.cpp의주소 ./solve.cpp'

이 명령어를 입력하면 현재 자신이 위치한 폴더에 템플릿 코드를 solve.cpp로 복사가 된다. 코드포스나 백준을 할 때 템플릿 코드를 직접 복붙을 안해도 되기에 엄청나게 유용하며, 이후 바로 porosolve 콤보로 실행까지 되기에 매우 편리하다. 지금은 ICPC도 끝났겠다, porotemplate 이후 -segtree, -fft 뭐 이런 인자들을 넣어서 원하는 템플릿을 쏙쏙 고를 수 있게 설계된 speedy 코드포스 환경을 개발중인데, 사실 저걸로 퍼포가 크게 변하진 않을 것 같다.

 

아무튼 이 정도까지 세팅을 하면, 환경적인 이슈로 PS하는데 불편함을 느낄 일은 거의 없을 것이다. 세팅 방법들도 다 크게 어렵지 않은 것들 뿐이니 적당히 좋은 애들 골라서 사용해보면 좋을 것 같다.

4. ICPC용 세팅

위에서 말한 내용들을 요약한 것이다. 당신(의 팀)이 ICPC 본선에 나가는데 Linux에 익숙하지 않고, 딱히 위에 것들을 읽기도 귀찮다면 그냥 얘들만 숙지하고 가자.

  • 대회 전 간단한 Linux 명령어 사용법은 알고 가자. cd와 ls만 알면 된다.
  • 일단 컴퓨터 로그인을 하고, VSC를 키고, 바탕화면 폴더를 연다.
  • mkdir A B C D E F G ... M  을 실행하자. 각 문제의 폴더가 생성된다. 각 문제 폴더를 분리해두는 습관이 있으면 좋다.
  • alias sol='g++ solve.cpp -fsanitize=undefined -o solve && ./solve' 을 넣어라. 파일이랑 명령어 이름은 팀끼리 합의를 봐 정하자.
  • 이후 바탕화면 폴더에 temp.cpp를 만들고 팀 공용 템플릿을 적당히 치자. 세터는 여기까지 하고 본인에게 할당된 문제를 읽자.
  • 이후 코딩하는 사람은 터미널에서 풀 문제의 폴더에 들어간 후, temp.cpp의 내용을 복붙해 solve.cpp 파일을 만들자. 코딩을 마친 후 sol 명령어로 실행을 하면 된다.

sanitize는 완전 처음 써보면 빨간 글씨 때문에 패닉이 올 수 있으니, 적당히 손에 익히고 나서 쓰거나, 아니면 그냥 빼기를 권장한다.

 

여기까지 해서, PS WSL 세팅론에 대한 글을 마친다. 사람마다 맞는 세팅이 다를 수 있으니 적당히 좋아보이는 것만 참조하시고, 다들 즐거운 PS 하길 바란다.

'PS' 카테고리의 다른 글

SCPC 2025 후기  (11) 2025.09.18
UCPC 2025 본선 후기  (15) 2025.07.28
2025 ICPC Asia Pacific Championship 후기 - 2부  (4) 2025.03.11
2025 ICPC Asia Pacific Championship 후기 - 1부  (0) 2025.03.09
[SW멤버십 블로그 투고] 지애상수 풀기  (2) 2025.02.11