一、数据结构以及选择

  • 前面几篇文章主要介绍了Linux中比较重要的四种数据结构:链表、队列、映射和红黑树
  • 要是上述数据结构都不能满足你的需要,内核还实现了一些较少使用的数据结构,也许它们能帮你,比如基树(trie类型)和位图。只有当寻遍所有内核提供的数据结构都不能满足时,你 才需要自己设计数据结构。经常在独立的源文件中实现的一种常见数据结构是散列表。因为散列表无非是一些“桶”和一个散列函数,而且这个散列函数是针对每个用例的,因此用非泛型编程语言( C语言)实现内核范围内的统一散列表,其实这没有什么价值

数据结构的选择

  • 如果你对数据集合的主要操作是遍历数据,就使用链表。事实上没有数据结构可以提供比线性算法复杂度更好的算法遍历元素,所以你应该用最简单的数据结构完成简单工作。另外,当性能并非首要考虑因素时,或者当你需要存储相对较少的数据项时,或者当你需要和内核中其他使用链表的代码交互时,也该优先选择链表
  • 如果你的代码符合生产者/消费者模式,则使用队列,特别是如果你想(或者可以)要一个定长缓冲。队列会使得添加和删除项的工作简单有效。同时队列也提供了先入先出(FIFO)语义,而这也正是生产者/消费者用例的普遍需求。另一方面,如果你需要存储一个大小不明的 (可能很多项)的数据集合,那么链表可能更合适,因为你可以动态添加任何数量的数据项
  • 如果你需要映射一个UID到一个对象,就使用映射。映射结构使得映射工作简单有效,而且映射可以帮你维护和分配UID 。Linux的映射接口是针对UID到指针的映射,它并不适合其他场景。但是如果你在处理发给用户空间的描述符,就考虑一下映射吧
  • 如果你需要存储大最数据,并且检索迅速,那么红黑树最好。红黑树可确保搜索时间复杂度是对数关系,同时也能保证按序遍历时间复杂度是线性关系。虽然它比其他数据结构复杂一些, 但其内存开销情况并不是太糟。但是如果你没有执行太多次时间紧迫的查找操作,则红黑树可能不是最好选择。这种情况最好使用链表

二、算法复杂度

  • 在计算机科学和相关的学科中,很有必要将算法的复杂度(或伸缩度)量化地表示出来。 虽然存在各种各样表示伸缩度的方法,但最常用的技术还是研究算法的渐近行为(asymptotic behavior)。渐近行为是指当算法的输入变得非常大或接近于无限大时算法的行为。渐近行为充分显示了当一个算法的输入逐渐变大时,该算法的伸缩度如何。研究算法的伸缩 (当输入增大时算法执行的变化)可以帮助我们以特定基准抽象出算法模型,从而更好地理解算法的行为

算法

  • 算法就是一系列的指令,它可能有一个或多个输入,最后产生一个结果或输出。比如计算一个房间中人数的步骤就是一个算法,它的输入是人,计数结果是输出。在Linux内核中,页换出和进程调度都是算法的例子。从数学角度讲,一个算法好比一个函数(或至少我们可将它抽象为 一个函数)。比如,我们称人数统计算法为f, 要统计的人数为x,可以写成下面形式:
y = f(x)   人数统计的函数
  • 这里y是统计x个人所需的时间

大O符号

  • 一个很有用的渐近表示法就是上限——它是一个函数,其值自从一个起始点之后总是超过我们所研究的函数的值,也就是说上限等于或者快于我们研究的函数。一个特殊符号,大O符号用来描述这种正常率
  • 函数f(x)可写作O(g(x)),读为“f是g的大O”。数学定义形式为:

Linux(内核剖析):18---内核数据结构总结(数据结构选择与算法复杂度分析)

  • 换成自然语言就是,完成f(x)的时间总是短于或等于完成g(x)的时间和任意常量(至少,只要输入的x值大于某个初始值x’)的乘积
  • 从根本上讲,我们需要寻找一个函数,它的行为和我们的算法一样差或更差,这样一来我们就可以通过给该函数送入非常大的输入,然后观察该函数的结果,从而了解我们算法的执行上限

大Θ符号

  • 当大多数人谈论大O符号时,更准确地讲它们谈论的更接近Donald Knuth描述的大Θ符号
  • 从技术角度讲,大O符号适合描述上限,比如7是6的上限,同样道理,9、12、65也都是6的上限,但在后来大多数人讨论函数增长率时,更多说的是最小上限,或一个抽象出具有上限和下限的函数
  • 算法分析领域之父,Knuth教授,将其描述为大Θ符号,并给出了下面的定义:

Linux(内核剖析):18---内核数据结构总结(数据结构选择与算法复杂度分析)

  • 那么,我们也可以说f(x)是g(x)级(order)。级或大Θ,是理解内核中算法的最重要的数学工具之一
  • 所以,当人们谈到大O符号时,他们往往是在谈论大Θ

时间复杂度

  • 比如,再次考虑计算房间里的人数,假设你一秒钟数一个人,那么如果有7个人在房间里,你需要花7秒钟数它们。显然如果有n个人,需要花n秒来数它们。我们称该算法复杂度为O(n)。如果任务时在房间里的所有人面前跳舞?因为不管房间里有多少人,跳舞花费的时间都是相同的,所以该任务的复杂度为O(1)
  • 下表给出了常见的复杂度

Linux(内核剖析):18---内核数据结构总结(数据结构选择与算法复杂度分析)