알고리즘 분류는 컴퓨터 과학에서 중요한 문제로, 코드 조각에 사용된 프로그래밍 기법과 특정 알고리즘을 식별하는 것을 목표로 합니다. 이 작업은 코드의 의미론적 이해를 필요로하며 이를 통해 코드 취약성 검출이나 자동 코드 생성 도우미 설계 등 다양한 응용 분야에서 통찰력을 제공할 수 있습니다. 본 논문에서는 정적 코드 분석이 아닌 동적 분석을 중점적으로 다룹니다. 알고리즘이 어떻게 작동하는지 실제 실행 부분을 분석하여 리소스 사용량(예: CPU 시간, 메모리)이 입력 크기에 따라 어떻게 변화하는지를 조사합니다.
💡 논문 해설
1. **동적 코드 임베딩의 활용**:
- **메타포**: 동적 코드 임베딩은 자동차의 연비를 측정하는 것과 비슷하다. 자동차가 어떤 속도로 달리는지, 얼마나 효율적으로 연료를 소모하는지를 파악하면 차량의 성능을 알 수 있다.
알고리즘 분류기 모델:
메타포: 의사가 환자의 증상을 바탕으로 질병을 진단하듯이, 알고리즘 분류기는 코드 실행 결과를 통해 어떤 알고리즘이 사용되었는지를 판단한다.
컴퓨팅 아키텍처의 영향력:
메타포: 컴퓨터가 레스토랑과 같다면, 각 레스토랑마다 다르게 준비되는 음식처럼 코드 임베딩도 실행 환경에 따라 달라진다.
📄 논문 발췌 (ArXiv Source)
# 소개
알고리즘 분류는 컴퓨터 과학에서 중요한 문제로, 코드 조각에 사용된 프로그래밍 기법과 특정 알고리즘을 식별하는 것을 목표로 합니다. 이 작업은 코드의 의미론적 이해를 필요로하며 이를 통해 코드 취약성 검출이나 자동 코드 생성 도우미 설계 등 다양한 응용 분야에서 통찰력을 제공할 수 있습니다.
대부분 최근 연구 커뮤니티는 정적 코드 분석에 집중하고 있지만, 우리는 동적 분석[^1]에 초점을 맞춥니다. 알고리즘이 어떻게 작동하는지 실제 실행 부분을 분석하여 리소스 사용량(예: CPU 시간, 메모리)이 입력 크기에 따라 어떻게 변화하는지를 조사합니다. 우리가 제안하는 가설은 이러한 지표와 고급 아키텍처 측면(예: 브랜치 및 캐시 미스)을 함께 사용하여 서로 다른 알고리즘 접근 방식을 구분할 수 있다는 것입니다.
본 연구에서는 C++로 작성된 경쟁 프로그래밍 문제 해결 방법의 분류를 위해 우리의 접근법이 어떻게 적용되는지 조사했습니다. 의사결정 나무 모델, 랜덤 포레스트 분류기 및 XGBoost를 사용하여 동적 코드 임베딩으로부터 알고리즘 라벨을 도출한 결과가 유망하다는 것을 보고합니다.
이 논문은 다음과 같이 구성되어 있습니다. 제2장에서는 기존 문헌의 관련 접근법에 대해 보고하고, 제3장에서는 동적 코드 임베딩 생성 방법을 설명합니다. 제4장에서는 시스템 아키텍처의 일반적인 개요를 제공하며, 제5장에서는 새로운 종합 데이터셋 생성을 위한 실험적 세부 사항을 제공합니다. 다음으로, 제6장에서는 여러 머신러닝 솔루션을 사용하여 달성한 결과를 보고하고, 마지막으로 제7장에서는 우리의 해결책의 적용 가능성에 대한 최종 관점을 결론 내립니다.
관련 연구
머신러닝 모델을 데이터 분석에 적용하기 전에 중요한 단계는 의미 있는 입력 표현을 선택하는 것입니다. 본 장에서는 소프트웨어 프로그램 임베딩 생성을 위한 몇 가지 대표적인 접근법을 탐색합니다.
첫째, 연구자들은 자연어 처리 분야에서 차용한 기술들을 조사했습니다. 이는 개별 단어의 분포적 특성(예: Word2Vec)에 의존합니다. 이러한 접근법은 서로 다른 어휘 특징들의 통계적 공연출 패턴을 포착하려고 합니다. 이러한 특징들은 소스 코드의 텍스트 표현에서 또는 다양한 컴파일 단계와 관련된 표현(LLVM-IR, Java 바이트코드, 어셈블리 등)으로부터 다른 세그먼테이션 전략을 사용하여 도출될 수 있습니다.
또 다른 옵션은 그래프 표현(예: AST 또는 CFG)의 코드를 인코딩하기 위해 신경 아키텍처를 학습하는 것입니다. 이 접근법의 장점은 서로 멀리 떨어진 프로그램 요소들 간의 의미론적 관계를 명시적으로 모델링할 수 있다는 점입니다.
동적 분석을 통한 프로그램 실행 동작에 대한 다른 관점을 얻을 수 있습니다. 하나의 접근법은 실행 추적이며, 이는 코드가 실행되는 순간마다 변수 상태를 포착합니다. 다른 방향으로는 운영 체제와의 상호 작용을 수집하고 분석하는 것입니다. 예를 들어 파일 생성, 열기 또는 수정과 관련된 시스템 호출 등입니다. 유의할 점은 이러한 접근법이 운영 체제와 최소한의 상호 작용만 하는 프로그램들을 구분하기에 적합하지 않을 수 있다는 것입니다.
정적 코드 표현은 직접적으로 소스 코드에 액세스합니다. 한편, 위에서 논의된 동적 코드 표현은 대상 프로그램의 의미론에 대한 특정 지식을 활용하며, 프로그램 실행 횟수와 다양성에 크게 의존합니다. 반면 본 논문에서 제시한 솔루션은 프로그램 구조에 대해 사전 가정이 필요하지 않습니다. 따라서 수집된 실행 동작 통계만을 기반으로 합니다.
알고리즘을 임베딩으로 변환하기
제시된 r-Complexity는 유사한 프로그램에 대한 복잡도 피드백을 더 잘 제공하는 수정된 복잡도 모델입니다. 함수 $`f:\mathbb{N}\longrightarrow\mathbb{R}`$가 알고리즘 계산 복잡도를 설명한다고 가정합니다. 모든 복잡도 계산 집합은 $`\mathcal{F}= \lbrace f:\mathbb{N}\longrightarrow\mathbb{R} \rbrace`$로 정의됩니다. 또한 임의의 복잡도 함수 $`g\in \mathcal{F}`$를 고려합니다.
Big r-Theta 클래스는 $`g(n)`$과 유사한 크기를 가진 수학적 함수들의 집합으로, 점근적 행동을 연구할 때 정의됩니다. 이 그룹에 대한 집합 기반 설명은 다음과 같습니다:
Big r-Theta 표기법은 동적 코드 임베딩 생성에도 유용합니다. 이러한 임베딩의 아이디어는 간단하다: 분석된 알고리즘(보통 Bachmann–Landau 복잡도가 알려지지 않은 경우)에 대한 다양한 지표들에 대해 r-Theta 클래스를 자동으로 추정하려고 시도합니다.
일반화된 해결책은 $`f(n)`$을 다음과 같이 표현하는 회귀 모델을 통해 r-Complexity 클래스의 좋은 자동 추정치를 제공합니다:
본 연구에서는 다음과 같은 Big r-Theta 함수의 단순화된 버전을 적합시키려고 합니다:
MATH
\begin{equation*}
\left\{\begin{array}{@{}l@{}}
r \cdot log_{2}^{p}log_{2}(n) + X\\
r \cdot log_{2}^{p}(n) + X \\
r \cdot p^{n} + X, p < 1 \\
r \cdot n^{p} + X \\
r \cdot p^{n} + X, p > 1 \\
r \cdot \Gamma(n) + X
\end{array}\right.\,.
\end{equation*}
클릭하여 더 보기
회귀 함수의 검색 공간은 자동 계산을 위해haustively 검색하기에 불가능합니다. 이 문제를 극복하기 위해, 우리의 접근법은 연속 공간 검색을 피하고 일반적으로 알고리즘에 관련된 몇 가지 중요한 구성 요소만 샘플링하여 비슷한 결과를 얻는 것입니다. 이를 통해 검색 공간을 이산화할 수 있으며 다음 함수들을 검색합니다:
MATH
\begin{equation*}
\left\{\begin{array}{@{}l@{}}
r \cdot log_{2}^{p}log_{2}(n) + X, p \in \{0,1,2,3\}\\
r \cdot log_{2}^{p}(n) + X, p \in \{0,1,2,...,10\} \\
r \cdot p^{n} + X, p < 1, p \in \{0.1,0.2,...,0.9\} \\
r \cdot n^{p} + X, p \in \{1, 1.3, 1.5, 1.7, 2, 2.5, 2.7, 3, 3.5, 4, 4.5, 5, 5.5, 6, 7, 8, 9, 10\} \\
r \cdot p^{n} + X, p > 1, p \in \{1.5, 2, 2.5, 3, 3.5, 4, 5\} \\
r \cdot \Gamma(n) + X
\end{array}\right.\,.
\end{equation*}
클릭하여 더 보기
각 Big r-Theta에 대한 적합화는 다음과 같은 사물의 결과를 갖습니다:
(FEATURE_TYPE, FEATURE_CONFIG, INTERCEPT, R-VAL),
여기서:
FEATURE_TYPE은 다음 값을 가지며[^2]:
LOGLOG_POLYNOMIAL,
LOG_POLYNOMIAL,
FRACTIONAL_POWER,
POLYNOMIAL,
POWER,
FACTORIAL,
FEATURE_CONFIG는 다음과 같이 정의됩니다[^3]: $`n \in \mathbb{R}_{+}`$ 값이 일반적인 Big r-Theta가 위의 FEATURE_TYPE을 준수하도록 합니다.
INTERCEPT은 입력 크기가 0일 때 복잡도 함수의 예상값입니다.
R-VAL은 $`r`$ 값으로, 정의된 $`g`$ 복잡도 클래스가 실제 알고리즘의 복잡도 함수인 $`f \in \Theta_{r}(g(n))`$을 포함합니다.
이 모델은 모든 임의 지표에 대해 일반적이지만, 이를 사용하려면 투명한 메트릭으로 모델을 인스턴스화해야 합니다. 알고리즘을 분석할 때 이 알고리즘이 동반하는 r-Complexity를 하나의 코드 임베딩으로 묶습니다. 원래 Linux perf 프로파일러를 사용하여 다음과 같은 지표들을 얻고 이를 코드 임베딩 계산에 사용했습니다: 하드웨어 이벤트인 branch-misses (브랜치 미스 예측 횟수), branches (실행된 브랜치 명령 수), cycles (실행된 사이클 수), instructions (실행된 명령 수), stalled-cycles-frontend (“사용되지 않은” CPU 사이클 수, 프론트엔드[^4]가 백엔드에 마이크로 오퍼레이션을 제공하지 못한 경우)와 소프트웨어 이벤트인 context-switches (스케줄링 시간에 프로세스/쓰레드 상태를 저장하는 절차 수), CPU-migrations (프로세스/쓰레드가 다른 CPU에서 스케줄된 횟수), page-faults (가상 메모리가 물리적 메모리에 매핑되지 않은 이벤트 수), task-clock (실행된 작업에 대한 클록 카운트를 저장).
임베딩을 계산할 때 분석되는 알고리즘의 구조에 대한 지식은 필요하지 않습니다. 그러나 결과 임베딩은 생성한 아키텍처에 의존합니다. 일반적으로 메트릭은 아키텍처마다 다릅니다. 알고리즘의 일반 복잡도 클래스는 변하지 않기 때문에, FEATURE_TYPE과 FEATURE_CONFIG 매개변수는 여러 컴퓨팅 아키텍처에서 동일하게 유지될 가능성이 높습니다. 본 논문에 있는 샘플 임베딩은 3세대 Intel Core 프로세서(3.10 GHz), i5-3210M (2.5 GHz, 3MB L3 캐시, 2 코어)에서 실행되었습니다. 다른 복잡도를 가진 알고리즘의 경우 이러한 매개변수는 변할 수 있습니다.
시스템 아키텍처
데이터 수집 시스템 개요: 바이너리에서 메트릭 세트를 생성하는 능력.임베딩 시스템 개요: 원시 메트릭을 코드 임베딩으로 변환하고 다른 시스템 구성 요소와 상호 작용하는 방법.
제안된 시스템은 데이터셋에 포함된 문제의 해결 방안을 분석하기 위한 복잡한 시스템입니다. 이는 데이터 수집 서브시스템 (그림 1)과 임베딩 서브시스템 (그림 2)으로 나눌 수 있습니다. 파이프라인을 실행하는 주요 단계는 그림 1의 라벨로 설명됩니다. 컨트롤 플레인은 1에서 데이터 파이프라인을 초기화하고 필요한 인수를 제공합니다. 다음 단계 2에서는 TheCrawlCodeforces(해결책 데이터셋)에서 모든 문제에 대한 일치하는 해결 방법을 가져옵니다. 이는 소스 코드를 컴파일하고 결과를 데이터베이스에 다시 저장하는 것으로 나타납니다 (3). 그다음, 4에서는 컴파일된 실행 파일 형태로 데이터가 발견 서비스에 전달됩니다. 다음으로, TheInputsCodeforces[^5] (입력 데이터셋)에서 합성 입력을 가져옵니다(5). 이 작업이 완료되면, 파이프라인은 6에서 스케줄 상태로 들어가며 특정 실행자에서의 실행 준비를 합니다. 우리의 연구에서는 하드웨어 차이에 따른 데이터 변동을 피하기 위해 단일 실행자를 사용했지만, 이는 확장성에 심각한 영향을 미칠 수 있습니다. 작업이 스케줄링되고 완료되면 결과 프로파일링 데이터가 TheOutputsCodeforces(프로파일링 데이터셋)에 저장됩니다(7). 파이프라인의 마지막 단계에서는 8에서 수집된 메트릭들이 집계되어 파이프라인이 성공적으로 마무리됩니다.
원시 프로파일링 데이터가 생성되면, 임베딩 시스템(그림 2)은 복잡도 매핑을 적용할 수 있도록 이러한 메트릭들을 집계합니다. 이 임베딩 시스템은 데이터 수집 시스템이 제공하는 데이터를 바탕으로 코드 임베딩을 구축합니다. 이를 통해 다양한 입력 차원에 대해 r-Complexity와 관련된 동적 코드 임베딩을 대규모로 계산할 수 있습니다.
결과 임베딩은 프로파일링된 아키텍처에 어느 정도 의존하므로, 우리는 데이터셋의 모든 해결책을 동일한 아키텍처에서 평가했습니다. 그러나 이는 우리의 솔루션이 특정 아키텍처에 종속되는 것은 아니며 다른 아키텍처에서도 파이프라인을 실행하여 메트릭을 재처리할 수 있다는 점입니다. 또한 두 아키텍처가 동일한 가족에서 와 유사한 성능을 가지거나 분석된 메트릭에 대한 성능 차이를 추정하는 매핑 함수가 있는 경우, 새로운 아키텍처에서 임베딩을 계산할 때 이 단계를 수행하지 않아도 비슷한 결과를 얻을 수 있습니다.
데이터셋
r-Complexity 코드 임베딩을 사용하여 주어진 알고리즘에 대한 코드 임베딩 생성 과정의 깊이 있는 시각화.
본 연구에서는 Codeforces 기술 과제를 위한 오픈 소스 구현물을 자동으로 탐색하는 시스템을 구축했습니다. AlgoLabel 데이터셋을 사용하여 문제와 알고리즘 라벨 간의 매핑을 얻습니다.
동적 측정을 통해 동적 코드 임베딩을 생성하려면 각각의 문제에 대한 합성 테스트 입력이 필요합니다. 이러한 입력의 생성은 수작업으로 이루어지며, 일반적으로 적절한 차원과 입력 공간을 선택하는 정도의 추론이 필요합니다. 이를 통해 복잡도를 계산할 때 샘플들이 관련성이 있을 수 있습니다. 예를 들어, 입력 $`x`$에 대한 처리 사이클 수가 함수 $`c`$로 설명되는 문제를 고려해봅시다:
이 문제에서는 어떤 입력에 대한 알고리즘 샘플링은 r-Complexity 계산에 관련되지만 $`x=10000`$와 $`x=20000`$의 분석 결과는 아웃라이어입니다. 실제로 이러한 아웃라이어가 존재하며, 보통 복잡도를 계산하는 데 중요한 입력 공간과 차원을 나타냅니다.