'Python'에 해당되는 글 2건

  1. 2011/11/08 Erlang으로 팩토리얼 계산해보기 (1)
  2. 2010/04/05 Python에서 삼항 연산자 사용하기

최근 정말 우연히 Programming Erlang(조 암스트롱, 인사이트)이라는 책을 접했는데, 함수형 언어이고 분산 처리에 적합하게 설계되며 실제로 많이 쓰이고 있다는(!) 것에 감명을 받아 열심히 공부하고 있다. 아직 다 읽지는 못했지만 공부 한 내용을 블로그에 정리해 두려고 한다. (아직 공부한 지 일주일도 되지 않아 코드가 세련되지 못할 수도 있으니 코멘트 부탁드립니다.)

얼랭을 처음 보았을 때의 느낌은 함수형 언어라는 면에서는 LISP와 굉장히 닮았고(실제로 LISP의 많은 철학과 용어를 차용한 것 같다) 문법은 Scala와 많이 유사하다는 것이었다. 일반적으로 C나 Java와 같은 언어로 프로그래밍을 시작한 사람들에게 있어 가장 혼란스러운 부분은

  • 변수가 없다.
  • 반복문이 없다.
일 것이다. 저장해야 할 자료는 모두 함수의 매개변수에 담아 놓고, 반복문을 작성하는 대신 함수를 재귀호출해야 한다는 함수형 언어의 특징이 낯설 수도 있었을텐데, 다행히 나는 Scheme에 조금은 익숙한 상태라 큰 반감 없이 받아들일 수 있었다.

그럼 본론으로 넘어가서 Erlang으로 Factorial을 계산하는 코드를 작성해 보고 실행 시간을 비교해 보고자 한다. 우선, 가장 쉽게 생각할 수 있는 코드는 다음과 같을 것이다. 좋은 코드이지만, Erlang의 장점인 병렬 처리를 충분히 활용하지 못한 코드인 것 같다. 이번에는 병렬 처리를 위해 다음과 같은 코드를 생각할 수 있다. 이 코드는 크게 네 부분으로 구성된다. 우선 fact/1은 작은 문제의 답을 모으는 collector를 띄운 후 문제를 작은 문제로 분할한다. 이 때 작은 문제의 크기는 T의 값에 의해 결정된다. 우선은 T=10000이라는 상수로 두었다. split/4는 문제를 작은 문제들로 분할하는 함수이다. 큰 문제를 T 이하의 크기의 작은 문제 하나와 나머지 크기의 문제로 나눈 후 각각을 푸는 방식이다. 일종의 Divide & Conquer라고 생각할 수도 있겠다. fact/2, fact/3은 먼저 살펴 본 코드와 크게 다르지 않고, 다만 값을 반환하는 것이 아니라 C로 보낸다는 것이 다르다. 마지막으로 collector/3은 작은 문제의 개수를 기억하고 있어서 그 개수 만큼의 답을 얻으면 얻은 답의 곱을 fact/1로 보낸다.

가령 50000!을 계산하고자 한다면, 이 코드는 10000! * (10001 * ... * 20000) * ... * (40001 * ... * 50000)을 계산할 것이다. 싱글 코어라면 오히려 문제를 분할하고 합치는 데에 overhead가 발생하여 성능 저하가 일어나겠지만, 멀티 코어 시스템에서는 작은 문제를 각 코어에서 처리할 수 있으니 더 빠른 성능을 보여 줄 것이라 기대할 수 있다.

실제로, 성능 분석을 해 보면 50000!을 계산하는 데에 처음 제시한 코드는 3095ms, 병렬 처리를 사용한 두 번째 코드는 1059ms가 소요되었다.(i3 머신에서 statistics/1 사용하여 5회 테스트 한 평균) 대략 3배 정도 빠르다는 것을 알 수 있다!

심심하여 큰 수를 자유롭게 계산할 수 있는 또 하나의 언어인 Python으로도 팩토리얼을 구하는 함수를 작성한 후 분석을 해 보았다. 같은 조건에서 5회 테스트 평균을 구해 보았더니 5999ms가 나왔다. 이것은 병렬 처리를 사용하지 않은 Erlang 버전의 2배, 병렬 처리를 사용한 Erlang 버전의 6배 수준이다. 아, Erlang 좋구나.




2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry

C에서 코드 길이를 줄이고 깔끔하게 만들기 위해 많이 사용하는 것이 바로 삼항 연산자(ternary operator)이다. ?:으로 구성되는 삼항 연산자의 사용법은 다음과 같다.


result = Condition ? resultWhenTrue : resultWhenFalse;

위 코드는 Condition이 true일 때 result에 resultWhenTrue가 대입되고, Condition이 False일 때 result에 resultWhenFalse가 대입된다. if문을 쓰면 오히려 소스가 보기 싫어지고 이해하기 어렵게 될 때 복잡한 연산 사이에 잘 사용하면 참 간편한 녀석이다.


그러나 Python을 배우면서 삼항 연산자는 공부한 기억이 없다. 코딩을 하면서 색상값을 증가시켜야 하는 부분이 있었다. 색상값이기에 최댓값을 255로 제한해야 했는데, C 같았으면 삼항 연산자를 이용해서 할 터였다. newColor=(oldColor*factor)>255?255:oldColor*factor와 같이 말이다. 그러나 Python에서는 if문을 쓰자니 지저분해지고, 분명히 다른 방법이 있을 것이라는 생각이 들어 찾아보았더니 여러 가지 방법이 존재했다.


  1. 2.5부터 지원되는 공식 삼항 연산자

    다행스럽게도, Python 2.5부터 기본적으로 공식 삼항 연산자가 지원된다고 한다. C와 비교하면, ?와 : 대신 if, else를 쓰고, 순서가 조금 바뀌었다.,


    result = resultWhenTrue if Condition else resultWhenFalse

    읽었을 때 자연스럽게 의미가 있는 문장으로 읽히는 것은 좋지만 resultWhenTrue와 resultWhenFalse 사이에 Condition이 들어 있어 가독성 면에서는 조금 부정적인 면이 있지 않나 생각한다.

  2. 튜플을 이용한 방법:

    Python에서 True는 1이고, False는 0이다. 따라서 조건을 시퀀스의 인덱스로 사용할 수 있다.


    result = ( resultWhenFalse, resultWhenTrue )[ Condition ]

    형태는 간결하지만 처음 보는 사람의 경우 그 의미를 이해하기까지 시간이 걸린다. 또한 resultWhenFalse와 resultWhenTrue를 미리 계산해 놓기 때문에 미리 계산하면 안되는 경우(조건에서 인덱스의 범위를 검사하여 범위 안에서만 원소에 접근해야 하는 경우 등)에는 부적절하다.

  3. 논리 연산자를 이용한 방법:

    x and y는 x를 먼저 실행한 후 결과가 False이면 x를 리턴하고, True이면 y를 계산한 후 이를 리턴한다.
    x or y는 x를 먼저 실행한 후 결과가 True이면 x를 리턴하고, False이면 y를 계산한 후 이를 리턴한다.
    자세한 설명은 Documentation 에 잘 나와 있다. 이와 같은 특성을 이용하면 논리 연산자로도 삼항 연산자를 구현할 수 있다.


    result = Condition and resultWhenTrue or resultWhenFalse

    만약 Condition이 True라면 and 연산자는 resultWhenTrue를 리턴할 것이다. resultWhenTrue가 True라면 or 연산자는 resultWhenTrue를 리턴하므로 결과적으로 전체 문장은 resultWhenTrue를 리턴한다. 만약 Condition이 False라면 and 연산자는 Condition, 즉 False를 리턴할 것이다. or 연산자는 앞에 있는 문장이 False이므로 뒤에 있는 resultWhenFalse를 리턴하므로, 전체 문장은 resultWhenFalse를 리턴한다.
    이 방법의 큰 문제는 resultWhenTrue가 항상 True일 때에만 사용할 수 있다는 것이다. 만약 resultWhenTrue가 False라면 Condition이 True일 때 or 연산자는 resultWhenTrue 대신 resultWhenFalse를 리턴할 것이다. 이를 해결하기 위해 약간의 수정을 한다.


    result = (Condition and [resultWhenTrue] or [resultWhenFalse])[0]

    resultWhenTrue를 담고 있는 리스트는 항상 True이므로(원소 개수가 1개 이상이면 True) 둥근 괄호는 resultWhenTrue와 resultWhenFalse 중 하나를 원소로 가지는 리스트를 리턴할 것이고, 이의 첫 번째 원소를 취하면 우리가 원하는 값을 얻을 수 있다.
    논리 연산자의 특성상 이 방법은 조건이 거짓일 때에는 아예 resultWhenFalse를 계산하지 않는다.


2.5버전부터는 새로운 연산자가 생겨 굳이 편법을 사용하지 않아도 되게 되었지만 편법을 쓴 사람들이 쓴 다른 코드를 읽을 때에는 조금 도움이 되지 않을까.




2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry