IOCP의 경우에는 여러개의 thread가 경쟁적으로 queue에서 io을 순서대로 가져와서 처리하는 것이 lock없이 처리 가능하다.(kernel level에서 serialized된 대로 user level로 post 되기 때문이다.)
하지만, epoll,kqueue의 경우에는 그러한 역할이 내부적으로 존재하지 않는다.
io event 발생을 polling하고, event들을 iteration하는 loop에서 처리한다.
좀 더 성능 향상을 위해서 iteration을 쪼개서 pararell하게 가능하긴 할 것이다.
어쨌든, 위의 일들은 몇개의 network io thread에서 일어나는 일들이다.
network io thread context에서, recv(socket,buffer,MAX_SIZE)를 통해 buffer에 copy된 것을 packet으로 쪼개고, dispatcher를 통해서 해당 logic thread에 전달하고, 처리되는 과정에서, 그 buffer를 한 번도 copy하지 않으려면 어떻게 해야 하는가?
이 문제가 zero-copy문제이다.
dispatch된 packet이 parse되어, logic function의 input parameter로 변환된 후에는 비로소, 생명을 다해도 된다.
그때까지는 recv를 통해 얻어진 packet에 overwrite되지 않고 보호 되어야 한다. 이 메커니즘을 어떻게 구현할까?
다시 말하면, packet이 circular buffer에서 잘라지고(slice), 전달되고(dispatch), 처리되는(handle) 동안에 어떻게 보호할 수 있을까? 그 동안 circular buffer에는 socket으로부터 새롭게 받아진 stream이 쯔나미처럼 무서운 속도로 다가올 것이다.
slice,dispatch 전까지는 일단 circular buffer를 통해서 zero-copy 구현이 가능하다.
보호하려면, socket에서 buffer로 copy하기 전에 copy해도 되는지 여부를 확인이 가능해야 한다. copy해도 되는지 여부는, slice->dispatch->handle의 과정을 거치고 있는 packet의 memory를 overwrite하는지 여부를 알아내야 하는 것이다.
dispatch까지는, 순서대로 처리가 될 것이기 때문에, 마지막 packet의 index를 검사함으로써 간단히 해결된다.
문제는, dispatch된 후에는 여러개의 thread들이 경쟁적으로 처리할 것이고, 어떤 것이 먼저 처리 될지 알 수 없기 때문에, 마지막이라는 것을 알 수가 없다.
이에 맞는 데이터 구조를 만들어야 한다.
dispatch된 packet의 index를 sorted list로 갖고 있고, dispatch될 때, insert 해주고,
handled되고 나면, list에서 index를 remove한다. 그리고, recv 시 overwrite check여부는 recv index보다 큰 최소값을 체크하면 되므로 가능하다.
이 오퍼레이션들은 network thread,dispatch thread,logic thread가 건들기 때문에, atomic하도록 구현해야 한다.
lock없이 구현하는 방법은 dispatch에서 logic thread로 전달시 zero-copy를 포기하고 packet을 copy하는 방법일 것이다. 두가지 경우에 대해서 성능 테스트를 해보지 않고서는 섣불리 판단하기 힘들 것 같다.
도움이 되셨다면, 광고 클릭을 ㅎㅎ ^^
댓글 없음:
댓글 쓰기