본문 바로가기
C++

[CppCon] C++에서 데이터를 Handling하기 위한 다양한 구조들

by 매운돌 2023. 4. 20.

최근에 CppCon에서 데이터 핸들링에 관하여 재밌는 영상을 보게되어 소개하려고 합니다.
5분이 약간 넘는 아주 간단한 동영상이니 직접 시청해 보시는 것을 추천합니다.

Lightning Talk: Cute Approach for Polymorphism in C++ - Liad Aben Sour Asayag - CppCon 2022 - YouTube

 

데이터를 핸들링하는 아래와 같은 상황을 생각해 볼 수 있습니다. 다양한 형태의 데이터(Element)들이 들어오고, 그 데이터의 형태에 따라서 작업을 하기위해 다양한 Handler가 존재할 수 있습니다. 그리고 어떠한 Handler를 사용해야 하는지는 runtime에 결정되게 됩니다.


그러면 우리가 가장 먼저 생각해볼 수 있는 구조는 이렇습니다.

Handler에 대한 인터페이스가 존재하고, 이를 상속하는 각각의 Handler들이 존재하는 형태입니다.

이 경우 main에서 아래와 같이 사용될 수 있습니다. choose_handler라는 함수를 통해 다형성으로 Handler를 리턴 받아 데이터를 처리하게 됩니다. 그리고 이럴 경우 main에서는 오직 Base 인터페이스(HandlerBase)만 알고 있으면 되기 때문에 encapsulation측면에서 이점이 있고, 인터페이스가 변경되지 않는 이상 main에서 코드변화가 없기 때문에 컴파일 속도가 비교적 빠르다고 말할 수 있습니다.

하지만, elements의 수가 많아지게 되면 가상 함수 호출이 많아지게 되면서 아무래도 직접 호출이 아닌 포인터를 통해 한단계 거쳐서 호출되기 때문에 높은 성능을 요구하는 프로그램에서는 사용하기 어렵다고 이야기 하고 있습니다.


하지만 댓글에서는 위에 언급한 가상 함수 호출에 따른 performance저하가 일어나지 않는다고 말하는 사람도 있었습니다.

왜냐하면 loop내에서 반복적으로 호출되는 가상함수의 경우에 virtual pointer와 virtual table이 캐쉬되어 이러한 성능저하가 일어나지 않는다고 이야기하고 있습니다.
관련 내용은 아래의 영상에서 확인할 수 있습니다.
REUPLOAD: The Hidden Performance Price of C++ Virtual Functions - Ivica Bogosavljevic - CppCon 2022 - YouTube


그러면 이번에는 이전에 문제가 되었던 가상 함수를 제거해봅시다.

이전과 다르게 하나의 인터페이스로 다양한 Handler들을 다룰 수 없다보니, std::variant로 Handler를 리턴하고 std::visit을 사용하여 함수에 접근하였습니다.

가상 함수를 사용하지 않았으니 이전에 발생했던 performance 이슈는 더 이상 없습니다. 하지만 여전히 문제가 존재합니다. main에서 모든 Handler 종류에 대해서 알아야 하고, handler가 추가 되거나 삭제될 때 마다 main에서 변경이 필요하게 됩니다. 그리고 이는 컴파일 속도를 느려지게 합니다.


이전에 인터페이스를 사용하지 않고, 다양한 Handler들을 다루려다 보니 문제가 굉장히 많았습니다. 따라서 이번에는 인터페이스를 그대로 사용합니다. 그리고 main의 for문에서 가상 함수를 계속 호출하던게 성능 저하에 원인이였기 때문에, for문을 각각의 인터페이스를 상속받은 Handler들의 handle함수 안으로 옮겨 봅시다.

for문이 각각의 Handler안으로 옮겨졌기 때문에, main에서는 이제 handle함수 단 한 번만 호출하게 됩니다. 그리고 다형성을 활용하였기 때문에 main안에서 다양한 Handler의 종류를 알 필요가 없어졌습니다. 이제까지 언급되었던 단점들은 모두 해결할 수 있었습니다. 그러나 각각의 Handler의 handle함수 안에 boilerplate가 존재하기 때문에 코드가 보기 좋지는 못합니다.


boilerplate를 없애기 위해서 이전에 반복되던 for문은 abstract layer의 형태의 Handler에 handle함수에 한번만 구현합니다. 그리고 이를 처리하는 Handler는 template으로 받아서 생성하게 됩니다.

 

main코드와 이점들은 이전과 동일합니다. 하지만 이전과 달리 boilerplate가 없기 때문에 Handler를 추가하기 수월해 지고, 코드가 훨씬 깔끔해졌습니다.