0

我们有一个服务器应用程序,它将文件从 clientA 中继到 clientB、clientC、clientD 等。我们将这种文件中继称为任务。如果有很多任务正在执行,那么 CPU 使用率会非常非常高。

我想知道并发执行多个任务时这种高CPU使用率现象是否正常。有什么方法可以减少这种应用程序中的 CPU 使用率?

      //pseudo code
     void service(void){
          while(1){
               ....
               struct timeval timeout;
               timeout.tv_sec = 3;

               ...
               ret = select(maxFd+1, &read_set, NULL, NULL, &timeout);
               if (ret > 0){
                   //get socket from SocketsMap
                   //if fd in SocketsMap and its being set
                   //then receive data from the socket
                   **all fd are in non-blocking mode**
                   receive_data(fd);
               }
          }
     } 

     void receive_data(int fd){
          const int ONE_MEGA = 1024 * 1024;
          char *buffer = new char[ONE_MEGA]; 
          int readn = recv(fd, buffer, ONE_MEGA, 0);

          //handle the data: many steps
          char* DataToProcess = buffer;
          int LenToProcess = readn;
          while(LenToProcess > 0){
              1. scan the data to find the packet header
              2. get the length from the packet then perform checksum 
                 function which will scan every character of the packet 
                 to get a checksum value.
              3. if the packet is valid then add the packet to data queue. 
                 Move the buffer pointer and process the remaining data.
              ......
              LenToProcess -= DataProcessed;
              DataToProcess += DataProcessed; 
          };
     }

如您所见,receive_data() 中的所有三个步骤都是 cpu 密集型操作。有什么方法可以在此类操作中尽可能减少 CPU 使用率(除了这种方式:设置一个非常小的缓冲区大小,例如“char buffer[1024]”)?

这里的问题是我们的应用程序将与同一台机器上的另一个服务器应用程序一起运行,因此 FileRelayer 应用程序不能消耗过多的 cpu,否则其他服务器应用程序将无法正常工作--!

[更新]
以下是有关该应用程序的一些信息:
A. 在这个 FileServer 多线程服务器应用程序中大约有 70 个线程,但其中只有一个用于从所有套接字接收数据。
B. 所有套接字都处于非阻塞模式,包括监听套接字。
C. 当应用程序从 4 个客户端(4 个套接字)接收 4 个 200 Mega 的文件时,发现 CPU 使用率很高(80% - 90%)。

关于问题:
我们将整个接收流分为两个主要部分,我们称它们为FlowA和FlowB。FlowA 只接收来自套接字的数据。FlowB代表receive_data()中处理数据的部分,如数据包切片等。我们发现FlowA和FlowB分别会导致CPU使用率高。

1)FlowA:从堆栈分配的大数组(1 Mega),由这篇文章描述。在我们的测试中,我们只留下 FlowA(从套接字接收数据后丢弃),发现 CPU 使用率长时间保持在 80-90% 的高位。并且将“char Buffer[ONE_MEGA]”替换为“char *buffer = new char[ONE_MEGA]”,CPU 使用率降低到 14%。
2) FlowA + FlowB:我们解决FlowA的问题后,发现整个流程(FlowA + FlowB)的CPU使用率仍然高达80%,但这次有所波动。

将接收缓冲区设置为非常小的缓冲区,例如 char buffer[1024] 会大大降低 cpu 使用率,因为每个函数调用它只会处理一两个数据包,但我们担心传输速度也会降低。那么有没有其他方法可以解决这个问题呢?</p>

4

3 回答 3

0

对于 TCP 套接字功能receive_data可能无法正常工作。

它分配了一个新的本地缓冲区这一事实表明,该缓冲区在函数返回时被破坏。这意味着receive_data无法处理不完整的消息。

正确的做法是为每个套接字分配一次缓冲区。从套接字读取到该缓冲区,然后处理并丢弃缓冲区前面的完整消息。消费完所有完整消息后,将包含不完整消息的缓冲区尾部移动到前面,并且在下次套接字准备好读取时,将新字节附加到不完整消息的末尾,直到它完成。

于 2013-07-29T13:54:59.213 回答
0

是的。CPU 不应该做太多工作。你真正在做的唯一一件事就是复制字节,这是不必要的

于 2013-07-29T14:35:56.800 回答
0

为了用缓存来说明这个例子,我回答了关于类似主题的上一个问题,并添加了一段校验和代码:

#include <iostream>
#include <cstdio>

using namespace std;

static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}

const int M = 1024*1024;
const int S = 8*1024;

void bigstack()
{
    FILE *f = fopen("test.txt", "r");
    unsigned long long time;
    time = rdtsc();
    char buffer[M];

    fread(buffer, M, 1, f);
    int csum = 0;
    for(char i : buffer)
    {
    csum += i;
    }
    time = rdtsc() - time;
    fclose(f);
    cout << "bs: Time = " << time / 1000 << " csum=" << csum << endl;
}


void bigheap()
{
    FILE *f = fopen("test.txt", "r");
    unsigned long long time;
    time = rdtsc();
    char *buffer = new char[M];

    fread(buffer, M, 1, f);
    int csum = 0;
    for(int i = 0; i < M; i++)
    {
    csum += buffer[i];
    }
    delete [] buffer;
    time = rdtsc() - time;
    fclose(f);
    cout << "bh: Time = " << time / 1000 << " csum=" << csum << endl;
}


void smallstack()
{
    FILE *f = fopen("test.txt", "r");
    unsigned long long time;
    time = rdtsc();
    char buffer[S];
    int toread = M;

    int csum = 0;
    while(toread > 0)
    {
    fread(buffer, S, 1, f);
    for(char i : buffer)
    {
        csum += i;
    }
    toread -= S;
    }
    time = rdtsc() - time;
    fclose(f);
    cout << "ss: Time = " << time / 1000 << " csum=" << csum << endl;
}


int main()
{
    for(int i = 0; i < 10; i++)
    {
    bigstack();
    bigheap();
    smallstack();
    }
}

因此,现在代码正在将数据读入 CPU,然后遍历所有内容。完成大区块所需的时间比小区块高出约 16%。如下所示,大块的时间约为 1400 个时间单位,而较小的块大小,即使它fread多次调用,也约为 1200 个时间单位。

这是输出的缩减版本:

bs: Time = 1417 csum=89411462
bh: Time = 1428 csum=89411462
ss: Time = 1208 csum=89411462
bs: Time = 1444 csum=89411462
bh: Time = 1415 csum=89411462
ss: Time = 1205 csum=89411462
bs: Time = 1463 csum=89411462
bh: Time = 1409 csum=89411462
ss: Time = 1262 csum=89411462
bs: Time = 1418 csum=89411462
bh: Time = 1441 csum=89411462
ss: Time = 1213 csum=89411462

这样做的原因是,大块将与其他数据项“争斗”更多以适应 CPU 缓存,因此速度较慢。

于 2013-07-29T14:52:55.747 回答