select和poll之间的性能对比

poll 通过 revents 字段直接标记事件,而 select 需要遍历三个独立的位图。这种设计差异直接导致了性能上的差距。以下是详细对比:


1. poll 的事件检测:单次遍历 + 直接标记

(1)pollfd 结构体

cpp 复制代码
struct pollfd {
    int fd;         // 文件描述符
    short events;   // 用户关心的事件(如 POLLIN、POLLOUT)
    short revents;  // 内核填充的实际发生的事件
};
  • events :用户设置的监听事件掩码(如 POLLIN | POLLHUP)。
  • revents :由内核填充,表示该 fd 上实际发生的事件(如 POLLIN | POLLERR)。

(2)内核处理逻辑

  1. 用户调用

    cpp 复制代码
    struct pollfd fds[1000];
    for (int i = 0; i < 1000; i++) {
        fds[i].fd = ...;
        fds[i].events = POLLIN; // 只关心可读事件
    }
    poll(fds, 1000, timeout);
  2. 内核行为

    • 单次遍历 :内核直接遍历 fds 数组(1000 次)。
    • 直接标记
      • 对每个 fds[i],内核检查 fd 的状态。
      • 如果事件发生(如可读),则将 revents 设置为对应标志(如 POLLIN)。
      • 无需额外遍历或位操作
  3. 用户检查

    cpp 复制代码
    for (int i = 0; i < 1000; i++) {
        if (fds[i].revents & POLLIN) {
            // fd 可读
        }
    }

(3)优势

  • 高效 :内核只需遍历一次数组,且直接填充 revents,无冗余操作。
  • 灵活revents 可以同时标记多个事件(如 POLLIN | POLLHUP)。

2. select 的事件检测:三次遍历 + 位图操作

(1)select 的参数

cpp 复制代码
int select(
    int nfds,          // 最大 fd + 1
    fd_set *readfds,   // 可读事件位图
    fd_set *writefds,  // 可写事件位图
    fd_set *exceptfds, // 异常事件位图
    struct timeval *timeout
);
  • 三个位图:分别对应读、写、异常事件。
  • nfds :强制内核遍历 0nfds-1 的所有 fd,即使未被监听。

(2)内核处理逻辑

  1. 用户调用

    cpp 复制代码
    fd_set read_fds, write_fds;
    FD_ZERO(&read_fds);
    FD_ZERO(&write_fds);
    for (int fd = 3; fd <= 1002; fd++) {
        FD_SET(fd, &read_fds);  // 监听 1000 个 fd 的可读事件
    }
    select(1003, &read_fds, NULL, NULL, NULL);
  2. 内核行为

    • 拷贝位图 :将 read_fdswrite_fdsexcept_fds 从用户空间拷贝到内核。
    • 三次遍历
      1. 遍历 read_fds
        • 对每个 fd(从 01002):
          • 如果 FD_ISSET(fd, &read_fds) 为真,检查是否可读。
          • 如果可读,保留该 fd 在位图中(内核可能直接修改位图)。
      2. 遍历 write_fds(同理)。
      3. 遍历 except_fds(同理)。
    • 无效操作
      • 即使 fd=0,1,2 未被设置,内核仍会检查它们(共 3 次无效操作)。
      • 如果 fd 数量更多(如 10,000),无效操作会线性增加。
  3. 用户检查

    cpp 复制代码
    for (int fd = 3; fd <= 1002; fd++) {
        if (FD_ISSET(fd, &read_fds)) {
            // fd 可读
        }
    }

(3)劣势

  • 低效
    • 内核需要三次独立遍历(读、写、异常),且每次遍历都可能涉及全局扫描。
    • 每次遍历需通过 FD_ISSET 位操作检查 fd 是否被监听。
  • 局限性
    • FD_SETSIZE 限制(通常 1024),无法直接支持大量 fd。
    • 位图操作在 fd 稀疏时(如跳过前 1000 个 fd)浪费大量计算。

3. 直观对比:poll vs select 的事件检测

步骤 poll select
事件标记方式 revents 字段直接填充 三个独立位图(读、写、异常)
内核遍历次数 1 次(遍历 pollfd 数组) 3 次(遍历三个位图)
无效 fd 检查 无(仅检查用户提供的 fd) 有(必须检查 0nfds-1
位图操作 每次检查需 FD_ISSET 位操作
支持 fd 数量 无限制(仅受系统资源限制) FD_SETSIZE 限制(通常 1024)

4. 为什么 poll 更高效?

  1. 单次遍历 vs 三次遍历
    • poll 只需遍历用户提供的 fd 列表一次,而 select 需要遍历三个位图各一次。
  2. 直接标记 vs 位图操作
    • pollrevents 直接填充事件标志,无需位操作。
    • select 需通过 FD_ISSET 检查位图,额外开销大。
  3. 无效操作优化
    • poll 完全跳过未监听的 fd,而 select 必须检查所有 fd(即使未被监听)。

5. 代码示例对比

(1)poll 检测可读事件

cpp 复制代码
struct pollfd fds[1000];
for (int i = 0; i < 1000; i++) {
    fds[i].fd = 3 + i;
    fds[i].events = POLLIN;
}
poll(fds, 1000, timeout);

for (int i = 0; i < 1000; i++) {
    if (fds[i].revents & POLLIN) {
        printf("fd %d is readable\n", fds[i].fd);
    }
}

(2)select 检测可读事件

cpp 复制代码
fd_set read_fds;
FD_ZERO(&read_fds);
for (int fd = 3; fd <= 1002; fd++) {
    FD_SET(fd, &read_fds);
}
select(1003, &read_fds, NULL, NULL, timeout);

for (int fd = 3; fd <= 1002; fd++) {
    if (FD_ISSET(fd, &read_fds)) {
        printf("fd %d is readable\n", fd);
    }
}

6. 总结

  • pollrevents 设计
    通过单次遍历和直接标记事件,避免了 select 的三次遍历和位图操作,因此性能更高。
  • select 的局限性
    全局扫描和位图操作使其在处理大量 fd 时效率低下,且受 FD_SETSIZE 限制。
  • 适用场景
    • 高并发(大量 fd)时,优先使用 poll(或更现代的 epoll/kqueue)。
    • 低并发或遗留代码中,select 仍可能被使用,但性能较差。
相关推荐
大明者省43 分钟前
WSL2 Ubuntu22.04 GPU训练环境配置指南
人工智能·算法·计算机视觉
WWJA王文举1 小时前
I²C通信完整流程详解:START、地址、ACK、数据、Repeated START和STOP一次讲透
c语言·开发语言
zhanghaha13142 小时前
Python进阶教程:6_JSON 数据解析 —— 新手完全指南
开发语言·python·json
白狐_7982 小时前
408数据结构第8章:排序②——性质对比秒杀、场景选择与外部排序
java·数据结构·算法
zander2583 小时前
LeetCode 84:柱状图中的最大矩形——单调栈如何确定左右边界
java·数据结构·算法
Shell运维手记3 小时前
Linux 常用基础命令学习笔记
linux·运维·笔记·学习·算法·github
qq_448011163 小时前
C语言中的动态内存分配
c语言·开发语言·php
杨航 AI3 小时前
O(n log n):线性对数原理拆解 这个是排序算法的黄金复杂度之一。
算法·排序算法
船厂电气自动化ai大模型4 小时前
AI大模型与数学 第32课 函数凹凸性与二阶导数:拐点求解、凹凸区间计算(10道二阶导数计算题)
数据结构·人工智能·python·深度学习·算法
旖旎夜光4 小时前
LeetCode 3:无重复字符的最长子串(滑动窗口) —— 题解
数据结构·c++·算法·leetcode·滑动窗口