Go에서 쓰레드간 데이터 공유시, Channel과 Mutex의 속도 비교

Don’t communicate by sharing memory; share memory by communicating.

라길래, channel과 mutex의 성능을 알아봤다.

테스트 머신은 Xeon CPU가 두 개 달렸고, 12G의 램을 가지고 있다. 각 CPU당 6개의 코어가 있으므로, 하이퍼쓰레딩을 이용하여 최대 24개의 코어를 사용할 수 있다.

테스트는 channel을 사용하는 경우와 mutex를 사용하는 경우로 나눴으며, 10번의 결과를 평균내어 최종 결론을 만든다.

아래의 그래프가 그 결과로, 사용하는 코어의 개수와 관계없이 channel을 사용하는 것이 mutex를 사용하는 것보다 느리다는 것을 알려준다.

추가로 테스트 한 바, channel의 크기를 변경하는 것은 결과에 별다른 영향을 미치지 못한다.

golang_comapre_message_mutex

Go로 프로그래밍 할 경우, channel을 사용할지 mutex를 사용할지에 대한 조언은 여기에 좀 더 자세히 나와있다.

hiredis for Windows

Redis is simple but powerful memory-based key/value DB and it supports various programming languages. As it is like a dark horse of web, you can easily find any of its drivers for popular web programming languages. But, if you don’t write code for web and use C/C++, don’t worry! They have official C client library called hiredis also.

As you expected, of course, it works well if you are under Linux-based machine. The problem is hiredis does not support Windows compile by itself. So, you have to port its source code to Windows, huh?

Right, it’s not a good idea and not even easy for ordinary C/C++ programmer. You may think you can find some with googling. Okay, here’s my story.

I’m a game server programmer and decided to use Redis for my game. Because I had an experience with hiredis on my Mac, I didn’t expect I will spend such a long time for that – almost a whole work day.

Firstly, as other programmers usually do, I’ve googled about hiredis for windows compile and got the link. Even though the project was a little bit old, it’s under control by Microsoft! I’ve just kept following blog instruction and it worked in some wise. But, as Microsoft’s hard situation, it vomited tons of link errors I could not solve easily, while testing pub/sub functionality. I consumed most of my time to figure it out and almost gave up, almost.

At that moment, by chance, I thought about Node.js. If I google ‘hiredis windows’, the top most result is ‘hiredis – npm’. Node.js is built by C++ and I could get some clue from it. If I followed the link and there was Eldorado!

Windows

Dmitry Gorbunov (@fuwaneko) made a fork of hiredis-node with Windows support.

That fork works clearly + perfectly! No additional code is needed at all. Pub/sub works nicely, too. Node saved my life and everybody live in peace.

That’s how could I find Eldorado for a whole work day. If you encounter above situation, just ignore MSOpenTech and give your hands to Node.

Windows Batch Script

이 블로그에는 세 개의 카테고리가 있다. ‘개(Dog)’, ‘발(Foot)’, ‘인생(Life)’이 그것인데, 순서대로 ‘어떠한 것에 대한 설명’, ‘실수한 경험’, ‘코드가 없는 이야기’라는 의미를 담아두었다. 대충 그렇다.

이번에 쓰려던 것은 사실, ‘개(Dog)’카테고리에 넣으려고 했던 것이다. 이것이 ‘발(Foot)’카테고리에 들어가게 된 데에는 아래와 같은 사연이 있다.

회사에는 수십 대의 서버가 있고, 거기에는 수시로 로그가 쌓이게 된다. 그 양이 꽤 많아서, 몇 달 지나면 금새 하드디스크가 다 차버리게 되는데, 어찌된 영문인지 이를 자동으로 지워주는 기능이 없었다.

서버는 윈도 서버였으며, 로그는 최근 세 달의 것만 유지하도록 하고자 했다. 어려울 거 없다. 세달전의 로그를 지우는 스크립트 혹은 프로그램을 주기적으로 실행해주기만 하면 된다.

윈도에는 리눅스의 crontab과 비슷한 Task Scheduler가 있으며, crontab과 비슷한 형태로 실행하려는 명령을 등록할 수 있다. Manager프로그램을 이용해서 Task를 등록하려면, 이를 실행할 계정정보를 명시해야 한다. 개발자가 공통으로 사용하는 계정은 몇 달 간격으로 비밀번호를 변경하는데, 이것이 Task Scheduler가 등록한 Task를 실행하지 못하는 원인이 되므로, 다른 개발자가 주기적으로 명령어를 실행하는 프로그램을 윈도 서비스로 만들어 놨다. 그리곤, 그걸 써보랜다.

‘MS가 바보도 아니고, 그런걸 생각하지 못했을 리 없잖아!. 게다가, 당신이 만든 그 서비스를 등록하는 것도 귀찮다고!’라고 속으로만 생각했다. 왜냐면, 그 사람이 한참 상사였으니깐.

AT라는 Command Line명령으로 Task Scheduler에 Task를 등록하면, 기본적으로(Manager프로그램에서 변경할 수 있다) System계정이 사용된다. 즉, 그냥 AT명령어 사용하면 된다. 몇 번만 구글링해보면 나온다. 한번은 부족할 수 있다.

로그를 지우는 스크립트는 Windows Batch파일로 작성했다. 이 과정이 길었다. Batch파일의 문법은 제약이 많고, 표현도 풍부하지 못하다. 결정적으로, 난 거의 모른다. 그래도 굳이 Batch파일을 사용한 이유는, 누구나 소스를 보고 수정할 수 있고, 서버에 설치해야 할 프로그램이 없다는 점 때문이었다. 어줍잖은 오픈 마인드.

도저히 익숙해지지 않을법한 문법도 문제였지만, 더 큰 문제는 날짜간 연산을 지원하지 않는 관계로, 대학교 숙제 생각하며 윤년 계산하며 이를 직접 구현해야 했다는데 있다. 그래서…, Ruby로 했으면 30분이면 끝났을 일을 하루 종일 했다.

최종적으로 스크립트를 복사하고 Task Scheduler에 등록하는 등의 일련의 과정은 Ruby를 이용해서 만들고, 이를 OCRA로 exe 변환, 서버에서 실행/설치하는 것으로 마무리 지었다.

당연히 테스트 했고, 날짜계산도 잘 되고…, 뭐 암튼 다 잘 되었다. 처음 작성한 Batch파일에 문제가 없음에 스스로 대견스러웠다.

여기까지였으면 ‘개(Dog)’카테고리에 이 글을 넣었을 거다.

문제는 정확히 2013년 2월 8일 발생했다. 스크립트는 1월 25일부터 매일 문제없이 돌고 있었다. 2월 8일까지는 그랬던 거다. 2월 8일 새벽에…, 스크립트는 모든 로그를 지워버렸다. 젠장! 이 한마디는 결국, 이 포스트가 ‘발(Foot)’카테고리에 들어가 되었음을 의미한다.

난, 이런 경우 심한 자괴감에 빠져든다. 물론, 남들 모르게.

역시나 날짜를 계산하는 부분이 썩어있었다. 날짜는 연월일을 포함한 8자리 문자열로 구성되는데, 90일 이전의 날짜를 구하기 위해서 이를 연, 월, 일로 분리한다. 이를 Batch파일에서는 다음과 같이 처리한다.

SET /A "year=%from:~0,4%"
SET /A "month=%from:~4,2"
SET /A "day=%from:~6,2"

짐작하듯이, 8자리 문자열을 4/2/2 분할하여 이를 숫자로 각 변수에 저장한다. /A옵션은 문자열을 숫자로 인식하도록 한다. 예를 들어 문자열 04는 숫자로 인식되어 4로 저장되게 된다. 물론, Batch에는 타입이 존재하지 않으므로, 4는 다시금 문자열로 다룰 수 있다.

이때, 모르고 넘어가면 안될 결정적인 부분이 있는데, 내가 놓친 것이 이것이다.

Batch파일에서 문자열을 숫자로 변환할 때, 문자열이 0으로 시작하면 이는 무조건 8진수로 인식한다. 따라서, 문자열 08은 유효하지 않은 숫자가 된다. 8진수는 0~7의 숫자만 사용하니깐! 그래서 2월 8일 문제가 생긴 거다. 중간에 에러가 난 거지. 그래서 잘못된 날짜를 갖고 현재날짜와 비교해서 로그를 지우는 바람에, 모든 로그를 몽땅 지워버린 거라고.

수정 후 위 코드는 다음과 같이 바뀌었다.

SET /A "year=%from% / 10000"
SET /A "month=%from% %% 10000 / 100"
SET /A "day=%from% %% 100"

%%는 mod연산이다. 원 날짜 값을 문자열로써 파싱하지 않고, 그냥 산술 식으로 계산해서 변환시켰다. 진작 이럴걸 후회해도 이미 로그는 다 지워졌고, 고객문의는 처리되지 못할 뿐이었다.

덕분에 사건 다음 출근 일에는 하루 종일 입이 부르트도록 로그파일에 인공 호흡했다. 다행히 대부분은 살아나서 우울증은 걸리지 않았다. 그리고 결심했다. Windows Batch파일은 더 이상 사용하지 않으리!

WriteFile() 이 느리다구요?

새로운 프로젝트를 진행 중, 내부 망에서 진행한 테스트였음에도 반응속도가 더뎌지는 경우가 발생했다. 다행히 그 원인을 발견하는 데는 오랜 시간이 걸리지 않았는데, 다름아닌 사내 로깅(Logging) 라이브러리가 그 주범이었다. 안타깝게도, 라이브러리 담당자도 원인을 찾아주지는 못했다. 이 프로젝트는 철저히 혼자 만드는 것이었고, (존재한다면 반드시 사내 라이브러리를 사용해야 한다는 등의)기술적인 간섭 받지 않았기 때문에 로깅 라이브러리를 간단히 새로 만들었고, 결과는 만족스러웠다. 물론, 사내 라이브러리에 대한 불신은 좀 더 커졌다.

기존 프로젝트에도 꾸준히 문제되는 부분이 있었는데, 이 역시 다름아닌 로깅이었다. 이 프로젝트에서 사용되는 로깅 라이브러리는 위에서 언급했던 사내 로깅 라이브러리보다 더 오래된 것으로, 큰 문제가 없어서 그냥 사용하고 있었지만, 로그 양이 갑자기 증가하는 경우에는 문제가 발생했다. 결국, 기존의 소스코드를 약간 수정해서 성능이 개선되는 것을 확인했다.

수정한 부분은 기본 API인 WriteFile() 함수대신 fputs() 함수를 사용한 것인데, 선뜻 이해가 되지 않았던 것은, fputs() 함수도 결국 기본 API를 사용하므로, 성능이 느려지면 느려져야지, 빨리질 수 없다는 사실이었다. 그러나 분명히 성능이 개선되었으므로 소스를 커밋했다.

수정된 소스가 반영되어 세상에 나가기 이틀 전. 전체회의에서 ‘WriteFile() 함수가 fputs() 함수보다 느릴 수 없다’라는 이유로, 소스 적용이 취소되었고, 난 다시 원인을 찾기 시작했다.

내가 가장 의심했던 부분은, WriteFile()함수와 CreateFile() 함수에 붙어있는 무수한 플래그였다. 특히, WriteFile() 함수를 중심으로 살펴봤는데, 별다른 문제를 발견하지 못했다. 그렇지만, 역시나 대부분의 시간은 이 함수가 소모하고 있었다.

결국, 파일과 관련되지 않은 다른 모든 부분을 제거했다. 여전히 느렸고, 따라서 다른 부분은 문제가 아니라고 확신할 수 있었다.

그 전에, 내가 만든 로깅 라이브러리의 파일 입출력 부분을 모두 WriteFile() 함수를 사용하여 시간을 재어보니, 속도가 미미하게나마 더 빨라졌다. 당연히 이래야 하는 거잖아~!?

시간을 되돌려 다시 CreateFile() 함수와 WriteFile() 함수를 살펴보고 있었다. 그리고, 바로 전에 테스트 한 부분과의 차이가 눈에 들어왔다. 내가 만든 라이브러리에서 CreateFile() 함수를 사용할 때는 FILE_FLAG_WRITE_THROUGH를 사용하지 않았다.

다시 테스트해본 결과, 문제의 원인은 FILE_FLAG_WRITE_THROUGH이 확실했다. 구세주 MSDN과 StackOverflow의 글을 살펴본 결과 위 플래그의 역할은 다음과 같다.

C API를 사용하여 파일에 데이터를 기록하려고 할 때, 기본적으로 flush()함수를 호출하지 않으면 파일에 데이터가 기록되어있다는 것을 보장할 수 없다. 이는 쓰려는 데이터가 어딘가에 임시로 저장되어 있다는 것이며, 이로 인해 성능(속도)가 증가할 수 있다. FILE_FLAG_WRITE_THROUGH 플래그는 이 동작을 제어하며, 이 플래그를 사용하여 파일을 생성하였을 경우, WriteFile() 함수는 데이터를 버퍼링하지 않고 바로 디스크캐시로 보낸다. 그러나 디스크캐시로 보낸다는 것이 물리 디스크에 기록하는 것과 동일한 의미는 아니며, 이를 위해서는 추가로 FILE_FLAG_NO_BUFFERING 플래그를 사용해야 한다. 그러면, WriteFile() 함수의 종료 후, 데이터가 물리 디스크에 저장되었다고 보장할 수 있으며, 이에 대한 트레이드오프로 성능(속도)이 떨어지게 된다. 테스트결과, FILE_FLAG_WRITE_THROUGH 플래그만으로도 수 백배의 성능차이가 발생한다.

아마도 라이브러리 개발자는 버퍼링되는 로그가 파일에 남지 않게 될 것을 우려해서 FILE_FLAG_WRITE_THROUGH 플래그를 기본 옵션으로 설정해 둔 듯 하나, 엄밀하게는 FILE_FLAG_NO_BUFFERING 플래그도 함께 설정해야 했다.

그래도, 로깅의 성능은 상당히 크리티컬한 부분인 만큼, 그 의도는 납득하기 어려우며, 오히려 flush() 함수와 같은 인터페이스를 만드는 것이 옳지 않았을까 싶다.

결론: 네가 이해 못하는 코드는 있어도, 이해할 수 없는 현상이 발생하는 코드는 없다.

참고사이트 MSDN, StackOverflow

C++로 만든 라이브러리를 C#에서 사용하기 2

최근에 잠깐 C++/CLI에 대해서 살펴볼 일이 있어서, 무려 4년적에 적었던 ‘C++로 만든 라이브러리를 C#에서 사용하기‘를 살펴봤다.

당시와는 달리, 검색하면 관련된 내용을 찾기가 수월해진 것 같다. C++/CLI가 사용되는 경우가 늘었나 라고 생각했지만, VS2010에서 CLR프로젝트의 자동완성 기능은 제대로 동작하지 않는다. 스마트 기기류에 대응하느라 바쁜 Microsoft라 여기까지 신경 쓸 여력은 없나 보다.

일전의 설명만으론 부족한 부분을 보충하기위해 테스트코드를 만들었다.

Visual Studio 2010을 사용했으며, 솔루션의 구성은 다음과 같다.

LibAdder C++ 라이브러리
TestLibAdder LibAdder 테스트
LibAdderCS LibAdder의 인터페이스를 ManagedCode로 감싸주는 C++/CLI 라이브러리
TestLibAdderCS LibAdderCS 테스트

즉, LibAdder(C++) -> LibAdderCS(C++/CLI) -> TestLibAdderCS(C#) 이다.

예제파일은 github 에서 받을 수 있다.

참조사이트1(Managed String을 Unmanaged String으로 변환하기)
참조사이트2(델리게이트를 함수포인터로 변환하기)

코딩 인터뷰 완전 분석 215쪽 고난이도 연습문제 18.10(알고리즘 이벤트 마지막 문제)

이번 문제는 같은 길이의 단어로 구성되어있는 사전에서, 한 단어에서 다른 단어로 한 글자씩 바꿔가며 변형해 나가는 과정을 알아내는 것이 과제이다.

심화문제에서 최단/최장 경로를 구하는 것까지 나와서, 처음에는 사전 데이터를 그래프로 구성하려고 했다. 그러면 기존의 알고리즘을 이용해 경로를 찾아내기 수월할 테니. 그러나, 수천 개에 달하는 사전데이터를 그래프로 구성하는데 예상보다 시간이 많이 걸렸고, 이걸 최적화 해보겠다고 삽질하다가, 방법을 선회해야만 했다.

1. 미리 존재하는 파일로부터 사전을 구성한다. read_dic/1
2. 주어진 단어 A, B로 부터 중간단어를 하나씩 구한다. replace_word/3
3. 중간단어가 사전에 존재하는지 확인하고, 존재한다면 결과에 저장하고 그렇지 않으면 다음 순서의 중간단어를 구해서 2의 과정을 반복한다. find_path/6
4. 마지막의 중간단어가 B와 동일하면 경로를 구한 것이고, 그렇지 않으면 해당하는 경로가 존재하지 않는 것이다.
5. 단어 A, B의 길이가 다르면, word_length_mismatch를 반환하고, 4자리나 5자리 단어가 아니면, no_dictionary_exist를 반환한다. find_path/2
6. 각 사전은 word4.txt와 word5.txt로 존재한다고 가정한다.

출력예

74> insight:find_path("wean", "zein").
["wean",impossible]
75> insight:find_path("damp", "like").
["damp","dame","dime","dike","like"]
76> insight:find_path("damp", "damp2").
word_length_mismatched
77> insight:find_path("damp", "damp"). 
["damp"]

코딩 인터뷰 완전 분석 210쪽 17.3 변형 문제(알고리즘 코딩 이벤트 2주차 문제)

문제는 인사이트의 블로그에서 확인할 수 있듯이,
팩토리얼(!)의 결과값에서, 마지막에 연속되는 0의 개수와 0이 아닌 첫번째 숫자를 알아내는 것이다.

사실, 이전 문제를 낑낑대며 풀고, 다른 사람들의 풀이 법을 보니, 완전 삽질했구나 싶었다. 더구나 이미 얼랭을 사용한 사람이 있다니…OTL

그러나 이건 좀 쉽다! 한 15분 걸린 것 같다. Erlang도 조금 익숙해 졌고…

1. 팩토리얼식의 각 값을 순차적으로 곱한다. last/1
1.1 1의 과정 중, 마지막 0의 수를 세어 누적한다. countzero/1
1.2 1의 과정 중, 0이 아닌 마지막 수를 다음 곱셈에 사용하고, 나머지 숫자는 버린다.

출력예

Eshell V5.9.1  (abort with ^G)
1> c(insight2).
{ok,insight2}
2> insight2:last(1).
{0,1}
3> insight2:last(10).
{2,8}
4> insight2:last(100).
{24,6}
5> insight2:last(2012).
{490,2}
6> insight2:last(10000).
{2444,2}
7> 

문제로 풀어보는 알고리즘 149쪽 문제 3.c 풀이(알고리즘 코딩 이벤트 1주차 2번째 문제)

구독하던 인사이트 출판사 블로그에서 이벤트를 하는 것을 발견하고, 간만에 알고리즘 문제에 도전~

문제는 블로그에서 확인할 수 있듯이, 주어진 집합을 하나이상의 집합으로 나누는 모든 방법을 출력하는 것이다.

뭔가, 효율적으로 이를 구하는 방법이 존재할 것 같아서 계속 고민해 봤으나, 원초적인 알고리즘을 만드는 일을 수학자가 할 일이라고 생각해서, 그냥 딱 떠오르는 방법으로 코딩 해 버렸다.

1. 입력 받은 N으로 리스트를 집합을 구한다. makelist/1
2. 1에서 만든 집합에서, 가능한 모든 부분집합을 구한다. subsets/1
3. 2에서 구한 부분집합의 조합 중, 각 원소가 한 개씩만 존재하는 조합만 고른다. combinations/1

평소 사용하던 언어로 하는 것은 시시할 것 같아, 도무지 사용할 일이 없을 것 같았던 Erlang으로 시도. 머리가 더 아팠다.

출력예

1> c(insight).
{ok,insight}
2> insight:combinations(3).
[[0],[1],[2]]
[[0,1],[2]]
[[0,2],[1]]
[[0],[1,2]]
[[0,1,2]]
ok
3> insight:combinations(4).
[[0],[1],[2],[3]]
[[0,1],[2],[3]]
[[0,2],[1],[3]]
[[0],[1,2],[3]]
[[0,1,2],[3]]
[[0,3],[1],[2]]
[[0],[1,3],[2]]
[[0,1,3],[2]]
[[0],[1],[2,3]]
[[0,1],[2,3]]
[[0,2,3],[1]]
[[0,2],[1,3]]
[[0,3],[1,2]]
[[0],[1,2,3]]
[[0,1,2,3]]
ok
4>

중요: Erlang은 인사이트의 책으로 처음 배웠다.

비트필드의 크기(Size of Bit-fields)

union U_Data
{
    unsigned int _key;
    struct S_Data
    {
        unsigned short _code;
        unsigned short _no;
    } _evtkey;
};

개발자는 두 값(code, no)를 조합해서 고유한 키값을 손쉽게 만들기 위해 U_Data를 아래와 같이 사용하고 있다.

U_Data data;
data._evtkey._code = 10;
data._evtkey._no = 4;
unsigned int another_key = data._key; // 고유한 키값을 손쉽게(?) 얻었다!

그런데, _code의 크기가 100,000을 넘어가게 생겼다. 그래서 타입을 int로 바꿔야 했다. 이미 _key는 unsigned int 타입으로 여러 함수의 인자로 사용되고 있기 때문에, U_Data의 현 크기(4바이트)를 바꾸기는 쉽지 않다.

다행히 _no의 범위는 기껏해야 수십을 넘어가지 않았으므로, 비트필드를 사용하여, _code에 24비트를, _no에 8비트를 사용하기로 하였다.

union U_Data
{
    unsigned int _key;
    struct S_Data
    {
        int _code:24; // 타입을 바꾸고, 24비트를 할당
        unsigned short _no:8; // 8비트를 할당
    } _evtkey;
};

다른 코드는 손대지 않고, 손쉽게 _code의 크기를 늘렸다. 근데, 제대로 동작하지 않는다. 앞선 예제와 반대로, _key를 이용해서 _code와 _no를 구하려고 하니, _no에는 쓰레기 값이 있다.

이유는, 1바이트 패킹 시(#pragma pack(1)), VC++에서, U_Data의 크기를 4바이트가 아닌, 6바이트로 다루기 때문이며, 비트필드가 어떻게 메모리에 할당되는지에 대해서는 표준문서가 명시하고 있지 않다.

Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined.

– ISO/IEC 14882:2011 9.6.1

VC++에서는 비트필드가 명시된 타입에 순차적으로 값을 구겨 넣으며, 타입이 변경되면 새로운 타입에 새롭게 할당을 시작한다. 즉, S_Data의 첫번째 데이터 타입이 int이므로 4바이트에 24비트를 할당하고, 그 다음 2바이트인 unsigned short타입에 8비트를 할당해서, 총 6바이트가 사용된다. int타입인 _code에 8비트가 남았다고 해서 _no를 int에 할당하는 것이 아니란 이야기다(gcc는 4바이트를 할당한다). 명시적으로 새로운 할당을 시작하려면 비트의 크기에 0을 넣으면 된단다(http://msdn.microsoft.com/en-us/library/ewwyfdbe.aspx).

이 문제는 _code와 _no의 타입을 int로 맞춰주는 것으로 해결된다.

문제를 해결하고, 기억을 되살려보니, 정확히 2005년 10월 4일에 이와 똑같은 문제를 겪고 남겨둔 기록이 있었다.


struct T1
{
    int m_a : 5;
    int m_b : 6;
    int m_c : 5;
};
struct T2
{
    short m_a : 5;
    short m_b : 6;
    short m_c : 5;
};
struct T3
{
    char m_a : 5;
    char m_b : 6;
    char m_c : 5;
};

1로 패킹했을 경우(#pramgma pack(1)), 각 구조체의 크기는 각 각 얼마일까?

만약, sizeof()의 결과로 2를 기대했다면, T1과 T3는 잘못된 결과를 야기한다.

T1의 경우, sizeof()의 결과는 4이다. 총 비트의 합은 16이지만, 첫 번째 멤버변수의 크기가 4이기 때문에 컴파일러는 모든 비트를 첫 번째 변수의 크기인 4바이트에 구겨 넣는다. 나머지 16비트는 사용되지 않는다.

T2의 경우는 2이다. 16비트는 short int형에 딱 맞게 들어간다. 따라서 낭비하는 비트는 없다.

T3의 경우는 3이다. 컴파일러는 char의 크기인 1바이트에 5비트를 우선 구겨 넣는다. 그 다음 6비트를 확보해야 하는데, 첫 번째 1바이트에는 3비트밖에 남은 공간이 없으므로 또 하나의 char형 크기인 1바이트에 6비트를 확보한다. 마찬가지로 그 다음 5비트는 적절한 공간이 없으므로 새로운 char형 크기인 1바이트를 또 할당한다. 그래서 총 3바이트 크기가 되며, 8비트를 사용하지 않은 채 남겨두게 된다.

대부분의 컴파일러는 이와 같은 방식으로 동작한다. 그러나 표준문서에 비트필드의 크기에 대한 언급은 없기 때문에 엄밀히 따지면 이는 컴파일러의 구현에 따라서 틀려질 수 있다. 참고로, 비트필드에 접근하는 속도는 표준타입의 멤버변수에 접근하는 시간보다 느리기 때문에, 속도를 위해서라면 바람직한 방법은 아니다.

2011년, 이직.

마지막으로 포스팅했던 날짜가 2010년 12월 7일이네요. 그때라면, 발전하지 못하고 있는 것 같은 스스로를 자책하면서 이직을 준비하던때 입니다. 나름 만족하며 다니던 회사였는데, 막상 옮기려니 이런저런 생각이 들어군요. 왜 제가 회사를 갈아타려했는지 이야기해보겠습니다.

우선, 월급이 밀린다거나, 회사의 정치적인 문제라거나, 혹은 회사가 망해버렸거나, 그것도 아니면 잘렸거나…, 이런건 이유에 해당되지 않습니다. 전 한번도 월급을 늦게 받아본 적이 없었고, 회사가 망했던 경우도 없었고, 사내정치나 대인관계로 고민해본적이 없습니다. 그런 점에선 분명히 운이 좋았다고 할 수 있습니다. 여기에 조금 덧붙이자면, 제 여건에 맞춰서 나름 회사를 잘 선택했다고 볼 수도 있고요.

2010년 12월 7일 당시의 회사는 두 번째 회사였습니다. 첫 번째 회사는 2년 동안 다녔고, 당시의 퇴직 사유는 절반 이상이 ‘세계여행’때문이었죠. 나머지 중 상당수는 출근길에 본 눈부신 햇살 때문이었고요. 좀 더 정확히 이야기하면, 그때의 햇살이, 오랜 시간 꿈꿔왔던 ‘세계여행’에 대한 욕구를 자극했던 거죠. 그리고 얼마 안 있어 회사를 그만 두었고, 세 달을 쉬고 떠나려 했는데, 채 세 달을 채우진 못했습니다. 세 달 동안 논다는 것도 어렵더라고요. 제 생각엔, 한 한 두 달이면 충분하지 않을까 싶습니다. 그런 의미에서 회사에서 장기 근속자에게 한 달의 특별 휴가를 주는 건 직원을 붙잡아둘 수 있는 아주 좋은 수단이라는 생각이 듭니다. 어쨌든 그리고 정말 여행을 떠났습니다. 2년 간 일을 했으니 어느 정도는 돈이 있었고, 따라서 굳이 돈에 스트레스 받으며 돌아다니지 않아도 됐죠. 또, 1년을 예상했으니 시간 때문에 서두를 필요도 없었고요. 근데, 그도 6개월 여 만에 그만 두었습니다. 여기에 대해서는 어떤 식으로 든 따로 이야기할 기회가 있으리라 믿고, 끝내죠.

한국에 돌아와서 일주일 동안은 매일 면접을 봤는데, 그 중 한 회사에 입사하기로 했지만, 이후에 다른 회사에서 연락이 오는 바람에, 먼저 회사에 양해를 구하고 저의 두 번째 회사에 입사하게 되었죠. 그렇게 두 번째 회사에서 거의 4년을 일했습니다.

환경, (대부분의)사람들, 보수. 이 모두에 대해서 큰 불만이 없었습니다. 적어도 처음 2년은 상당히 만족스럽게 느꼈고, 그래서 타 회사의 제의도 고민 없이 거절해 버렸죠. 돌이켜 생각해보면, 팀장이 바뀐 이후부터 아쉬움이 조금씩 늘어나지 않았나 싶습니다. 우선, 팀 내의 사소한 일들이 공정하게 처리되지 않는다고 느끼게 되었고요, 팀원들의 업무 성과에 대해서 신경 쓰지 못하는 모습이 눈에 띄었죠. 또 하나 결정적인 건, 그런 아쉬움이나 불만들을 제대로 들어주지 않는다는 것이었습니다. 즉, 팀원은 팀원들끼리 불만을 이야기하고, 팀장은 팀장 나름의 불만을 이야기하는 형국이었던 겁니다. 이런 상태로 시간이 흐르니, 팀원이 어떤 문제를 발견했을 때도, 굳이 고치려 하지 않게 되더군요. 혼자 해결 할 수 있는 일이면 당연히 그렇게 하겠지만, 아시다시피 회사 업무라는 게 부서간, 또 사람간에, 얽히고 설킨 관계인 지라, 어디 그게 쉽습니까. 이렇게 되니, 팀이 스스로 발전하는 기회를 놓쳐버리게 된 것입니다. 이 말은요…, 팀원이 팀의 구성원으로서 느낄 수 있는 성취감을 없애버렸다는 말이기도 합니다. 회사는 그냥 회사가 되어버린 거고, 팀은 그냥 팀이 되어버린 겁니다. 조직을 위해서 하고자 하는 동기부여가 제대로 되지 않았던 거죠.
일이 재밌으면, 사람을 붙잡을 수 있습니다. 수많은 오픈소스 프로젝트가 이를 증명하고 있습니다. 동시에, 소리 없이 사라지는 상당수의 오픈소스 프로젝트가, 일에 대한 열정이 얼마나 쉽게 사라지는지, 또 동기부여가 부족한 일이 얼마나 쉽게 중단되는 지를 증명합니다.

돈을 많이 주면, 사람을 붙잡을 수 있습니다. 다른 의견도 있겠지만, 대부분의 직장인에게 연봉을 두 배로 준다고 하면, 군말 없이 자리에서 일어날 겁니다. 그러나, 이 많다는 기준이 천차만별인데다, 계속 변하는 지라, 돈으로만 사람을 붙잡으려면, 상당한 손해를 감수해야 할 수도 있겠죠.

이 모든 것을 뛰어넘을 수 있는 게 ‘사람’이라고 생각합니다. 단순히 좋은 술친구를 의미하는 것 말고, 서로의 고민을 이야기하고 해결해 줄 수 있는 사람 말입니다. 사람이 좋으면 일이 재미없을 리 없을 테죠. 그렇다면, 돈이 다소 모자라도 좀 더 견딜 수 있을 겁니다.

비슷한 처지의 사람들과는 이것이 쉽습니다. 친구 간에 고민을 털어놓는 것 처럼요. 최소한 같이 고민해 줄 수는 있죠. 그러나 이를 해결해 줄 수 있는 열쇠를 가진 사람에게는 그것이 어렵습니다. 그래서, 책임 있는 자리에 앉아있는 사람의 역할이 중요합니다. 안타깝게도, 자리가 높아질 수록, 이런 생각을 점점 잊더군요. 그래서 이런 사람은 찾기 힘듭니다. 전, 지금까지 한 번 봤습니다. 위에 언급했던 첫 번째 팀장이요. 팀원을 위해 상사나 다른 부서와 싸워줄 수 있는 탐장은 많지 않습니다. 근데, 그 팀장은 싸우더군요. 싸운다기 보다는, 할 말을 하는 거죠. 누가 봐도 옳은 생각을요.

이쯤 해서 정리하는 게 좋겠습니다. 2011년이 얼마 남지 않았거든요. 제가 회사를 옮기려고 했던 이유는, 모두가 옳다고 생각하는 의견이 제대로 반영되지 못하는 것 때문이었습니다. 그렇게 5-6개월 동안 몇 군데에 이력서를 넣었고, 지금 일하는 곳으로 옮기게 되었습니다.

어쩌면, 새로운 회사에서는 제 고민이 해결됐는지 궁금한 분도 있을지 모르겠네요. 그것에 대해서는, 나중에 기회가 되면 다시 쓰도록 하겠습니다.

2011년을 포스트 하나 없이 그냥 넘길 수 없어서, 원래 쓰려 던 주제는 이게 아니었는데, 괜스레 개인적인 이야기만 늘어놓아 버렸습니다. 새해에도, 프로그래밍을 좋아하시는 모든 분들이 계속 프로그래밍 할 수 있었으면 좋겠습니다.