명령형 프로그래밍 (Imperative Programming)
명령형 프로그래밍은 프로그래밍 구문을 사용하여 프로그램의 상태를 변경하고, 입력 변수를 명확히 지정한 뒤 프로그램의 논리에 따라 단계별로 연산을 수행하는 방식입니다.
- 직관적인 이해: 파이썬과 같은 언어에서 명령형 방식으로 코드를 작성하면 대부분의 로직이 매우 직관적이고 읽기 쉽습니다.
- 용이한 디버깅: 단계별 추적(Step-by-step debugging)이 간편하여 모든 중간 변수의 값을 쉽게 확인하고 분석할 수 있으며, 표준 디버깅 도구를 완벽하게 활용할 수 있습니다.
하지만 명령형 프로그래밍은 개발 편의성이 높은 반면, 실행 속도가 상대적으로 느릴 수 있습니다. 함수 호출이 반복되고 변수 값이 메모리에 장기간 유지되는 등의 문제로 인해 리소스 소모가 커질 수 있습니다.
def compute_product(x, y):
return x * y
def complex_calculation(a, b, c, d):
part1 = compute_product(a, b)
part2 = compute_product(c, d)
final_result = part1 + part2
return final_result
print(complex_calculation(2, 3, 4, 5))
실행 결과: 26
심볼릭 프로그래밍 (Symbolic Programming)
심볼릭 프로그래밍은 전체 계산 흐름이 완전히 정의된 이후에 비로소 실행되는 방식입니다.
- 높은 효율성: 컴파일 단계에서 시스템이 전체 코드를 파악할 수 있으므로 다양한 최적화를 적용하기 용이합니다.
- 우수한 이식성: 프로그램을 파이썬 환경에 종속되지 않는 독립적인 형식으로 변환할 수 있어, 파이썬 인터프리터의 성능 병목 현상을 피하고 다른 환경에서도 실행할 수 있습니다.
일반적으로 심볼릭 프로그래밍은 다음 세 가지 단계를 거칩니다:
- 계산 흐름(그래프)을 정의합니다.
- 정의된 계산 흐름을 실행 가능한 프로그램으로 컴파일합니다.
- 실제 입력 데이터를 제공하고 컴파일된 프로그램을 호출하여 실행합니다.
컴파일 시점에 시스템이 프로그램의 전체 구조를 미리 알 수 있기 때문에, 불필요한 함수 호출을 줄이고 메모리 사용을 최적화할 수 있는 여지가 큽니다. 초기 딥러닝 프레임워크인 TensorFlow(1.x)와 Theano가 이 방식을 채택했습니다.
def get_product_code():
"""곱셈 연산의 계산 흐름을 문자열로 반환"""
return '''
def multiply(x, y):
return x * y
'''
def get_complex_code():
"""복합 연산의 계산 흐름을 문자열로 반환"""
return '''
def calculate(a, b, c, d):
part1 = multiply(a, b)
part2 = multiply(c, d)
return part1 + part2
'''
def get_execution_code():
"""전체 실행 스크립트를 문자열로 조합"""
return get_product_code() + get_complex_code() + '''
print(calculate(2, 3, 4, 5))
'''
script = get_execution_code()
compiled_script = compile(script, '<string>', 'exec')
exec(compiled_script)
컴퓨팅 그래프와 심볼릭 그래프
심볼릭 프로그래밍은 계산 과정을 추상화하여 '컴퓨팅 그래프(또는 심볼릭 그래프)'로 표현합니다.
- 계산 과정의 명확한 표현: 모든 입력 노드, 연산 노드, 출력 노드를 심볼(기호)로 처리하여 데이터의 흐름을 명확하게 묘사합니다.
- 데이터 흐름 추적: 입력 노드에서 출력 노드까지의 연결 폐포(Transitive closure)를 구축하고, 이를 따라 수치 계산과 데이터 이동을 수행합니다.
- 그래프 최적화: 데이터 흐름 방식으로 연산을 수행하여 메모리 사용을 줄이고 계산 속도를 높입니다. 다만, 중간 상태를 확인하기 어려워 디버깅에는 불리하며 일반적인 범용 프로그래밍 언어보다는 특정 도메인(딥러닝 등)에 주로 사용됩니다.
대부분의 심볼릭 프로그램은 명시적 또는 암시적으로 컴파일 단계를 포함하여, 계산 그래프를 호출 가능한 함수로 변환합니다. 즉, 연산 그래프를 정의하는 단계와 실제 컴파일 및 실행 단계가 명확하게 분리되어 있습니다.
하이브리드 프로그래밍 (Hybrid Programming)
요약하자면, 명령형 프로그래밍은 이해와 디버깅이 쉽지만 최적화가 제한적이며, 심볼릭 프로그래밍은 최적화와 성능 향상에는 유리하지만 코드의 직관성이 떨어지고 디버깅이 어렵습니다.
그렇다면 명령형 프로그래밍의 개발 편의성과 심볼릭 프로그래밍의 실행 성능을 동시에 얻을 수 있을까요? 이를 위해 하이브리드 접근법이 도입되었습니다. 개발자는 초기 개발과 디버깅 단계에서는 순수 명령형 프로그래밍을 사용하고, 프로덕션 환경에서의 고성능 연산과 배포가 필요할 때는 코드를 심볼릭 형태로 변환하여 실행합니다. MXNet의 Gluon 인터페이스와 같은 현대 딥러닝 프레임워크들이 이러한 하이브리드 프로그래밍 모델을 지원하여 두 방식의 장점을 모두 활용할 수 있도록 합니다.