为什么共享内存段在传输大数据时比管道运行时间更长?

问题描述 投票:0回答:1

我正在写一个关于操作系统的实验。您需要编写两个使用管道和共享内存段传输数据的程序。有必要比较传输时间并得出结论:共享内存段在处理大数据时工作得更快。但事实恰恰相反。我的代码有什么问题?我究竟做错了什么? 我计算工作时间为阅读结束减去写作开始

UPD:我听了评论并更改了代码,添加了新结果

代码管:

#define _GNU_SOURCE

#include <sys/time.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <wait.h>
#include <math.h>
#include <stdlib.h>
#include <fcntl.h>

#define COUNT 6

int main()
{
    // fd[0]=READ, fd[1]=WRITE
    int fd[2], nread, pid, status, base = 1;
    for (int i = 0; i < COUNT; i++)
    {
        int size = 8*base; // bytes
        base *= 16;
        pipe(fd);
        fcntl(fd[1], F_SETPIPE_SZ, size);
        fcntl(fd[0], F_SETPIPE_SZ, size);
        struct timespec t1, t2;
        pid = fork();
        
        if (pid == 0)
        {
            // READ
            int tmp_size = 0;           
            close(fd[1]);               
            char *buf = (char *)malloc((size) * sizeof(char));
            while((nread = read(fd[0], buf, size)) != 0);

            free(buf);
            exit(1);    
        }
        else
        {
            // WRITE
            close(fd[0]);   
            clock_gettime(CLOCK_REALTIME, &t1);
        
            char *buf = (char *)malloc((size) * sizeof(char));
            for (int j = 0; j < size; j++)
            {
                *(buf+j) = '1';
            }
            write(fd[1], buf, size);
            
            close(fd[1]);
            free(buf);
        }
        wait(&status);
        clock_gettime(CLOCK_REALTIME, &t2);
        FILE *file = i == 0? fopen("program1_result", "w"): fopen("program1_result", "a+");
        char cur_time[150];
        sprintf(cur_time, "%ld nsec\n", 1000000000*(t2.tv_sec - t1.tv_sec) + (t2.tv_nsec-t1.tv_nsec));
        if (file != NULL) fputs(cur_time, file);
        fclose(file);

    }
}

代码共享内存:

#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/sem.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <wait.h>

#define COUNT 6

int main()
{
    int pid, status, shmid, sem, base = 1;
    char *shmPtr;
    key_t shm_keys[COUNT], sem_keys[COUNT];
    for (int i = 0; i < COUNT; i++)
    {
        shm_keys[i] = ftok(".", (char)i);
        sem_keys[i] = ftok(".", 'a' + i);
    }
    
    for (int i = 0; i < COUNT; i++)
    {
        int size = 8*base;      
        base *= 16;
        if ((shmid = shmget(shm_keys[i], size + 1, IPC_CREAT | 0666)) == -1) exit(1);
        shmPtr = shmat(shmid, 0, 0);
        
        sem = semget(sem_keys[i], 1, IPC_CREAT | 0666);
        //struct timeval s, e;
        struct timespec t1, t2;
        pid = fork();

        if (pid == 0)
        {
            while(semctl(sem, 0, GETVAL) != 1);
            char *str = (char *)malloc(size * sizeof(char));
            strcpy(str, shmPtr);
            
            free(str);
            exit(1);
        }
        else
        {           
            clock_gettime(CLOCK_REALTIME, &t1);
            char *str = (char *)malloc((size+1) * sizeof(char));
            for (int j = 0; j < size; j++)
            {
                *(str+j) = '1';
            }
            *(str + size) = '\0';
            strcpy(shmPtr, str);
            
            semctl(sem, 0, SETVAL, 1);
            free(str);          
        }   
        wait(&status);
        clock_gettime(CLOCK_REALTIME, &t2);
        FILE *file = i == 0? fopen("program2_result", "w"): fopen("program2_result", "a+");
        char cur_time[150];
        sprintf(cur_time, "%ld nsec\n", 1000000000*(t2.tv_sec - t1.tv_sec) + (t2.tv_nsec-t1.tv_nsec));
        if (file != NULL) fputs(cur_time, file);
        fclose(file);
        
        shmdt(shmPtr);
        shmctl(shmid, IPC_RMID, NULL);
        semctl(sem, 0, IPC_RMID);
    }
    
    return 0;
}   

结果管:

  • 510134 纳秒(8 字节)
  • 751695 纳秒(128 字节)
  • 317859 纳秒(2048 字节)
  • 372219 纳秒(32768 字节)
  • 2158510 纳秒(524288 字节)
  • 19722241 纳秒(8388608 字节)

结果共享内存:

  • 463168 纳秒(8 字节)
  • 369917 纳秒(128 字节)
  • 307799 纳秒(2048 字节)
  • 361162 纳秒(32768 字节)
  • 2019749 纳秒(524288 字节)
  • 24943196 纳秒(8388608 字节)
New result:
|bytes    | pipe (nsec) | shm (nsec)|
|---------|-------------|-----------|
| 8       |   517516    |  489703   |
| 128     |   205485    |  440699   |
| 2048    |   374170    |  1162227  |
| 32768   |   584830    |  548490   |
| 524288  |   3162177   |  4010005  |
| 8388608 |   50293808  |  67142116 |

请帮助理解我做错了什么。

c ipc
1个回答
0
投票

有必要比较传输时间并得出共享内存段在处理大数据时工作速度更快的结论。

这就是所谓的“回避问题”——假设结论是正确的而不证明它(然后修改测试以迫使假设稍后成立)。

但事实恰恰相反。我的代码有什么问题?

您的代码没有任何问题。有时未经证实的假设是错误的假设,有时强迫测试与未经证实的假设相匹配是自欺欺人的基础(即使是学生强迫测试与老师想要的相匹配,从而导致老师的自欺欺人)。请注意,“我的老师是正确的”是另一个未经证实的假设,可能是(而且经常是)错误的。

对于您的具体代码:

  • 理论上;读取/写入管道时,内核将数据复制到内核空间中的缓冲区或从缓冲区复制数据的成本与您自己将数据复制到共享内存或从共享内存复制数据的成本大致相同。但是,共享内存会增加您使用的虚拟内存量,从而增加

    fork()
    的成本,并且内核用作管道缓冲区的底层内存不受用户空间中“转换更改”的影响,并且会产生更少的 TLB 未命中;因此,由于这些原因,您会期望管道更好。

  • 这两种情况的同步基本相同。例如。阻塞等待数据到达管道,然后在数据到达时被唤醒的进程本质上与阻塞等待信号量并在可以获取信号量时被唤醒的进程相同。这包括相同数量的系统调用和“用户 -> 内核 -> 用户”转换。

  • 没有其他“非表面”差异。

换句话说,您应该预料到共享内存会更慢。

您可以通过将

fork()
移出循环并只执行一次来更改此设置;就像
pid = fork(); if (pid == 0) { for (int i = 0; i < COUNT; i++) { ... } else /* pid != 0 */ { for (int i = 0; i < COUNT; i++) { ... } }
。这将减少使共享内存变得更糟的内存管理差异。然而,这一更改也将改变同步,并可能允许管道执行“一个进程发送,而另一个进程并行读取”(而不是纯粹的“只有一个进程实际上可以在任何时间做任何事情,根本没有并行性”模式) )并由于并行性而导致管道比共享内存快得多。

修复并行度差异;您可以在共享内存中实现自己的 FIFO 队列,以便可以将较新的数据放入队列,同时从队列中取出较旧的数据。如果你这样做,你将重新发明管道,并且应该充分强化自然/预期的“完全相同的工作,具有不相关的表面差异=完全相同的性能”关系。

当然“共享内存更快”并不是一个可以期待的选项。它建立在一个神话之上,涉及避免内核系统调用,包括避免需要告诉内核“我必须等待数据到达,所以阻止我”,并避免需要告诉内核“其他进程需要停止等待并解除阻塞”因为数据到达了”。要人为地制造共享内存更好的神话,您需要将调度程序移至用户空间;或者将调度程序移至用户空间。或者引入额外的后台工作,以便进程执行其他有用的工作而不是阻塞。这意味着投票;就像“

while(true) { do_some_other_work(); if( data_received() ) { handle_received_data(); } }
”;其中通信从不涉及内核。然而,为了公平的测试,您必须对管道执行相同的操作,并且“不涉及内核的管道”与“共享内存中的 FIFO 队列”的代码完全相同;所以我们永远无法进行任何类型的公平比较,其中共享内存能够具有更高的性能。

© www.soinside.com 2019 - 2024. All rights reserved.