高效Python-1提高数据处理效率的迫切需要

1提高数据处理效率的迫切需要

本章包括

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

我们一直在以极快的速度从各种来源收集海量数据。无论目前是否有使用价值,这些数据都会被收集起来。无论是否有办法对其进行处理、存储、访问或学习,数据都会被收集起来。在数据科学家对其进行分析之前,在设计师、开发人员和政策制定者利用其创造产品、服务和程序之前,软件工程师必须找到存储和处理这些数据的方法。现在,这些工程师比以往任何时候都更需要有效的方法来提高性能和优化存储。

在本书中,我将分享我在工作中使用的一系列性能和存储优化策略。简单地增加机器数量往往既不可能,也无济于事。因此,我在这里介绍的解决方案更多地依赖于理解和利用我们手头的东西:编码方法、硬件和系统架构、可用软件,当然还有Python语言、库和生态系统的细微差别。

Python已经成为一种首选语言,可以完成或至少粘合所有与数据洪流有关的繁重工作,这也是老生常谈的说法。事实上,Python在数据科学和数据工程领域的流行是该语言发展的主要推动力之一,根据大多数开发人员调查,Python已成为最流行的三大语言之一。在处理大数据方面,Python有其独特的优势和局限性,速度不够快肯定会带来挑战。好的一面是,正如您将看到的,有许多不同的角度、方法和变通办法可以让Python更高效地处理大量数据。

在找到解决方案之前,我们需要充分理解问题,而这正是我们在第一章中要做的事情。我们将花一些时间仔细研究大量数据带来的计算挑战,以确定我们所面对的问题到底是什么。接下来,我们将研究硬件、网络和云架构的作用,以了解为什么提高CPU速度等旧的解决方案不再适用。然后,我们将讨论Python在处理大数据时面临的特殊挑战,包括Python的线程和CPython的全局解释器锁(GIL)。一旦我们充分理解了需要新的方法来使Python性能更佳,我将概述您将在本书中学到的解决方案。

1.1 数据洪水有多严重?

您可能知道摩尔定律和埃德霍姆定律这两个计算定律,它们共同描绘了数据呈指数级增长,而计算系统处理这些数据的能力却滞后的戏剧性画面。埃德霍姆定律指出,电信领域的数据传输速率每18个月翻一番,而摩尔定律则预测,微芯片上可容纳的晶体管数量每两年翻一番。我们可以把埃德霍尔姆数据传输速率作为收集数据量的代表,把摩尔晶体管密度作为计算硬件速度和容量的指标。当我们把它们放在一起时,就会发现我们收集数据的速度和数量与我们处理和存储数据的能力之间存在六个月的差距。由于指数式增长很难用语言来理解,因此我将这两个定律对照绘制成一张图,如图1.1所示

摩尔定律和埃德霍尔姆定律之间的比率表明,硬件将始终落后于所产生的数据量。而且,随着时间的推移,这种差距会越来越大。

这张图描述的情况可以看作是我们需要分析的内容(埃德霍姆定律)与我们进行分析的能力(摩尔定律)之间的斗争。实际上,这幅图描绘的情况比我们的实际情况更为乐观。我们将在第 6 章讨论现代CPU架构中的摩尔定律时了解其中的原因。这里我们重点讨论数据增长,举一个例子,互联网流量是可用数据的间接衡量标准。如图1.2所示,多年来互联网流量的增长很好地遵循了埃德霍姆定律。

图 1.2 多年来全球互联网流量的增长(以每月 PB 为单位)。(资料来源:https://en.wikipedia.org/wiki/Internet_traffic)

此外,人类产生的 90% 的数据都发生在过去两年(参见"大数据及其意义",http://mng.bz/v1ya )。至于这些新数据的质量是否与其规模成正比,则完全是另一回事。问题是,产生的数据需要处理,而处理需要资源。

给软件工程师带来障碍的不仅仅是可用数据的数量。所有这些新数据的表现方式在本质上也在发生变化。有人预测,到2025年,大约80%的数据可能是非结构化数据("挖掘非结构化数据的力量",http://mng.bz/BlP0 )。我们将在本书稍后部分详细介绍,但简单地说,从计算角度来看,非结构化数据对数据处理的要求更高。

我们如何处理这些增长的数据?事实证明,我们大多没有这样做。据《卫报》(http://mng.bz/Q8M4 )报道,超过99%的数据从未被分析过。我们之所以无法利用如此多的数据,部分原因在于我们缺乏分析这些数据的有效程序。

数据的增长以及随之而来的对更多处理的需求,已发展成为有关计算的最恶毒的咒语之一: "如果你有更多的数据,只需投入更多的服务器"。出于多种原因,这往往不是一个可行或合适的解决方案。相反,当我们需要提高现有系统的性能时,我们可以审视系统架构和实施,找到可以优化性能的地方。我已经记不清有多少次在审查现有代码时,只需注意效率问题,就能将性能提高十倍。

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

您的解决方案只需要一台计算机,但突然您需要更多的机器。增加机器意味着你必须管理机器的数量,在它们之间分配工作负载,并确保数据被正确分区。您可能还需要一个文件系统服务器来增加机器数量。维护服务器群或云的成本要比维护单台计算机的成本高得多。

您的解决方案在内存中运行良好,但随着数据量的增加,已不再适合您的内存。要处理存储在磁盘中的新数据量,通常需要重新编写代码。当然,代码本身的复杂性也会增加。例如,如果主数据库现在在磁盘上,您可能需要创建缓存策略。或者,您可能需要从多个进程进行并发读取,或者更糟糕的是,并发写入。

在使用SQL数据库时,突然达到了服务器的最大吞吐能力。如果只是读取能力问题,那么只需创建几个读取副本就能解决。但如果是写入问题,该怎么办呢?也许你会建立分片,或者决定彻底改变你的数据库技术,转而使用一些所谓性能更好的 NoSQL 变体?

如果您依赖的是基于供应商专有技术的云系统,您可能会发现,无限扩展的能力更多是营销上的空谈,而不是技术上的现实。在许多情况下,如果您遇到性能限制,唯一现实的解决方案就是改变您正在使用的技术,而这种改变需要大量的时间、金钱和人力。

我希望这些例子能够说明,增长并不仅仅是"增加更多机器"的问题,而是需要在多个方面开展大量工作,以应对日益增加的复杂性。即使是在单台计算机上实施并行解决方案这样"简单"的事情,也会带来并行处理的所有问题(竞赛、死锁等)。这些更高效的解决方案会对复杂性、可靠性和成本产生巨大影响。

最后,我们可以提出这样的观点:即使我们可以线性扩展我们的基础设施(我们确实做不到),也需要考虑伦理和生态问题:据预测,与"数据海啸"相关的能源消耗占全球发电量的20%("数据海啸",http://mng.bz/X5GE ),而且随着硬件的更新,还存在垃圾填埋问题。

好消息是,在处理大数据时提高计算效率可以帮助我们减少计算费用、解决方案架构的复杂性、存储需求、上市时间和能源消耗。有时,更高效的解决方案甚至可以将实施成本降到最低。例如,明智地使用数据结构可以减少计算时间,而无需大量的开发成本。

另一方面,我们将研究的许多解决方案都需要开发成本,而且本身也会增加一定的复杂性。当您查看您的数据并预测其增长时,您必须判断在哪些方面进行优化,因为没有一目了然的方法或一刀切的解决方案。尽管如此,可能有一条规则可以适用于所有情况:如果解决方案对Netflix、谷歌、亚马逊、苹果或 Facebook 有利,那么它可能对你不利,当然,除非你在这些公司中工作。

我们大多数人所看到的数据量将大大低于最大的技术公司所使用的数据量。数据量仍然会很大,数据仍然会很难获取,但可能会低几个数量级。在我看来,认为适用于这些公司的技术也适用于我们其他人的观点是错误的。一般来说,不太复杂的解决方案更适合我们大多数人。

正如你所看到的,在这个数据和算法的数量和复杂性都急剧增长的新世界里,需要更复杂的技术,以高效和节约成本的方式进行计算和存储。不要误解我的意思:有时你需要扩展你的基础设施。但是,在设计和实施解决方案时,您仍然可以使用同样的思维方式,注重效率。只是技术不同而已。

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

创建更高效的解决方案并非抽象空洞。首先,我们要考虑我们的领域问题,即您要解决的实际问题是什么。同样重要的是运行解决方案的计算架构。计算架构在决定最佳优化技术方面发挥着重要作用,因此我们在设计软件解决方案时必须将其考虑在内。本节将介绍影响解决方案设计和实施的主要架构问题。

1.2.1 计算机内部的变化

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

要了解如何将GPU用于数据处理,我们需要先了解GPU的原始用途和架构含义。顾名思义,GPU是为图形处理而开发的。一些对计算要求最高的应用实际上就是游戏。游戏和一般的图形应用都需要不断更新屏幕上的数百万像素。为解决这一问题而设计的硬件架构有许多小型处理核心。GPU很容易拥有数千个内核,而CPU通常只有不到10个。GPU内核要简单得多,而且每个内核大多运行相同的代码。因此,它们非常适合运行大量类似的任务,如更新像素。

鉴于GPU强大的处理能力,人们试图将其用于其他任务,于是出现了图形处理器通用计算(GPGPU)。由于GPU架构的组织方式,它们大多适用于大规模并行性质的任务。事实证明,许多现代人工智能算法,如基于神经网络的算法,往往是大规模并行的。因此,两者之间存在着天然的契合点。

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

在第9章中我们会清楚地看到,使用Python对GPU进行编程要比针对CPU的编程困难得多,也更不实用。尽管如此,Python仍然有足够的空间来使用GPU。

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

现代CPU的编程方式发生了许多变化,正如你在第6章中看到的,其中一些变化非常反直觉,值得从一开始就加以关注。例如,虽然CPU的速度近年来有所下降,但CPU的速度仍然比RAM快几个数量级。如果CPU缓存不存在,那么CPU大部分时间都会闲置,因为它们大部分时间都在等待RAM。这意味着,有时处理压缩数据(包括解压缩的成本)比处理原始数据更快。为什么呢?如果可以将压缩块放在 CPU 缓存中,那么那些等待 RAM 访问的空闲周期就可以用来解压缩数据,而空余的 CPU周期则可以用于计算!类似的论点也适用于压缩文件系统:它们有时比原始文件系统更快。这在Python世界中也有直接的应用;例如,通过改变一个简单的布尔标志来选择NumPy数组的内部表示法,就可以利用缓存的局部性问题,大大加快NumPy处理速度。表1.1列出了不同类型内存的访问时间和大小,包括CPU缓存、RAM、本地磁盘和远程存储。这里的关键不是精确数字,而是大小和访问时间的数量级差异。

表 1.1 包括计算机外部的三级存储。这里也发生了一些变化,我们将在下一节讨论。

参考资料

1.2.2 网络中的变化

在高性能计算环境中,我们使用网络来增加存储,尤其是提高计算能力。虽然我们希望使用单台计算机解决我们的问题,但有时依靠计算集群是不可避免的。优化多台计算机的架构--无论是在云端还是在企业内部--将是我们通往高性能之路的一部分。

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

我们将努力优化网络堆栈的使用,为此,我们必须了解图1.3所示的各个层面。在网络之外,我们有自己的代码和Python库,它们会对下面的层进行选择。在网络堆栈的顶层,数据传输的典型选择是基于 JSON 有效载荷的 HTTPS。

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

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

1.2.3 云

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

云不仅仅是增加更多的计算机或网络存储。它还涉及如何处理存储和计算资源的一系列专有扩展,而这些扩展会对性能产生影响。此外,虚拟计算机还可能影响某些CPU优化。例如,在裸机中,你可以设计一个考虑到缓存位置问题的解决方案,但在虚拟机中,你无法知道你的缓存是否被同时执行的另一个虚拟机抢占。在这样的环境中,我们该如何保持算法的效率呢?此外,云计算的成本模式完全不同,时间就是金钱,因此高效的解决方案变得更加重要。

云计算中的许多计算和存储解决方案都是专有的,具有非常特殊的应用程序接口和行为。使用这些专有解决方案也会对性能产生影响,这一点应加以考虑。因此,虽然与传统集群有关的大多数问题也适用于云,但有时会有一些特殊问题需要单独处理。既然我们已经了解了架构的可能性和局限性,这些可能性和局限性将影响我们的应用程序,下面我们就来谈谈Python在高性能计算方面的优缺点。

1.3 Python的局限性

Python广泛应用于现代数据处理应用程序。与任何语言一样,它既有优点,也有缺点。使用Python有很多理由,但在这里我们更关注的是如何处理 Python 在高性能数据处理中的局限性。

让我们不要粉饰现实:在处理高性能计算方面,Python的能力远远不够。如果只考虑性能和并行性,就不会有人使用Python。Python拥有令人惊叹的数据分析库生态、优秀的文档和支持性极强的社区。这就是我们使用它的原因,而不是计算性能。

有一句话是这样说的:"没有慢的语言,只有慢的语言实现"。我希望允许我提出不同意见。要求Python(或者JavaScript)这样的动态高级语言的实现者在速度上与 C、C++、Rust或Go这样的低级语言竞争是不公平的。

动态类型和垃圾回收等功能将在性能方面付出代价。这没有问题:在很多情况下,程序员的时间比计算时间更宝贵。但我们也不要把头埋在沙子里:更多的声明性和动态语言将在计算和内存方面付出代价。这是一种平衡。

尽管如此,这并不能成为性能不佳的语言实现的借口。在这方面,您可能正在使用的Python旗舰实现CPython的表现如何?要进行全面分析并非易事,但您可以做一个简单的练习:编写一个矩阵乘法函数并计时。例如,用另一种Python实现(如PyPy)运行它。然后将代码转换为 JavaScript(这是一种公平的比较,因为该语言也是动态语言;不公平的比较则是 C 语言)并再次计时。

1.3.1 全局解释器锁(Global Interpreter Lock)

CPython有GIL,它只允许一个线程在一个时间点上执行。即使在多核处理器上,在一个时间点上也只能执行一个线程。

Python的其他实现,如Jython和IronPython,没有GIL,可以使用现代多核处理器的所有内核。但CPython仍是所有主要库开发的参考实现。此外,Jython和IronPython分别依赖于JVM和.NET。因此,CPython凭借其庞大的库基础,最终成为默认的Python实现。我们将在书中简要讨论其他实现,其中最著名的是PyPy。

发是指一定数量的任务可以在时间上重叠,尽管它们可能不是同时运行。例如,它们可以交错运行。并行是指任务同时执行。因此,在 Python 中,并发是可能的,但并行是不可能的......。

没有并行的并发仍然非常有用。这方面最好的例子来自JavaScript世界和Node.JS,Node.JS被广泛用于实现网络服务器的后台。在许多服务器端网络任务中,大部分时间实际上都在等待IO;这正是一个线程主动放弃控制权的大好时机,以便其他线程可以继续计算。现代Python 也有类似的异步设施,我们将讨论它们。

但回到主要问题:GIL 会带来严重的性能损失吗?在大多数情况下,答案是令人惊讶的否定。这主要有两个原因:

  • 大多数高性能代码,即那些紧凑的内循环,很可能需要用我们讨论过的低级语言来编写。
  • Python为低级语言提供了释放 GIL的机制。

此外,多进程(即同时运行多个进程)并不受GIL的影响。

1.4 解决方案总结

本书的主题是如何从Python中获得高性能,只有从数据和算法需求以及计算架构等更广阔的角度来考虑,才能设计出高效的代码。帮助你理解CPU设计、GPU、存储替代方案、网络协议和云架构以及其他系统考虑因素(图1.4)的影响,从而为提高Python代码的性能做出正确的决策。无论是单台计算机、支持GPU的计算机、集群还是云环境,本书都将帮助您评估计算架构的优缺点,并实施必要的更改以充分利用其优势。

本书的目标是向您介绍一系列解决方案,并向您展示每种解决方案的最佳应用方式和应用场合,这样您就可以针对特定的资源、目标和问题选择并实施最高效的解决方案。我们会花大量时间举例说明,让你亲眼目睹这些方法的正反两方面效果。我们并没有规定必须采用所有方法,也没有规定采用这些方法的顺序。每种方法都会或多或少地提高性能和效率,同时也会有所取舍。如果你了解自己的系统以及改善系统各方面的可用策略,你就可以选择在哪些方面花费时间和资源。为了帮助您理解这些方法,表1.2概述了书中介绍的技术及其所针对的系统开发流程的组件或领域。

表中的内容很多,所以让我强调一下主要重点领域的实际应用。读完本书后,您将能够查看本地Python代码,并理解内置数据结构和算法对性能的影响。您将能够发现并用更合适的解决方案替换低效的结构:例如,在对恒定列表重复搜索时用集合替换列表,或者使用非对象数组代替对象列表以提高速度。您还可以使用性能不佳的现有算法,并(1)对代码进行剖析,找出导致性能问题的部分,(2)确定优化这些代码的最佳方法。

如前所述,本书针对广泛使用的Python数据处理和分析库(如pandas和NumPy),旨在改进我们使用这些库的方式。在计算方面,这是一个很大的材料,因此我们不会讨论非常高级的库。例如,我们不会讨论如何优化TensorFlow的使用,但会讨论如何提高底层算法的效率。

关于数据存储和转换,您将能够查看数据源并了解其在高效处理和存储方面的缺点。然后,您将能够对数据进行转换,使所有必要的信息仍然得到保留,但数据访问模式将大大提高效率。最后,您还将了解到Dask,这是一个基于Python的框架,允许您开发并行解决方案,可以从单机扩展到超大型计算机集群或云计算解决方案。

热门相关:有个人爱你很久   裙上之臣   大妆   今天也没变成玩偶呢   买妻种田:山野夫君,强势宠!