2009년 6월 30일 화요일

[socket] 옵션 설정

소켓옵션
네트워크 환경은 매우 다양하며, 예측하기 힘든 경우도 많이 발생한다. 때문에 네트워크프로그램의 종류에 따라서 소켓의 세부사항을 조절해야 하는 경우가 발생한다. 이러한 소켓옵션 설정을 위해서 소켓은 getsockopt()와 setsockopt()두개의 함수를 제공한다. 이름에서 알 수 있듯이 getsockopt는 현재의 소켓옵션값을 가져오기 위해서, setsockopt는 소켓옵션값을 변경하기 위해서 사용한다.


예를 들자면 동일한 네트워 프로그램이라고 하더라도 ATM망에서 작동하는 것과 인터넷망 PPP에서 작동하는 것은 환경에 있어서 차이가 생길 수 밖에 없을 것이다. 소켓버퍼의 크기를 예로 들자면, 일반적으로 (대역폭 * 지연율) * 2의 공식에 따를 경우 최적의 효과를 보여준다고 한다. 다음은 이들 함수의 사용방법이다.

#include
#include

int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);

s : 소켓지정번호

level : 소켓의 레벨로 어떤 레벨의 소켓정보를 가져오거나 변경할 것인지를 명시하며, SOL_SOCKET와 IPPROTO_TCP 중 하나를 사용할 수 있다.

optname : 설정을 위한 소켓옵션의 번호

optval : 설정값을 저장하기 위한 버퍼의 포인터

optlen : optval 버퍼의 크기


설정값을 void * 로 넘기는 이유는 설정하고자 하는 소켓옵션에 따라서, boolean, interger, 구조체등 다양한 크기를 가지는 데이터형이 사용되기 때문이다. 만약 변경하고자 하는 소켓옵션이 boolean을 따른다면, 0혹은 1이 사용될 것이다.


SOL_SOCKET레벨에서 사용할 수 있는 옵션과 데이타형은 다음과 같다.
옵션값 데이터형 설명
SO_BROADCAST BOOL 브로드캐스트 메시지 전달이 가능하도록 한다.
SO_DEBUG BOOL 디버깅 정보를 레코딩 한다.
SO_DONTLINGER BOOL 소켓을 닫을때 보내지 않은 데이터를 보내기 위해서 블럭되지 않도록 한다.
SO_DONTROUTE BOOL 라우팅 하지 않고 직접 인터페이스로 보낸다.
SO_GROUP_PRIORITY int 사용하지 않음
SO_KEEPALIVE BOOL Keepalives를 전달한다.
SO_LINGER struct LINGER 소켓을 닫을 때 전송되지 않은 데이터의 처리 규칙
SO_RCVBUF int 데이터를 수신하기 위한 버퍼공간의 명시
SO_REUSEADDR BOOL 이미 사용된 주소를 재사용 (bind) 하도록 한다.
SO_SNDBUF int 데이터 전송을 위한 버퍼공간 명시

IPPROTO_TCP레벨에서 사용할 수 있는 옵션과 데이터형이다.
TCP_NODELAY BOOL Nagle 알고리즘 제어




SO_REUSEADDR
간단한 예로, 소켓을 이용한 서버프로그램을 운용하다 보면 강제종료되거나 비정상 종료되는 경우가 발생한다. 테스트를 목적으로 할 경우에는 특히 강제종료 시켜야 하는 경우가 자주 발생하는데, 강제종료 시키고 프로그램을 다시 실행시킬경우 다음과 같은 메시지를 종종 보게 된다.

bind error : Address already in use

이는 기존 프로그램이 종료되었지만, 비정상종료된 상태로 아직 커널이 bind정보를 유지하고 있음으로 발생하는 문제다. 보통 1-2분 정도 지나만 커널이 알아서 정리를 하긴 하지만, 그 시간동안 기달려야 한다는 것은 상당히 번거로운 일이 될 것이다. 이 경우 다음과 같은 코드를 삽입함으로써 문제를 해결할 수 있다.

int sock = socket(...);
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&bf, (int)sizeof(bf));

이렇게 하면 커널은 기존에 bind로 할당된 소켓자원을 프로세스가 재 사용할 수 있도록 허락하게 된다.


다음은 소켓버퍼의 크기를 가져오고 설정하는 완전한 코드다.

#include
#include
#include
#include
#include
#include

int main(int argc, char **argv)
{
int sockfd;
int bufsize;
int rn;

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Error");
return 1;
}

rn = sizeof(int);
// 현재 RCVBUF 값을 얻어온다.
if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &bufsize, (socklen_t *)&rn) < 0)
{
perror("Set Error");
return 1;
}
printf("Socket RCV Buf Size is %d\n", bufsize);

// 버퍼의 크기를 100000 으로 만든다.
bufsize = 100000;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (void *)&bufsize, (socklen_t)rn) < 0)
{
perror("Set Error");
return 1;
}
return 0;
}



TCP_NODELAY
이 옵션을 이해하려면 Nagle알고리즘에 대해서 이해를 해야 한다. Nagle 알고리즘이 적용되면, 운영체제는 패킷을 ACK가 오기를 기다렸다가 도착하면, 그 동안 쌓여있던 데이터를 한꺼번에 보내게 된다. 이러한 방식을 사용하게 되면, 대역폭이 낮은 WAN에서 빈번한 전송을 줄이게 됨으로 효과적인 대역폭활용이 가능해진다.


대부분의 경우에 있어서 Nagle 알고리즘은 효율적으로 작동하긴 하지만, 빈번한 응답이 중요한 서비스의 경우에는 적당하지 않은 경우가 발생한다. 예를 들어 X-Terminal을 이용할 경우 마우스 이벤트는 즉시 전달될 필요가 있는데, Nagle알고리즘을 사용하면 아무래도 반응시간이 떨어지게 될 것이다. 실시간적인 반응이 중요한 온라인 게임역시 Nagle 알고리즘을 제거하는게 좋을 것이다.


아래의 이미지는 nagle이 적용되었을 때와 그렇지 않을 때, 어떻게 데이터 전송이 일어나는지를 보여주고 있다.












SO_LINGER
SO_LINGER은 소켓이 close()되었을 때, 소켓버퍼에 남아있는 데이터를 어떻게 할 것이지를 결정하기 위해서 사용한다. 다음은 SO_LINGER 옵션에 사용되는 데이터구조체이다.

struct linger
{
int l_onoff;
int l_linger;
}

l_onoff : linger 옵션을 끌것인지 킬 것인지 결정

l_linger : 기다리는 시간의 결정


위의 두개의 멤버변수의 값을 어떻게 하느냐에 따라 3가지 close방식을 결정되어 진다.

l_onoff == 0 : 이 경우 l_linger의 영향을 받지 않는다. 소켓의 기본설정으로 소켓버퍼에 남아 있는 모든 데이터를 보낸다. 이때 close()는 바로 리턴을 하게 되므로 백그라운드에서 이러한 일이 일어나게 된다. 우아한 연결 종료를 보장한다.

l_onoff > 0 이고 l_linger == 0 : close()는 바로 리턴을 하며, 소켓버퍼에 아직 남아있는 데이터는 버려 버린다. TCP 연결상태일 경우에는 상대편 호스트에 리셋을 위한 RST 패킷을 보낸다. hard 혹은 abortive 종료라고 부른다.

l_onoff > 0 이고 l_linger > 0 : 버퍼에 남아있는 데이터를 모두 보내는 우아한 연결 종료를 행한다. 이때 close()에서는 l_linger에 지정된 시간만큼 블럭상태에서 대기한다. 만약 지정된 시간내에 데이터를 모두 보냈다면 리턴이 되고, 시간이 초과되었다면 에러와 함께 리턴이 된다.

2009년 6월 17일 수요일

[omp] directive, fuction, macro 정리

- #pragma omp ordered : 병령 루프는 동기화 없이 반복된다. 그러나 이러한 병렬 실행을 막고 순차적으로 실행해야 하는 부분에서 이 를 사용한다.

ex)

omp_set_num_thread(4);

#pragma omp parallel private(myid);
{
myid = omp_get_thread_num();

#pragma omp for private(i) ordered

for(int i=0; i<8; i++)
{
#pragma omp ordered

printf("T:%d,i=%d\n", myid, i);
}
}

-. 데이터 유효범위
OMP의 쓰레드별 데이터 변수별로 사용 범위(개별로 쓸것인지, 아니면 공유하여 쓸것인지)를 지정 할
수 있다.
기본적으로 OMP의 루프변수(루프의 count인자)는 private로 지정되며, 이외의 모든 변수는 shared
로 자동적으로 지정된다.
OMP는 이를 수동으로 사용자가 지정할 수 있도록 clause를 제공한다.
private(변수, 변수...)
shread(변수, 변수...)

-. omp_set_dynamic
사용자가 지정한 개수로만 동적 개수로 쓰레드를 생성하여 시스템의 상황에 맞도록 병렬 실행한다.

작업분할(do/for, section, single)

do/for

=> (균등한 분할)
Master Thread => Fork => Thread Team => JOIN
=>

section

=> (편중된 분할)
Master Thread =>Fork => Thread Team => JOIN
=>


single

=> (Only 1 Thread)
Master Thread =>Fork => Thread Team => JOIN
=>




FORK 후의 JOIN시 어떠한 쓰게드라도 모두 종료되어야만 쓰레드 팀이 소거된다.
다른 쓰레드가 종료되기를 기다리지 않고, 다른 작업을 계속하려면 nowait clause를 사용해야 한다.

-. single 지시어는 암시적인 장벽이 없어 가정 먼저 실행된 스레드만이 signle 코드 블럭을 실행하게 된다.
만약 nowait가 없다면 signle 코드를 실행한 스레드가 종료해야만, 다른 스레드도 종료하게 된다.


<결합도 작업 구문 clause>

#pragma omp parallel
#pragma omp for ==> #pragma omp parallel for


#pragma omp parallel
#pragma omp sections ==> #pragma omp parallel sections


변수 영역 관련

-. 병렬 구문 아래의 순차적으로 실행되는 for 루프의 인자는 기본적으로 shared를 가지게 된다.
따라서 명시적으로 루프의 인자는 private clause를 사용 할 필요가 있다.

-. 병렬 구문 아래의 서브 루틴의 지역변수는 기본적으로 private 속성을 가지게 된다.
그러나 static 변수경우 쓰레들간 공유하게 된다.

-. 병렬 구문 아래의 루프안의 수시로 선언되는 지역변수는 private가 된다.

-. defualt(none)을 사용하여, 병렬구문 내의 모든 변수가 shared또는 private로 미리 선언되어야
한다.

Reduction Clause

병렬로 처리된 결과를 마지막에 모두 취합하여 계산해야 하는 변수에 대하여 사용한다.

예)
#pragma omp parallel for
for(int i=0; i<100; i++)
sum += a + b;

위 변수 sum은 병렬로 처리되어 thread마다 소유하게 되고, join시 team thread 별 sum변수는 다
른 값을 가지게 되어 스레드별로 모두 취합하여 따로 계산해 줘야하는 경우에는
reduction clause를 사용하여 이러한 과정을 자동적으로 처리하도록 할 수 있다.

다음은 reduction clause를 사용한 예이다.

#pragma omp parallel for reduction(+:sum)
for(int i=0; i<100; i++)
sum += a+b;




reduction 사용시 주의점

우선 순위에 영향을 받게되는 연산의 경우에는 reduction 사용시 나중에 어떻한 값이 나오게 될 지 장담할 수 없게 된다.

즉, 먼저 실행이 종료된 스레드의 값이 우선순위가 높게되며, 나중에 종료된 스레드 값이 우선순위가 낮아 결과값으로 취합할 경우 장담할 수 없는 값이 나오게 되는데 이러한 경우를 막기위해서는 추가적인
코드 처리를 해줘야 한다.

또한 reduction 변수는 스칼라 형이여야하고, 스레드 팀에서 모두 공유 할 수 있는 shared 타입이여야 한다.

따라서 배열이나 구조체의 경우 reduction 변수가 될 수 없다.


scheduale


staic(nochunk)





staic(chunk)

[omp] 개념

Types of Parallel Programming
Before we begin with OpenMP, it is important to know why we need parallel processing. In a typical case, a sequential code will execute in a thread which is executed on a single processing unit. Thus, if a computer has 2 processors or more ( or 2 cores, or 1 processor with HyperThreading), only a single processor will be used for execution, thus wasting the other processing power. Rather than letting the other processor to sit idle (or process other threads from other programs) we can use it to speed up our algorithm.
Parallel processing can be divided in to two groups, task based and data based.

Task based : Divide different tasks to different CPUs to be executed in parallel. For example, a Printing thread and a Spell Checking thread running simultaneously in a word processor. Each thread is a separate task.
Data based : Execute the same task, but divide the work load on the data over several CPUs. For example, to convert a color image to grayscale. We can convert the top half of the image on the first CPU, while the lower half is converted on the second CPU (or as many CPUs you have), thus processing in half the time.
There are several methods to do parallel processing


























Use MPI : Mesage Passing Interface - MPI is most suited for a system with multiple processors and multiple memory. For example, a cluster of computers with their own local memory. You can use MPI to divide workload across this cluster, and merge the result when it is finished. Available with Microsoft Compute Cluster Pack.
Use OpenMP : OpenMP is suited for shared memory systems like we have on our desktop computers. Shared memory systems are systems with multiple processors but each are sharing a single memory subsystem. Using OpenMP is just like writing your own smaller threads but let the compiler do it. Available in Visual Studio 2005 Professional and Team Suite.
Use SIMD intrinsics : Single Instruction Multiple Data (SIMD) has been available on mainstream processors such as Intel's MMX, SSE, SSE2, SSE3, Motorola's (or IBM's) Altivec and AMD's 3DNow!. SIMD intrinsincs are primitive functions to parallelize data processing on the CPU register level. For example, the addition of two unsigned char will take the whole register size, although the size of this data type is just 8-bit, leaving 24-bit in the register to be filled with 0 and wasted. Using SIMD (such as MMX), we can load 8 unsigned chars (or 4 shorts or 2 integers) to be executed in parallel on the register level. Available in Visual Studio 2005 using SIMD intrinsics or with Visual C++ Processor Pack with Visual C++ 6.0.





Understanding the Fork-and-Join Model
OpenMP uses the fork-and-join parallelism model. In fork-and-join, parallel threads are created and branched out from a master thread to execute an operation and will only remain until the operation has finished, then all the threads are destroyed, thus leaving only one master thread.

The process of splitting and joining of threads including synchronization for end result are handled by OpenMP.




























How Many Threads Do I Need?
A typical question is that how many threads do I actually need? Are more threads better? How do I control the number of threads in my code? Are number of threads related to number of CPUs?
The number of threads required to solve a problem is generally limited to the number of CPUs you have. As you can see in the Fork-and-Join figure above, whenever threads are created, a little time is taken to create a thread and later to join the end result and destroy the threads. When the problem is small, and the number of CPUs are less than the number of threads, the total execution time will be longer (slower) because more time has been spent to create threads, and later switch between the threads (due to preemptive behaviour) then to actually solve the problem. Whenever a thread context is switched, data must be saved/loaded from the memory. This takes time.

The rule is simple, since all the threads will be executing the same operation (hence the same priority), 1 thread is sufficient per CPU (or core). The more CPUs you have, the more threads you can create.

Most compiler directives in OpenMP uses the Environment Variable OMP_NUM_THREADS to determine the number of threads to create. You can control the number of threads with the following functions;


// Get the number of processors in this system
int iCPU = omp_get_num_procs();

// Now set the number of threads
omp_set_num_threads(iCPU);



Of course, you can put any value for iCPU in the code above (if you do not want to call omp_get_num_procs), and you can call the omp_set_num_threads functions as many times as you like for different parts of your code for maximum control. If omp_set_num_threads is not called, OpenMP will use the OMP_NUM_THREADS Environment Variable.


Parallel for Loop
Let us start with a simple parallel for loop. The following is a code to convert a 32-bit Color (RGBA) image to 8-bit Grayscale image

// pDest is an unsigned char array of size width * height
// pSrc is an unsigned char array of size width * height * 4 (32-bit)
// To avoid floating point operation, all floating point weights in the original
// grayscale formula
// has been changed to integer approximation

// Use pragma for to make a parallel for loop
omp_set_num_threads(threads);

#pragma omp parallel for
for(int z = 0; z < height*width; z++)
{
pDest[z] = (pSrc[z*4+0]*3735 + pSrc[z*4 + 1]*19234+ pSrc[z*4+ 2]*9797)>>15;
}



The #pragma omp parallel for directive will parallelize the for loop according to the number of threads set. The following is the performance gained for a 3264x2488 image on a 1.66GHz Core Duo system (2 Cores).

Thread(s) : 1 Time 0.04081 sec
Thread(s) : 2 Time 0.01906 sec
Thread(s) : 4 Time 0.01940 sec
Thread(s) : 6 Time 0.02133 sec
Thread(s) : 8 Time 0.02029 sec

As you can see, by executing the problem using 2 threads on a dual-core CPU, the time has been cut by half. However, as the number of threads is increased, the performance does not due to increased time to fork and join.


Parallel double for loop
The same problem above (converting color to grayscale) can also be written in a double for loop way. This can be written like this;

Collapse Copy Code

for(int y = 0; y < height; y++)
for(int x = 0; x< width; x++)
pDest[x+y*width] = (pSrc[x*4 + y*4*width + 0]*3735 + pSrc[x*4 + y*4*width + 1]*19234+ pSrc[x*4 + y*4*width + 2]*9797)>>15;


In this case, there are two solutions;
Solution 1

We have made the inner loop parallel using the parallel for directive. As you can see, when 2 threads are being used, the execution time has actually increased! This is because, for every iteration of y, a fork-join operation is performed, and at the end contributed to the increased execution time.
Collapse Copy Code

for(int y = 0; y < height; y++)
{
#pragma omp parallel for
for(int x = 0; x< width; x++)
{
pDest[x+y*width] = (pSrc[x*4 + y*4*width + 0]*3735 + pSrc[x*4 + y*4*width + 1]*19234+ pSrc[x*4 + y*4*width + 2]*9797)>>15;
}
}


Thread(s) : 1 Time 0.04260 sec
Thread(s) : 2 Time 0.05171 sec

Solution 2

Instead of making the inner loop parallel, the outer loop is the better choice. Here, another directive is introduced - the private directive. The private clause directs the compiler to make variables private so multiple copies of a variable does not execute. Here, you can see that the execution time did indeed get reduced by half.

int x = 0;
#pragma omp parallel for private(x)
for(int y = 0; y < height; y++)
{
for(x = 0; x< width; x++)
{
pDest[x+y*width] = (pSrc[x*4 + y*4*width + 0]*3735 + pSrc[x*4 + y*4*width + 1]*19234+ pSrc[x*4 + y*4*width + 2]*9797)>>15;
}
}


Thread(s) : 1 Time 0.04039 sec
Thread(s) : 2 Time 0.02020 sec


Get to know the number of threads
At any time, we can obtain the number of OpenMP threads running by calling the function int omp_get_thread_num(); .



Summary
By using OpenMP, you can gain performance on multi-core systems for free, without much coding other than a line or too. There is no excuse not to use OpenMP. The benefits are there, and the coding is simple.

There are several other OpenMP directives such as firstprivate, critical sections and reductions which are left for another article. :)

Points of Interest
You can also perform similar operation using Windows threads, but it will be more difficult to implement, because of issues such as synchronization and thread management.

Additional Notes
The original RGB to Grayscale formula is given as Y = 0.299 R + 0.587 G + 0.114 B.

References
Parallel Programming in C with MPI and OpenMP (1st ed.) , Michael J. Quinn, McGraw Hill, 2004
Scientific Parallel Computing, L. Ridgway Scott, Terry Clark, Babak Bagheri, Princeton University Press, 2005
Introduction to Parallel Computing: A Practical Guide with Examples in An W.P. Petersen, P. Arbenz, Oxford University Press, 2004.
Digital Color Imaging Handbook, Gaurav Sharma, CRC Press, 2003

[omp] 예제

Matrix Multiplication 예제
Parallel program에서 많이 사용하는 matrix multiplication을 예제로 들어 openMP에 대한 간단한 설명을 하고자 한다.

[시작하기 전에]
openMP 프로그램의 순서는 먼저 parallelism을 형성하고 작업을 분배하는 형식이 된다.

Parallelism 형성
(MPI에서 process 여러 개 만드는 것과 같은 작업이라고 생각하면 된다)
#pragma omp parallel [clauses](clauses에 대해서는 manual이나 spec을 참조하기 바란다.)
이 구문을 사용하면 master thread는 정해진 thread 개수만큼 thread를 생성하여 아래 그림과 같은 parallel region을 형성한다. 그리고 나서 작업이 끝나면 모두 join하고 프로그램의 수행을 끝낸다.

작업 분배
이렇게 하여 형성된 parallel region에 원하는 작업을 분배한다. 작업을 분배하는 방법은 크게 두 가지로 나눌 수 있는데 ‘for’과 ‘section’이 있다.
1) #pragma omp for [clauses]이것은 data parallelism을 사용하는 것으로 이 구문으로 시작되는 부분을 모든 thread가 골고루 나누어 수행한다. 같은 작업을 여러 thread가 data를 나누어 수행하길 원할 때 사용한다. (100개의 배열을 10개의 thread가 10개씩 수행하도록 하고자 할 때)
2) #pragma omp section [clauses]이것은 functional parallelism을 사용하는 것으로 이 구문으로 시작되는 부분을 한 thread가 수행한다. Section을 여러 개 형성하고 각각을 한 개의 thread에게 분배하고 싶을 때 사용한다.

[Matrix Multiplication]
12*12 matrix A와 B를 곱하는 프로그램을 작성하였다.

간단하게 설명하면 다음과 같다.
4개의 thread를 사용하여 A의 row를 3개씩 분배한다.
각 thread는 A의 3개의 row와 B의 12개의 col을 사용하여 연산을 수행한다.
(Thread 0 은 A의 0,1,2번 row와 B의 0,1,2,..11번 col을 가지고 연산을 수행하고,
Thread 1은 A의 3,4,5번 row와 B의 0,1,2,..11번 col으로 연산을 수행한다.)
Thread가 전부 다 작업을 수행하면 matrix multiplication이 끝나고 결과를 출력한다.

#include
#include

#define NRA 12
#define NCA 12
#define NCB 12
// 12*12 matrix를 선언하기 위한 것으로 A의 col수와 row수는 12, B의 col 수는 12이고 row수는 A의 col수와 같아진다.

void main()
{
// 변수 선언
int tid,nthreads,i,j,k,chunk; // tid는 thread id, nthreads는 전체 thread의 수이다.
double a[NRA][NCA],b[NCA][NCB],c[NRA][NCB]
chunk=3; // chunk에 대해서는 나중에 설명하도록 한다.

//이 부분이 parallelism을 형성하는 부분이다. Shared()는 () 안의 변수들을 모든 thread가 share한다는 의미이고, private()는 ()안의 변수들이 thread마다 private임을 의미한다.
#pragma omp parallel shared(a,b,c,nthreads,chunk)private(tid,i,j,k)
{// 여기서부터 각자 thread들의 수행 code가 시작되는 것이다.

tid=omp_get_thread_num();
// 이렇게 해서 자신의 tid값을 알아낸다.

//그리고 master thread는 전체 thread의 개수를 출력한다.
if(tid==0)
{
nthreads=omp_get_num_threads();
// 이렇게 해서 현재 생성된 thread의 개수를 알 수 있다.

printf("Starting Matrix Multiplication with %d threads\n",nthreads);
}

// Matrix Initialization
// matrix의 초기화도 한 thread가 다 하기보다는 나눠서 해보자. Matrix를 초기화하고 싶은데, thread가 4개 생성된다고 가정하고 12*12 matrix니까 3*12만큼 나눠서 초기화를 시키는 게 좋겠다. Matirix a,b,c를 모두 다 그런 방식으로 해보도록 하자.
// 그렇다면 12*12를 4개의 thread에 어떻게 나눠줄까? 여기서 사용하는 것이 chunk이다. Chunk=3으로 앞에서 선언했고, 그 chunk에 따라 thread는 각자 data를 3개까지만 처리한다. 그렇다면 어떤 문법을 사용해서 이게 가능할까?
// 아래 진한 글씨로 된 부분을 보자. Matrix a를 초기화시키기 위한 부분인데, 첫 번째 for문에 대해서 i가 0부터 NRA까지 수행을 하는데 이 부분을 chunk만큼 thread가 나눠서 수행하는 것이다. 지금 현재 i는 12번 수행되어야 하니까 data size가 12라면, chunk가 3이니까 thread 하나당 3개씩 맡아서 수행하는 것이다.

// 옵션으로(이건 성능상 고려해야 할 프로그래밍 문법인 것 같다) static이면 chunk=3이니까 thread0,1,2,3이 data를 3개씩 나눠서 수행하고, dynamic이면 사정이 되는 녀석이 빨리 일을 해버린다. 원래는 thread 1이 해야 되는 부분인데 thread 0 이 한가하면 미리 남의 것까지 해버린다는 것이다.

#pragma omp for schedule(static,chunk)
for(i=0;i for(j=0;j a[i][j]=i+j;
#pragma omp for schedule(static,chunk)
for(i=0;i for(j=0;j b[i][j]=i*j;
#pragma omp for schedule(static,chunk)
for(i=0;i for(j=0;j c[i][j]=0;

// 자, 이제 초기화가 끝났으니 matrix의 곱셈을 해야 할 때이다.
// 곱셈도 당연히 나누어서 해야 하겠다. 12*12 matrix니까 앞에서 이야기했던 것처럼 row만 3개씩 분담해서 하도록 해보자. Thread는 4개 생성했으니까, 3개씩 나누면 되니까. Chunk=3이 되면 간단히 해결할 수 있게 된다.
// 이건 앞에서도 나왔던 문법이다. Chunk=3이니까 data를 사이 좋게 3개씩 나눠 갖고,
행렬 곱을 수행하게 된다.
#pragma omp for schedule(static,chunk)
for(i=0;i {
printf("thread %d did row %d\n",tid,i);
for(j=0;j for(k=0;k c[i][j]+=a[i][k]*b[k][j];
}
}

// 이제 출력만 남았다.
printf("------------------------------------------------\n");
printf("Result Matrix:\n");
for(i=0;i {
for(j=0;j printf("%6.2f ",c[i][j]);
printf("\n");
}
printf("------------------------------------------------\n");
printf("Done\n");
}

[stl] string tokenizer


stringTokenizer.cpp


#include "stdafx.h"

#include "StringTokenizer.h"


stringTokenizer::stringTokenizer(const string& inputstring, const string& seperator): _input(inputstring), _delimiter(seperator){ split();}
size_t stringTokenizer::countTokens(){ return token.size();}
bool stringTokenizer::hasMoreTokens(){ return index!=token.end();}
string stringTokenizer::nextToken(){ if(index!= token.end()) return *(index++); else return "";}
void stringTokenizer::split(){ string::size_type lastPos = _input.find_first_not_of(_delimiter, 0); //구분자가 나타나지 않는 위치 string::size_type Pos = _input.find_first_of(_delimiter, lastPos); //구분자가 나타나는 위치
while(string::npos!=Pos || string::npos!=lastPos) { token.push_back(_input.substr(lastPos, Pos-lastPos)); lastPos = _input.find_first_not_of(_delimiter, Pos); //구분자가 나타나지 않는 위치 Pos = _input.find_first_of(_delimiter, lastPos); //구분자가 나타나는 위치 }
index = token.begin();}
stringTokenizer.h
#ifndef stringTokenize_h#define stringTokenize_h#pragma once
#include #include
using namespace std;
class stringTokenizer{public: stringTokenizer(const string& inputstring, const string& seperator); virtual ~stringTokenizer() {};
private: string _input; string _delimiter; vector token; vector::iterator index;
public: size_t countTokens(); //token 갯수 bool hasMoreTokens(); //token 존재 확인 string nextToken(); //다음 token void split(); //string을 seperator로 나눠서 vector에 저장};
#endif
example 파일내 문자열을 분리시켜 그 데이터를 메모리에 가지고 있도록 하기위한 예제코드

mapReader.h
#pragma once
#include #include using namespace std;
class mapReader{public: mapReader(const char* filepath); virtual ~mapReader(void);
public: void readFile(const char* filepath);
public: vector> mapData;};
mapReader.cpp
#include "StdAfx.h"#include "mapReader.h"#include "StringTokenizer.h"
#include
mapReader::mapReader(const char* filepath){ readFile(filepath);}
mapReader::~mapReader(void){}
#define MAX_STRING_LEN 1024void mapReader::readFile(const char* filepath){ ifstream file; char tmpstring[MAX_STRING_LEN]; vector data; file.open(filepath); if(file.is_open()) { while(file.getline(tmpstring, MAX_STRING_LEN)) { stringTokenizer *pstringTokenizer = new stringTokenizer(tmpstring, " \t\n"); memset(tmpstring, 0, sizeof(tmpstring)); data.clear();
while(pstringTokenizer->hasMoreTokens()) { data.push_back(pstringTokenizer->nextToken()); } if(data.size()!=0) mapData.push_back(data); delete pstringTokenizer; } }
return;}
sample.txt
1 안녕하세요.2 반갑습니다.3 안녕히가세요위 sample.txt파일을 파싱하여 그 정보를 mapData라는 곳에 저장하려면,mapReader *pReader = new mapReader("sample.txt");이런식으로 파일위치만 생성인자로 넘겨주면 그 정보를 mapReader의 클래스의 mapData가 파싱된 정보를 가지고 있는것이다

[mfc] 소켓 프로그래밍 주의점

[MFC 소켓 프로그래밍시의 일반적인 주의점]

*) CSocket을 가능하면(절대라고 표현해도 될 정도죠) 사용하지 마세요.
- CSocket을 이용하여 프로그램 하시는 분이 있다면 존경받아 마땅한 분이죠...
왜냐면 CSocket은 Blocked Call이기 때문에 제대로 돌아가려면 Thread를 이용한 코딩이 되어야 하는데 거의 웬만한 경우에 배보다 배꼽이 크게 만듭니다...
옛날에는 유닉스 기반한 Telnet서버 등에서 이런 식의 Blocked Call을 Thread와 같이 사용했는데 실제적으론 문제가 많습니다. 가장 큰 문제는 시스템 리소스를 무지하게 잡아먹는다는 거죠
메모리 사용보다 가장 큰 문제는 Thread switching 오버헤드와 Thread 갯수 제한이죠...
1000명의 사용자가 접속하는 Telnet서버를 만들기 위해선 최소 1000개의 Thread를 만들어야 하는데 안되죠
1000개 Thread를 무리없이 돌릴 수 있는 프로그램이 몇 개나 될까요?
된다 치더라도 만약 각 Thread간의 Data Sync가 필요한 상황이 된다면 어찌될까요? 끔찍하겠죠?
- 이야기를 바꾸면 가능하면 Thread를 이용한 통신을 하지 말라는 말과도 같습니다... Thread는 필수일 경우에만 쓰시면 됩니다.
- 실제로 Thread를 이용한 통신을 하게 되더라도 결국엔 OS나 시스템 성능 때문에 대형 시스템에서 조차 ....
- Thread하나에 여러 개의 접속을 Async로 처리하게 될 수 있습니다. -> IOCP - 시스템의 성능을 한계까지 발휘했을 때 모든 처리를 각각의 Thread기반으로 처리했을 때와 한 개의 Thread에서 Async로 처리했을 때 당연 후자가 시스템을 가장 제대로 활용하게 될 수 밖에 없습니다.
*) CAsyncSocket 을 그대로 사용하지 마세요.
- Non Blocked Call이긴 하지만 그냥 사용해서 프로그램을 작성하면 끔찍한 프로그램 밖엔 만들 수가 없습니다.
일반적으로 CAsyncSocket을 사용하시는 분들의 특징이 데이터 수신은 받고 싶은 만큼만 받으면 되니 문제가 없는데 데이터를 송신할 때 문제를 야기시킵니다. 보내고 싶은 데이터를 그때 그때 바로 보내려고 시도하는데 문제는 OSI 세븐 레이어인지 하는 계통을 따라 데이터를 보내게 되는 통신 과정상 어디선가는 인터널 버퍼 오류 등의 여러가지 상황이 발생할 수 있습니다. 이때마다 .Send함수에 대한 예외 처리(혹은 그자리서 무식하게 메시지 루프를 돌면서 데이터가 전부 전달될 때까지 배째기)를 해야 하는데 프로그램이 커지면 수십 군데의 오류처리부가 존재하게 됩니다.
결국 무지하게 지저분한 코드가 될 수 밖에 없죠...
한 개의 함수 안에서 Receive와 Send가 번갈아 서너번 처리되어야 한다고 생각해보죠... 이중에 어디서 오류가 날지 누가 알겠습니까?
결국 개발자는 모든 Receive와 Send에 대하여 오류처리를 해야하죠.. 지금껏 정상이라고 믿고 작업했던 내용도 되돌려야 하죠
하지만 깊게 생각하셔야 할게 있습니다. .Send함수가 성공을 리턴했다고
상대방 프로그램이 그 데이터를 제대로 받았을까요? 혹은 받아서 처리하다가 죽었을 수도 있죠? 혹은 제대로 처리해서 결과를 리턴하는 도중에 인터넷 라인이 끊어지는 경우도 있을 수 있죠?
그런데도 수 십 개의 .Send함수 호출 바로 뒤에서 오류처리를 하실 겁니까?
진짜로 오류가 있는지 없는지는 내가 보낸 데이터에 대한 결과가 도착하기 전에는 알 수가 없는 거죠.
- 뒷부분에 자세한 이야기를....힌트를 말씀드리면 서로 신뢰할수 있는 프로토콜을 만들어야 합니다. 즉, 데이터를 수신한 곳에서 수신한 데이터가 올바른 것인지 판단해서 만약 잘못 받았으면 다시 보내달라고 해야합니다. 즉, 소켓오류로 처리하면 안됩니다. (제 경험에 의해서 드리는 말씀입니다.)
*) 가능하면 BSD소켓 API만을 사용하여 코딩하십시오...
- 뭐 여러가지 구질구질하게 이야기할 것 없이. BSD API만을 이용해서도 원하는 모든 것을 할 수 있습니다. (특히 CrossPlatform이 가능해 집니다. 짱!!!)
윈도우에서 잘 돌아가던 프로그램(예를 들어 ActiveX)을 유닉스(CGI프로그램으로 변경하여)에서 실행해야 한다고 생각해보죠.
MS의 API Extension은 꼭 필요한 경우에만 쓰시면 좋을 듯 싶군요. 예를 들어 오버랩드 IO정도 겠네요.(진짜 강력한 Winsock API입니다)
실제로 저같은 경우에 windows/free BSD/linux/solaris 등등에서 돌아가는 프로그램을 작성한 기억이 있는데 애초에 거의 BSD API만을
이용해 짰는데도 미묘한 API차이 때문에 고생을 많이 했습니다.
개인적으로 최소한 통신 API만큼은 MS API Extension들이 잘만들었다곤 생각지 않습니다.
- 윈도우즈 환경에서 기본적으로 사용해야 하는 MS구현 함수들
WSAStartup/WSACleanup/WSAGetLastError
- 오버랩드 IO를 사용할 때 써야하는 MS제공 함수들...
WSASocket(==socket)/WSAConnect(==connect)/WSAAccept(==accept)/WSASend(==send)
- 일반적인 BSD소켓 API함수들...
socket/connect/accept/send/recv/bind/listen/select/setsockopt/shutdown/closesocket
gethostbyname/inet_addr/inet_ntoa/ntohs/htons/FD_... 등의 utility함수 혹은 매크로...
- IOCTL함수 구현 - Win32(ioctl)/Unix계열(fcntl)

팔로어

프로필

평범한 모습 평범함을 즐기는 평범하게 사는 사람입니다.