关于操作系统线程调度中,链表实现的讨论

关于操作系统线程调度中,链表实现的讨论


这里我们默认以下约定:

  • 链表是双向链表,带头节点
  • 考虑在进程调度时使用

实现

我们先列出两种算法的代码实现,再开始讨论。

第一种实现:

struct PCB {
    // info
};

struct Node {
    PCB *data;
    Node *nex;
    Node *pre;
};

const int max_pcb = 5;
PCB pcb_pool[max_pcb];

const int max_node = 5;
Node node_pool[max_node];

struct List {
    Node *head;
    // info
    Node *new_node() {
        for (Node *ret = node_pool; ret != node_pool + max_node; ++ret)
            if (ret->data == 0) return ret;
        return nullptr;
    }
    bool push_front(PCB *x) {
        Node *p = new_node();
        if (p == nullptr) return false;
        p->data = x;
        p->pre = head;
        p->nex = head->nex;
        head->nex = p;
        head->nex->pre = p;
        return true;
    }
};

第二种实现:

struct PCB {
    // info
    Node tag;
};

struct Node {
    Node *nex;
    Node *pre;
};

const int max_pcb = 5;
PCB pcb_pool[max_pcb];

PCB *tag_to_PCB(Node *p) { return (PCB *)((int)p - (int)&(((PCB *)0)->tag)); }

struct List {
    Node head;
    // info
    void push_front(PCB *x) {
        x->tag.pre = &head;
        x->tag.nex = head.nex;
        head.nex = &x->tag;
        head.nex->pre = &x->tag;
    }
    PCB *front() { return tag_to_PCB(head.nex); }
};

分析

首先,在操作系统的设计中,我们无法使用malloc()函数来辅助我们管理内存分配。所以我们需要在堆空间中,提前分配好足够的空间,然后使用自己的内存分配算法管理这些空间。

自然地,有朴素算法:

  • 提前申请数组空间
  • 分配时,遍历数组,找未分配的空间,打标记
  • 释放时,取消标记

时间复杂度 O(n)
空间复杂度 O(n)

可用循环队列优化标记数组,从而时间复杂度为 O(1)

然后,我们开始分析两种实现。
实现 1 是最朴素的带头节点的双向链表实现,节点分指针域和数据域两部分。
实现 2 是更优版实现(我们老师的写法),节点只包含指针域,节点本身的地址在数据域中(绕晕了的去看一下实现 2 的 PCB 声明)。

显然,实现 2 相比实现 1 有以下优点:

  • 避免了节点指针数组的开销
  • 不需要额外的节点指针数组,减少不必要的空间开销。
  • 不需要节点的内存管理,减少任务量
    • 不需要寻址算法new_node
    • 不需要考虑提前分配多少空间max_node给结点指针
      • 在多级反馈队列调度算法中,效果尤为显著
  • 操作链表时,不需要考虑节点的内存管理,只剩几个算术运算,加速
  • 链表中的所有节点都在数据域空间中,cache 命中概率大大提升
    • 考虑实现 1 的 p->nex->data,访问了两块内存,指针数组的内存和 PCB 数组的内存。
    • 考虑实现 2 的 tag_to_PCB(p->nex),访问的内存一直在 PCB 数组中。
  • tag_to_PCB() 拿算术运算多的时间,来换取 cache 命中率,从而进一步提升效率。
    • 函数通过结构体的存储特性来实现,通过算术运算从节点中获得地址,而不是实现 1 的访存。
    • 自然地,可以写成宏函数/内联函数,消除函数调用开销

总结

实现 2 是时、空、硬件均最优的写法,全方面吊打实现 1

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岚花落_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值