python高效处理大数据集1数据处理效率的迫切需求

发布时间 2023-05-08 06:41:40作者: 磁石空杯

1数据处理效率的迫切需求

本章包括 

  • 处理指数级增长的数据的挑战
  • 比较传统和最新的计算架构
  • Python在现代数据分析中的作用和缺点
  • 提供高效Python计算解决方案的技术

本书分享工作中使用的性能和存储优化策略的集合。简单地在问题上投入更多的机器往往可能也没有帮助。因此介绍的解决方案更多的是依赖于理解和利用我们手头的东西:编码方法、硬件和系统架构、可用软件,当然还有Python语言、库和生态系统的细微差别。

Python已经成为首选的语言,Python在数据科学和数据工程中的普及是该语言发展的主要动力之一,根据大多数开发人员的调查,它有助于将其推到最受欢迎的三种语言之一。在处理大数据方面,Python有其独特的优势和局限性,它的速度不足当然会带来挑战。从好的方面看,正如你将看到的,有许多不同的角度、方法和变通办法来使Python更有效地处理大量的数据。

1.1 数据膨胀有多严重?

你可能知道两个计算定律,摩尔定律和埃德霍尔姆定律,它们共同提供了数据的指数级增长以及计算系统处理这些数据的滞后能力的戏剧性画面。埃德霍尔姆定律指出,电信的数据速率每18个月翻一番,而摩尔定律则预测,微芯片上可容纳的晶体管数量每两年翻一番。我们可以把埃德霍尔姆的数据传输率作为收集数据量的代表,把摩尔的晶体管密度作为计算硬件的速度和容量的指标。当我们把它们放在一起时,我们发现在我们收集数据的速度和数量与我们处理和存储数据的能力之间有六个月的滞后。

此外,人类产生的90%的数据都发生在过去两年。

摩尔定律和埃德霍尔姆定律之间的比率表明,硬件将永远落后于正在产生的数据量。而且,这个差距会随着时间的推移而加大。

我们如何处理所有这些增长的数据?事实证明,我们大多不知道。据《卫报》报道,超过99%的数据从未被分析过。阻碍我们利用如此多的数据的部分原因是我们缺乏有效的程序来分析它。

"如果你有更多的数据,只需向它投掷更多的服务器"。由于许多原因,这往往不是可行的或适当的解决方案。相反,当我们需要提高一个现有系统的性能时,我们可以看看系统的架构和实现,找到我们可以优化性能的地方。我已经数不清有多少次,仅仅通过在审查现有代码时注意到效率问题,就能使性能提高十倍之多。

最关键的是要明白,要分析的数据量的增加和分析数据所需的基础设施的复杂性之间的关系,几乎是线性的。解决这些问题需要开发人员比机器有更多的时间和智慧。不仅是云环境,而且是内部集群,甚至是单机实现,都是如此。一些使用案例将有助于明确这一点。比如说:

  • 你的解决方案只需要一台电脑,但突然你需要更多的机器。

增加机器意味着你将不得不管理机器的数量,在它们之间分配工作负载,并确保数据被正确分区。你可能还需要文件系统服务器来添加到你的机器列表中。维护服务器集群,或云,其成本比维护一台电脑高得多。

  • 你的解决方案在内存中运行良好,但后来数据量增加。

为了处理存储在磁盘中的新数据量,通常需要对你的代码进行重大重写。当然,代码本身的复杂性也会增加。例如,如果主数据库现在是在磁盘上,你可能需要创建缓存策略。或者你可能需要从多个进程中进行并发的读取,或者更糟糕的是,并发的写入。

  • SQL数据库,突然你达到了服务器的最大吞吐能力。

如果这只是读取容量的问题,那么你可能只需要创建几个读取副本就可以生存下来。但如果是写的问题,你会怎么做?或者你决定完全改变你的数据库技术,转而使用一些所谓的性能更好的NoSQL?

  • 如果你依赖于基于供应商专有技术的云端系统,你可能会发现,无限扩展的能力更多的是营销上的空谈而不是技术上的现实。

增长不仅仅是一个 "增加机器 "的问题,而是需要在多个方面进行大量工作,以处理增加的复杂性。即使是像在一台计算机上实现的并行解决方案这样 "简单 "的东西,也会带来并行处理的所有问题(竞赛、死锁等等)。这些更有效的解决方案可以对复杂性、可靠性和成本产生巨大的影响。

那种认为对这些公司有效的东西也适合我们其他人的普遍看法是错误的。一般来说,不太复杂的解决方案对我们大多数人来说会更合适。

正如你所看到的,这个数据和算法在数量和复杂性方面都极度增长的新世界,需要更复杂的技术,以高效和有成本意识的方式进行计算和存储。有时你将需要扩大你的基础设施。但当你架构和实施你的解决方案时,你仍然可以使用同样的心态来关注效率。
只是,技术会有所不同。

1.2 现代计算架构和高性能计算

创造更高效的解决方案并不是在抽象的空洞中发生的。首先,我们要考虑的是我们的领域问题--即你要解决的是什么实际问题。
同样重要的是我们的解决方案将在哪里运行的计算架构。

  • 计算机内部的变化

计算机内部正在发生巨大的变化。首先,我们有了CPU,它的处理能力主要体现在并行单元的数量上,而不是像过去那样的原始速度。计算机还可以配备图形处理单元(GPU),这些单元最初只为图形处理而开发,但现在也可用于通用计算。事实上,许多人工智能算法的有效实现都是针对GPU完成的。不幸的是,至少从我们的角度来看,GPU的架构与CPU完全不同:它们由成千上万的计算单元组成,预计在所有单元中进行相同的 "简单 "计算。内存模型也完全不同。这些差异意味着为GPU编程需要采取与CPU编程完全不同的方法。

为了了解我们如何使用GPU进行数据处理,我们需要了解其最初的目的和架构含义。GPU,正如其名称所示,是为了帮助图形处理而开发的。一些对计算要求最高的应用实际上是游戏。游戏,以及一般的图形应用,都在不断地更新屏幕上的数百万像素。为解决这一问题而设计的硬件架构有许多小型处理核心。GPU很容易拥有数千个内核,而CPU通常只有不到100个。GPU的内核实质上更简单,并且大多在每个内核上运行相同的代码。因此,它们非常适用于运行大量类似的任务,如更新像素。

鉴于GPU的巨大处理能力,人们试图尝试将这种能力用于其他任务,出现了图形处理单元上的通用计算(GPGPU GPGPU general-purpose computing on graphics processing units)。由于GPU架构的组织方式,它们大多适用于具有大规模并行性质的任务。事实证明,许多现代人工智能算法,如基于神经网络的算法,往往是大规模并行的。因此,两者之间存在着自然的契合。

不幸的是,CPU和GPU之间的区别不仅在于内核数量和复杂性。GPU内存,特别是在计算能力最强的GPU上,是与主内存分开的。因此,还存在着在主内存和GPU内存之间传输数据的问题。因此,我们在针对GPU时有两个巨大的问题需要考虑。

用Python对GPU进行编程要比针对CPU的编程困难得多,也不那么实用。尽管如此,仍然有足够的空间从Python中利用GPU。

虽然没有GPU的进步那么时髦,但CPU的编程方式也发生了巨大的变化。而且,与GPU不同,我们可以很容易地在Python中使用这些CPU的大部分变化。与过去相比,制造商提供的CPU性能提升方式不同。在物理学定律的驱动下,他们的解决方案是建立更多的并行处理,而不是更多的速度。摩尔定律有时被表述为速度每24个月翻一番,但这实际上不是正确的定义:它与晶体管密度每两年翻一番有关。速度增加和晶体管密度之间的线性关系在十多年前就已经打破了,从那时起,速度大多已经趋于平稳。鉴于数据与算法的复杂性持续增长,我们正处于一个有害的局面。来自CPU制造商的第一线解决方案是允许更多的并行性:每台计算机有更多的CPU,每个CPU有更多的内核,以及同时多线程。处理器不再真正加速顺序计算,而是允许更多的并发执行。这种并发执行需要我们对计算机编程方式进行范式转变。以前,当你更换CPU时,程序的速度会 "神奇地"
增加。现在,提高速度取决于程序员是否意识到底层架构向并行编程范式的转变。

我们对现代CPU进行编程的方式有很多变化,其中一些变化是非常反直觉的,值得我们从一开始就注意到。例如,虽然近年来CPU的速度已经趋于平缓,但CPU仍然比RAM快几个数量级。如果CPU缓存不存在,那么CPU大部分时间都会闲置,因为它们大部分时间都在等待RAM。这意味着,有时使用压缩数据(包括解压缩的成本)比使用原始数据要快。为什么呢?如果你能把压缩块放在CPU缓存中,那么那些原本闲置的等待RAM访问的周期就可以用来解压数据,而CPU的周期则可以用来进行计算 。类似也适用于压缩文件系统:它们有时比原始文件系统更快。在Python世界中,这一点有直接的应用;例如,通过改变一个关于NumPy数组内部表示方法的简单的布尔标志,你就可以利用缓存定位问题,大大加快NumPy的处理速度。

image

参考资料

在高性能计算环境中,我们既把网络作为增加存储的一种方式,特别是增加计算能力的一种方式。虽然我们希望使用单台计算机来解决我们的问题,但有时依靠计算集群是不可避免的。为具有多台计算机的架构进行优化--无论是在云端还是在企业内部--将是我们实现高性能之旅的一部分。

使用许多计算机和外部存储带来了一类全新的与分布式计算有关的问题:网络拓扑结构、跨机器共享数据、以及管理跨网络运行的进程。有很多例子。例如,在需要高性能和低延迟的服务上使用REST APIs的代价是什么?我们如何处理拥有远程文件系统的惩罚;我们能否减轻这些惩罚?

我们将试图优化我们对网络堆栈的使用。在网络之外,我们有我们的代码和Python库,它们对下面的层进行选择。在网络堆栈的顶端,数据传输的典型选择是HTTPS和基于JSON的有效载荷。

虽然这对许多应用来说是一个完全合理的选择,但在网络速度和滞后性很重要的情况下,还有更高性能的替代方案。例如,二进制有效载荷可能比JSON更有效。另外,HTTP可能被的TCP套接字所取代。但也有更激进的替代方案,比如替换TCP传输层:大多数互联网应用协议都使用TCP,尽管有一些例外,比如DNS和DHCP,它们都是基于UDP的。TCP协议是高度可靠的,但要为这种可靠性付出性能上的代价。有些时候,UDP较小的开销将是一个更有效的选择,而且不需要额外的可靠性。

在传输协议下面,我们有互联网协议(IP)和物理基础设施。当我们设计解决方案时,物理基础设施可能很重要。
例如,如果我们有一个非常可靠的本地网络,那么可能会丢失数据的UDP就会比在不可靠的网络中更有选择权。

  • 1.2.3 云

在过去,大多数数据处理的实现都是在一台计算机上或由运行工作负载的同一组织维护的内部集群上进行的。目前,基于云的基础设施,所有的服务器都是 "虚拟 "的,由外部实体维护,正变得越来越普遍。有时,如所谓的无服务器计算,我们甚至不直接与服务器打交道。

云不仅仅是增加更多的计算机或网络存储。它也是关于如何处理存储和计算资源的一套专有扩展,而这些扩展在性能上有影响。此外,虚拟计算机会给一些CPU优化带来麻烦。例如,在裸机中,你可以设计一个考虑到缓存位置问题的解决方案,但在虚拟机中,你没有办法知道你的缓存是否被另一个同时执行的虚拟机抢占了。

我们如何在这样的环境中保持我们的算法的高效性?另外,云计算的成本模式完全不同,时间就是金钱,因此,高效的解决方案变得更加重要。

云中的许多计算和存储解决方案也是专有的,有非常具体的API和行为。使用这种专有的解决方案也会对性能产生影响,应该加以考虑。因此,虽然大多数与传统集群有关的问题也适用于云,但有时会有一些特定的问题需要单独处理。现在我们对塑造我们的应用的架构可能性和局限性有了一个看法,让我们来谈谈Python在高性能计算中的优势和劣势。

通过网络堆栈的API调用。了解网络通信的替代方案可以极大地提高基于互联网的应用程序的速度。

1.3 Python的局限性

这些问题中的大多数都可以被克服。许多人已经制作了应用程序和库,可以缓解大多数性能问题。你仍然可以用Python写代码,在内存占用较小的情况下表现非常好。你只需要在写代码的同时注意到 Python 的缺陷。这就是许多核心科学库,如NumPy、scikit-learn和SciPy等。它们对计算要求最高的部分通常用C或Fortran实现。

  • 1.3.1 全局解释器锁

在关于 Python 性能的讨论中,不可避免地会提到它的 GIL,即全局解释器锁。GIL 究竟是什么?虽然Python有线程的概念,但CPython有一个GIL,它只允许在一个时间点上执行一个线程。即使在多核处理器上,你也只能在一个时间点上执行一个单线程。

Python的其他实现,如Jython和IronPython,没有GIL,可以使用现代多处理器的所有内核。但是CPython仍然是标准实现,所有的主要库都是为其开发的。此外,Jython和IronPython分别依赖于JVM和.NET。因此,鉴于CPython拥有庞大的库基础,它最终成为了默认的Python实现。我们将在书中简要地讨论其他的实现,特别是PyPy,但在实践中,CPython是女王。

GIL是否会带来严重的性能损失?在大多数情况下,答案是惊人的否定。这有两个主要原因:大部分的高性能代码,那些紧密的内循环,可能要用低级语言编写;Python为低级语言提供了释放GIL的机制。

这意味着当你进入用低级语言重写的一部分代码时,你可以指示Python继续用其他Python线程与你的低级实现并行。只有在安全的情况下,你才应该释放 GIL--例如,如果你不向可能被其他线程使用的对象写东西。

另外,多进程 (即同时运行多个进程) 不受 GIL 的影响,它只影响线程,所以即使在纯 Python 中,仍然有很多空间来部署并行解决方案。

1.4 小结

本书是关于从Python中获得高性能的,但是代码并不是在真空中运行的。只有当你考虑到数据和算法需求以及计算架构的更广阔的视角时,你才能设计出高效的代码。虽然不可能在一本书中深入研究每一个架构和算法细节,但我写这些的目的是帮助你理解CPU设计、GPU、存储替代方案、网络协议和云架构以及其他系统考虑因素的影响,这样你就可以为提高Python代码的性能做出合理的决定。本书应该让你有能力评估你的计算架构的优点和缺点,无论是单台计算机、支持GPU的计算机、集群,还是云环境,并实施必要的改变以充分利用它。

本书的目标是向你介绍一系列解决方案,并向你展示每一种解决方案的最佳应用方式和位置,以便你能够为你的特定资源、目标和问题集选择和实施最有效的解决方案。我们将花大量时间通过实例进行研究,这样你就可以自己看到这些方法的效果,包括正面和负面的。没有规定要应用所有的方法,也没有规定应用这些方法的顺序。每种方法在性能和效率方面都有或大或小的收益,同时也有其权衡之处。如果你了解你的系统和改善该系统各方面的可用策略,你就可以选择把时间和资源花在哪里。为了帮助你理解这些方法,列出了本书所介绍的技术以及它们所针对的系统开发过程的组成部分或领域的摘要。


这张表里有很多内容,让我强调一下主要焦点领域的实际应用。读完本书后,你将能够查看本地Python代码并理解内置数据结构和算法的性能影响。

你将能够发现并以更合适的解决方案取代低效的结构:例如,在常量列表上重复搜索的地方用集合取代列表,或者使用非对象数组取代对象列表以提高速度。你还将能够利用现有的性能不佳的算法,(1)对代码进行剖析,找到导致性能问题的部分;(2)确定优化这些代码部分的最佳方法。

如前所述,本书针对广泛使用的数据处理和分析的Python生态库(如pandas和NumPy),目的是改善我们使用它们的方式。在计算方面,这是一个很大的材料,所以我们不会讨论非常高级的库。例如,我们不会讨论优化TensorFlow的使用,但我们会讨论使底层算法更有效的技术。

在数据存储和转换方面,你将能够看到一个数据源,并了解它在有效处理和存储方面的缺点。然后,你将能够对数据进行转换,使所有需要的信息仍然保持,但对数据的访问模式将大大增加效率。最后,你还将了解Dask,一个基于Python的框架,它允许你开发并行解决方案,可以从一台机器扩展到非常大的计算机集群或云计算解决方案。

这不是一本食谱,而是介绍了优化的思维方式和为提高性能而调查的领域。因此,这些方法从数据科学的基本库中提取最大的性能。

讨论的对数据处理效率的迫切需求,在大多数情况下,应该经得起硬件、软件、网络、系统,甚至是数据本身的变化。虽然不是每一种技术都能赚钱,甚至在每一种情况下都能使用,但从头到尾阅读本书是了解你的选择、打开你的思维、也许能想出一些你自己的解决方案的最可靠方法。一旦你接触到各种可能性,你就可以把这本书作为参考,挑出你想实施的技术。