배열과 List의 속도

오늘 제가 에스이랩의 한장강의라는 문서를 통해 List와 Hash에 대한 내용을 다루었습니다. 여기에서 짧게 이들 자료형에 대한 속도 문제를 언급했는데요, 내용이 이렇습니다.

  • … List는 그런 제약이 없습니다. 필요하면 끼워 넣고, 필요 없어지면 빼버리는 것이 자유로우며, 이런 상황에서 배열보다 속도도 빠릅니다(e-mail 본문 중).
  • 특히 배열은 수치 계산에서 (List와 비교할 수 없는) 가장 빠른 속도를 보장합니다. (문서 본문 중)
  • 일반적인 상황에서는 고급 데이터 타입인 List와 Hash가 조금 느린 것이 정상입니다. (문서 본문 중)

List()와 Hash()가 IDL 언어에는 새로 등장한 데이터 타입이어서, 위 내용이 혼란스러울 수도 있을 것 같습니다. 지금 보니 그럴 수도 있겠군요. 배열이 더 빠르다고 했다가, 리스트가 더 빠르다고 했다가… 이 부분에 대한 좀 더 자세한 설명을 드리고자 합니다. 제 개인적인 사견을 듬뿍 포함해서 말이죠.

배열이 가장 빠릅니다.

IDL은 배열 기반의 언어이고, 배열 처리를 IDL스럽게 하면 매우 빠른 데이터 처리를 할 수 있습니다. IDL 뿐 아니라 다른 주요 언어들도 배열이 가장 빠를 수밖에 없습니다. 배열은 크기가 일정한 데이터들이 규칙적으로 메모리에 자리잡고 있는 형태이기 때문입니다. 정확한 비유는 아니지만, 징검다리의 돌들이 일정 간격으로 놓여 있는 상황이라면 간격만 잘 맞추면 쉽게 뛰어갈 수 있을 것입니다. 그런데 이 간격이 불규칙적이라면 신경 많이 쓰이죠. 속도를 내서 뛰기가 어렵습니다. 배열과 List의 장단점이 여기에 물려 있습니다. 배열은 같은 타입의 데이터만 저장할 수 있습니다. 이것은 제약입니다. 그렇지만 그렇기 때문에 배열 기반의 연산도 가능하고(배열 안의 모든 요소에 대한 연산 처리도 통일되니까요), 데이터간의 검색도 규칙적인 간격으로 뛰어 다니면 됩니다. 이렇게 속도라는 매우 중요한 장점을 확보합니다.

리스트는 아무런 데이터 타입이라도 하나로 묶을 수 있습니다. 좀 더 진지하게 설명하자면, 리스트 내의 원소는 어딘가 숨어있고, 그 숨어있는 원소들의 위치 정보(포인터라고 이해해도 좋고, 오브젝트 레퍼런스라고 이해해도 문제 없습니다)만 목록으로 가지고 있는 것입니다. 자유로운 면은 있지만 컴퓨터 내부적으로 처리할 일이 많아지고, 속도도 떨어집니다. 현대적인 프로그래밍 언어들이 가는 길이 대부분 이렇습니다. 컴퓨터 내부적으로 처리할 일이 많아지는 거는 신경쓸 바가 아니고, 사람이 얼마나 더 편해지는가에 초점을 맞춥니다. 리스트는 느립니다만, 상황에 따라서 “배열로 처리하면 골치아플 일들”을 쉽게 처리하도록 도와줍니다. 동적 데이터형이기 때문에 원소를 추가로 넣기도 하고, 빼기도 합니다. 그래서 크기가 얼마가될지 모르거나 유동적인 데이터를 묶어 놓는데 유용합니다. 예를 들어, 포토샵에서 사진을 한장 열지, 20장 열어 놓을지 아무도 모르잖아요. 사용자 마음이지요. 이런 경우 열어놓은 사진 창들의 목록을 리스트로 관리하면 매우 깔끔해집니다.

어쨌든 IDL은 과학기술언어이고, 그러다보니 수치 계산이 1순위로 중요합니다. 그래서 대부분의 일은 배열로 해야 합니다. 예를 들어 보겠습니다.

10만개의 난수를 리스트 L에, 그리고 배열 A에 넣었습니다. 값은 다르겠지만 데이터의 본질은 실수 데이터 10만개입니다. 결론부터 말씀드려서 초를 치자면, 애초에, List는 이런 데이터를 처리하기 위해 만든 것이 아닙니다. 즉, 이런 극단적인 비교를 하게 되면 List는 억울하겠죠. 그렇지만 공부를 위해 List에게 억울한 일을 진행해 보겠습니다.

IDL을 처음 배울 때부터 강조되는 것이, “IDL은 배열 기반의 언어이며, 배열 데이터를 가능한 배열에 특화된 연산을 통해 처리해야지, 반복문을 쓰면 엄청 느려진다”는 것입니다. 사실이죠. A 배열의 값에 2를 곱해서 A2라는 배열에 저장하는 것을 (쉽게 쓰면, A2=A*2. 입니다) C 언어 처럼 반복문을 써서 처리하면, 다음과 같이 됩니다.

이를 배열기반 연산을 사용하면 다음과 같습니다.

18배 정도 빠른데요, 컴퓨터 성능이 좋아질 수록, 연산이 복잡해질 수록 이 차이는 더 커집니다.

일단 List는 이런 연산이 안됩니다. 그래서 배열보다 수치 계산에서 느릴 수밖에 없죠.

굳이 반복문을 써서 계산을 할 수는 있는데, 좋은 평가를 받기는 어렵습니다.

배열에 반복문 쓴 것 보다도 많이 느리죠. 앞에 설명한 징검다리 문제도 있고, 내부적으로 실수형 데이터로 변환했다가 다시 리스트의 일원으로 변환되어 들어가는 변환 시간도 있습니다. 하여간 간단한 일이 아니라면 이 방법은 좋지도 않고, List의 영역도 사실 아닙니다. 차라리 리스트를 배열로 변환해서 처리하고 그 결과를 다시 리스트로 만드는 것이(결과적으로는 위 예제와 같아지지만) 훨씬 빠를 정도입니다.

그럼 도대체 List는 왜 만든거야 라는지, 도대체 어떤 상황에서 List가 더 빠르다는 거야 라는 질문이 나올 수 있는 상황입니다. 말씀 드렸듯이 List는 유동적인 목록을 처리하기 위해 만들어졌고, 이런 일을 할 때는 배열보다 효율적입니다. 여기서 효율적이라는 의미는, 1) 사람이 코딩하기가 쉽고 간결해지는 것, 2) 처리 속도가 빠른 것, 두가지를 모두 의미합니다.

이에 대한 예제는 Dr. David Fanning께서 정리한 논의에 잘 나와 있습니다.

IDL의 List 가 유용한 것인가? 

이 논의의 발단은, “뭐여? 유동적인 상황에서도 리스트가 더 느리단 말야?” 하는 것으로 시작됩니다. 현재는 이 것이 반은 맞고 반은 틀립니다. 리스트가 담고 있는 데이터들이 단순 데이터 타입일 때는 리스트가 더 느립니다. 그렇지만, 리스트가 담고 있는 데이터 원소들이 복합형 데이터일 때(예를 들면 구조체), 리스트가 더 빠릅니다. 이에 대한 예제는 위 논의의 아래쪽에 Chris Torrence가 올린 내용에서 찾아볼 수 있습니다.

이 예제에서는 !map 이라는 제법 복잡한 구조체들을 원소로 하는 배열과 리스트를 만들어 원소 하나를 넣었다가 빼는 과정을 반복하며 시간을 재는데요, 리스트가 10배~100배 정도 빠르다는 결과를 보여줍니다. 원소 개수가 많으면 많을 수록 이런 차이가 커지는데, 사실 이것도 역시 List에 유리한 극단적인 테스트라고 생각합니다. 어쨌든 “복잡한 원소를 가지는 가변적인 목록에는 List가 더 효율적이다”라는 사실은 확인할 수 있습니다.  저는 이러한 시간 재기 보다는, List를 썼을 때 코드가 간결해 지고 이해하기 쉬워지는 부분에 훨씬 큰 무게를 두고 싶습니다. 유동적인 데이터 묶음에는 List가 더 좋아 보입니다.

그리고, List는 IDL에 도입된지 얼마 되지 않는 신형 데이터 타입입니다. 앞으로 속도나 사용편의성이 많이 발전될 여지가 있다고 생각합니다. 프로그래밍이 복잡해질 수록 List가 유용한 상황이 자주 등장할 거라고 저는 믿습니다. 물론 그래 봐야 수치 계산에서는 배열이 더 빠를 겁니다. 현재의 컴퓨터가 취하는 데이터 처리 방식으로는, 언어를 불문하고, 배열보다 빠른 데이터 구조는 등장할 수 없습니다. 만일 배열이 속도에서 진다… 그렇다면 배열은 더 이상 설 자리가 없어지는 거구요… 그럴 리는 없다고 봅니다만… 그럴 리는 없어요.

List냐 배열이냐, 누가 속도의 승자냐 하는 것은 상황에 따라 다릅니다.  IDL은 과학기술 언어로 수치 연산 속도가 매우 중요합니다. 그 때는 배열을 쓰세요. 1초도 안되는 시간(속도)의 손해가 있더라도, List를 사용해서 프로그래밍하는 사람이 편해질 수 있다면, 그 때는 List를 쓰세요. 이제, IDL은 이제 이 두가지를 모두 사용할 수 있는 환경을 제공하고 있습니다.