채팅창에서 욕이 필터링되는 방법
지난 회고록에서도 얘기했지만, 고등학생 2학년이 갓 되었을 때의 나는 직전 프로젝트에서 미완성의 결과물을 내놨음에도 멋대로 자신에 차있던 상태였다. 비록 전체를 완성하진 못했지만 코딩을 해서 뭔가를 만들어내는 것에 큰 부담이 없었고 뭘 시키든 곧잘 해낼 수 있을 거란 막연한 확신에 가득했다. 물론 그저 할 줄 아는 것과 잘 할 줄 아는 것의 차이는 분명히 인지하고 있었기 때문에 더 잘 해내고자 마구 공부하려는 열의가 불타고 있었다.
그런 한편, 평일의 저녁 식사 후에 있던 전공수업 시간엔 지난 1년 동안 전혀 보지 못했던 웬 새로운 선생님이 나타나 우리를 가르치시게 되었다. 내가 지금 개발자로써 기본을 하고 있는지는 아직 잘 모르겠지만, 하고 있다면 바로 이 선생님 덕분이라고 말할 수 있을 정도로 많이 것을 가르쳐주셨던 분이다. 특이하게 성이 봉씨셔서 봉쌤이라 부르곤 했었다. 그 봉쌤께서 며칠에 걸쳐 우릴 시험한 뒤 과제를 내주셨는데, 바로 채팅창에서의 욕 필터링 모듈을 만드는 것이었다.
구현할 내용은 상당히 간단하다. 어떤 문장을 기입하여 입력하면 그 문장 중에서 비속어가 포함되었는지를 판단하고 asterisk(*)로 변환하는 것이다. 그 과정에서 필터링할 욕의 목록은 별도의 파일로 제공된다. 문제는 이걸 100만번 수행한 후의 결과를 빠르게 내놓는 것이었다. 지금에 와선 매우 간단한 일인데다 별 고민할 것 없이 뚝딱 만들어내겠지만, 당시엔 상당히 많은 부분에서 고민거리였다.
우선, 욕은 2000개 가량이 되었다. 물론 우리가 흔히 쓰는 욕이 정말 2000개나 되어서 그것들을 넣은 것은 아니었고 대충 임의로 만들어낸 한글 문자열의 나열들일 뿐이긴 했다. 그리고 한 문장 안에 욕이 들어가는 수는 제약이 없었다. 또한 애석하게도 욕 비슷한 단어는 걸러내면 안 되었다. 목표는 문장 당 1초. 말로만 들었을 땐 되는 거니까 내주시는 거겠거니 하면서 빨리 해내고 더 빠르게 다음으로 진도를 넘어가고 싶었다. 게임을 만들어야 하는데 필터링이라니 시간 낭비다 싶었으니 말이다.
일단, 해결을 위해서 우리는 C를 배울 1학년 시절엔 따로 가르쳐주지 않았던 linked list를 구성해야 했다. 다행히 나는 일전에 포인터를 써본적이 많았고, 슬쩍슬쩍 읽어보던 자료구조 책에서 linked list를 읽어본 적이 있어 그 개념에 어렵지 않게 도달할 수 있었다. 몇몇 친구들을 도와주면서 또 그 개념을 단단히 다져볼 수도 있기도 했다. 그렇게 list를 만든 후에야 문제에 접근할 수 있었다.
가장 단순한 방법으로 모든 문장에서 하나의 문자씩 비교를 하는 방법으로 문제를 풀면 그 당시 CPU 기준으로 천초 단위의 시간이 소요되었다. 나는 이를 줄이기 위해 doubly linked list도 써보고 이렇게도 해보고 저렇게도 해봤지만, 여전히 시간은 크게 줄어들지 못했다. 그렇게 하루를 꼬박 보내고서도 속도가 나아질 기미가 안 보이자 이건 안 되는 거지만 그냥 시켜본 거구나 싶었다. 적당히 만들고 난 후 나는 계속 공부 중이던 C++의 방식을 이 과제에 적용해보면서 남은 주말 시간을 보냈고, 검사의 날이 다가왔다.
나는 성능이 비록 비루하지만 이건 내 탓은 아닌 것 같다는 뉘앙스로 호기롭게 얘기했다. 소요시간을 전해들은 봉쌤은 어처구니 없다는 듯이 다른 해결자를 찾았다. 실력 좋은 친구는 백초 대에서 결과를 얘기하고 있었다. 이 결과적 차이의 원인을 예상한 듯 봉쌤은 내게 코드를 다 스스로 짰냐고 물어보셨고, 나는 그렇다고 답하면서 코드를 보여드렸다. 그러면서 나는 '그래 노력해봤는데 잘 안됐지' 정도의 대답을 예상하였는데, 막상 들려온 말은 전혀 예상 밖의 것이었다. '누가 문자열 비교하는 코드를 일일이 직접 짜? 니가 그렇게 대단해?' 라니.
당시 내가 본 string.h 를 통해 얻을 수 있는 함수들의 기능은 생각보다 단순하고 별 것 없어보였다. 문자열의 길이가 몇인지라든가, 두 문자열의 내용이 같은지를 비교하는 등 정말 기능적으로는 왜 굳이 함수로 만들었을까 싶었다. 난 주말 내내 내가 이런 걸 직접 할줄 안다는 것을 보이기 위해 다 C 코드로 직접 짜냈었다. 결과가 똑같은데 뭐가 다르겠어 싶었던 것인데 성능이 달랐던 것이다.
표준 함수들은 최대한 최적화된 결과를 내놓기 위한 대가들, Windows에서 코딩하고 있었으니 Microsoft의 수많은 개발자들이 이미 검증해서 내놓은 하나의 작품과도 같은 것이었는데 우매했던 나는 그걸 전혀 이해하지 못했던 실수에서 비롯된 결과였다. 봉쌤은 내 사례를 들어 '이것도 하나 제대로 못하는데 무슨 게임 개발이야' 라며 안이함을 꼬집었다. 나는 분통한 한편, 결과가 처참한 것은 사실이기에 받아들일 수밖에 없었다.
그러나 봉쌤은 그치지 않고 목표한 1초에 아무도 도달하지 못했다며, 모두에게 구현한 방법을 물어보았다. 하나같이 array이거나 몇몇은 list인 상황에서 봉쌤은 그렇게 구현하니 100초도 못 뚫는 거라며 한심하다는 듯이 말하시곤 곧바로 tree를 제시하였다. 이 신기한 구조는 이제 구닥다리 같은 string compare는 사용할 수 없다며 어떻게 해야 이걸 이용할 수 있는지 칠판에 쓱쓱 써내려가셨는데, 그 설명을 듣고만 있어도 이전의 내 생각과 사상으로는 도저히 깨달을 수 없는 것이었다. 설명을 마치신 봉쌤은 이를 이용해서 다시 만들어보라고 추가 과제를 내주며 그 날의 수업을 마쳤다.
나는 기숙사의 점호 시간이 되기 전까지 내 구현들을 지우고 표준 함수로 대체한 수행 결과를 보았다. 이전과는 명백히 다르게 백초 단위의 결과를 얻긴 했으나 정말 봉쌤 말대로 100초보다 빨라지기론 한참이나 모자랐다. 그럼에도 불구하고 내가 모든 것을 직접 만들어낸 코드의 수행 시간보단 족히 반 이상은 빠르게 끝났다는 사실에 스스로가 굉장히 멍청하게 느껴진 것은 어쩔 수가 없었다. 물론 이 사건 이후로 표준을 신뢰하는 것은 당연한 것이었다.
대표로 한 번 털리고 나서 다신 털리지 않겠다는 마음으로 tree를 이용하는 과제에 임하긴 했지만, 이후에도 지속적으로 털리는 것을 피할 순 없었다. 여러 차례 겪으며 알게 된 것은 이것이 그냥 봉쌤의 방식이라는 것이다. 채찍을 때리며 뭔가를 스스로 해내게 만들다가 벽에 부딪혀서 못 올라갈 때에만 밀어올려놓는다. 그러면 답 하나는 반드시 얻게 되고, 노력하는 과정에서 본인이 만들어본 답들은 비록 틀렸을 지언정 스스로에게 남아있는 것이다. 물론 당시엔 남아있는 게 크게 보이진 않았지만 지금와서 돌아보니 그것들이 쌓여 내가 되었다.
그 방식이 모두에게 통용되는 좋은 길인진 모르겠지만, 나는 굉장히 좋았던 기억으로 남아있다. 그 이후로 지금까지도 그 때만큼 성장한다는 느낌을 주는 시간이 없기 때문이다. 지금은 그런 채찍과 당근을 내게 휘두를 사람이 없어도 내 스스로가 그것으로 성장할 수 있다는 것을 알고는 있으니 어떻게든 스스로에게 그렇게 해보려고 노력 중이다. 채찍을 잘 못 들어서 문제지만. 이후의 내가 어떻게 발전해갔을지는 아직 모르겠지만, 나중에 다시 또 회고할 때엔 스스로 박차를 가했길 바란다.
욕 필터링은 마지막에 기어이 1초 이내의 성능을 내는 것에 성공하며 마무리된다. 물론 수업 과제로는 시간상 tree로 성능을 줄이는 것 까지였지만, 필터링과 관련된 마지막 수업 시간에 hash를 이용해서 메모리를 극단적으로 사용하는 방법까지 논한 뒤 개별적으로 해보라는 식으로 남겨두게 되었다. 이 필터링을 또 모든 친구들이 다 해본 건 아니다보니 나는 다시 한 번 더 자신에 차버리게 되는 결과를 낳게 되었다.
그러고보니 당시 봉쌤은 심심해서 이걸 명절에 만들어보곤 괜찮다고 생각해서 수업 과제로 가져온 것이었다고 하셨는데, 이번 명절은 길기도 기니 나도 심심한 관계로 뭐라도 만들어봐야겠는데 뭘 만들어볼지가 고민이다. 또 이렇게 고민만 하다가 넘기는 건 아쉬우니까 조만간 뭐라도 하나 만들어서 글 하나 더 써야겠다.