trip汽车仪表盘显示ABS故障,怎么解决?如何处理trip汽车仪表盘ABS故障警示灯的问题?,

数据工程师关于Go与Python的比较(第1部分)

介绍

探索golang —我们可以放弃Python吗? 而且我们是否(如"经常处理大量数据的人们"那样)最终找到了go的用例? 第1部分探索了Python和go之间的高级差异,并给出了这两种语言的具体示例,旨在回答基于Apache Beam和Google Dataflow的实际示例。

我曾经在此博客上使用过两次Apache Beam:这是一个用于批处理和流式用例的统一编程模型,可以处理令人愉悦的并行工作负载,并允许许多自定义I / O和其他连接器,以及在多种执行平台上运行,最著名的是Flink,Spark和Google Cloud的Dataflow。

您可以将其用于流式处理或批处理数据以对其进行分析,运行ETL并对其进行充实-您可以对其进行命名。 实际上,几年前我在这里使用过Beam。

现在,如果您要访问Beam的网站,则会发现以下精美图片:

> https://beam.apache.org/

它告诉您Beam是用Java或Python编写的,其他所有内容都属于"其他语言"。

但是,有一个"实验性"(GitHub)保证我们使用go代替Python-这就是让我兴奋的地方。

下一节将很少尝试给出一个"简短的"(约3,000个单词)的介绍,并根据不同的处理数据工程的特定用例以及使用多线程应用程序时将其与Python进行比较。 请注意,这绝不是完整的比较。

这是第2部分(共1部分),重点介绍两种语言以及Apache Beam SDK的当前状态。 在第2部分(即将推出)中,我们将探索如何使用Beam Go SDK,在GCP上将面临哪些局限性以及数据工程师的发展方向。

所有示例代码都可以在GitHub上找到。

Go vs.Python

如果您不熟悉,请允许我引用维基百科:

Go是由Robert Griesemer,Rob Pike和Ken Thompson在Google设计的一种静态类型的编译语言。 Go在语法上类似于C,但是具有内存安全性,垃圾回收,结构化类型和CSP样式的并发性。

https://en.wikipedia.org/wiki/Go_(programming_language)

Go有很多概念,应该让您为处理数据的并行执行框架而兴奋。 在以下各节中,我们将探索一些精心挑选的概念和示例,并以更长,更深入的并发示例作为结束。

速度

与解释型语言相反,go是一种编译器,它比Python更快。 它也倾向于真正快速地编译事物,并且比起尝试用Python进行同样的操作,将编译后的代码交付给最终用户要容易得多。

您可以在下面找到一些动手示例。

> Generated using matplotlib; CC BY-SA 3.0

静态类型

相对于Python的动态类型而言,静态类型避免了在正确的时机使用正确的数据类型的麻烦。

我并不是试图在静态和动态类型与Python的复杂性之间进行一般的比较,而只是根据我的经验,特别是在数据空间中,每天进行比较。

在数据驱动的项目中,您将不断发现自己要处理非常严格的数据类型定义,通常是不可避免地将所有公司数据存储在大型RDMB(例如Oracle)中强制执行严格键入的区域的工件(当然是这样)。

诸如pySpark甚至pandas之类的基于Python的框架附带了自己的抽象层,用于提供类型(请参见dtypes)。

以使用为NYS Yellow Cab数据<0>为pySpark(一种流行的数据处理框架,为Python(以及其他)提供)编写的代码为例。

首先,将CSV文件读入RDD(一个"弹性的,分布式数据集"),并在数据集中的每一行上应用一些转换lambda。

# Read datardd = sc.textFile('data/yellow_tripdata_2019-01.csv')# Parse the RDDrdd = rdd.map(lambda r: r.split(','))\ .map(lambda r: (r<10>,r<13>)) # Take 'fare_amount' and 'tip_amount'rdd = rdd.filter(lambda r: 'fare_amount' not in r) # filter headerrdd.take(1)

产生:

<('7', '1.65'), ('14', '1'), ('4.5', '0'), ('3.5', '0'), ('52', '0')>

如果我们查看其中一个元组:

type(rdd.take(1)<0><0>)

我们将str作为数据类型。

现在,如果我们要减少此费用以总结出租车费和小费金额:

def sum_fares(fare, tip):   return fare + tiprdd.map(lambda r: sum_fares(*r)).take(5)

如以上输出中的引号所示,结果是一个串联字符串的列表。

<'71.65', '141', '4.50', '3.50', '520'>

而不是数学上正确的:

rdd.map(lambda r: sum_fares(*)).take(5)# <8.65, 15.0, 4.5, 3.5, 52.0>

较新版本的Python支持类型提示,而Python解释器则跟踪变量类型。 但是,如以上示例所示,我个人很难维护一致,可读和可维护的代码库,尤其是在复杂的应用程序上。

另一方面,go是静态类型的。

package mainimport (   "fmt"   "reflect")func main() {   // Implicit   str := "A String"   fmt.Printf("%s is %s\n", str, reflect.TypeOf(str))   // Explicit   var x float64 = 3.14   fmt.Printf("%v is %s\n", x, reflect.TypeOf(x))}

将产生:

go run test.go

A String is string3.14 is float64

鉴于此:

str = x

无法编译:

go run testvar.go

# command-line-arguments./test.go:15:6: cannot use x (type float64) as type string in assignment

Go不支持泛型,但Python也不支持。

我跳过了go的空接口{}概念,以支持任意值并处理未知类型; 可能在需要弱类型抽象的几种极端情况下使用此概念

<0> Spark的SQL接口将推断字符串类型,并允许对字符串进行数学运算(如果它们一致)

接口和结构

Python确实具有类结构(并且我已经广泛使用了它),而go使用了结构和接口(这是一个可怕的简化)。 go没有继承,并且依赖于接口和组成。

在数据世界中,拥有严格的类结构(例如抽象转换,统计模型或仅是简单的旧数据结构)既是痛苦,也是诅咒。

Python广泛使用可以容纳任意键值对和嵌套结构且与JSON语法相似的字典,以及用于数据结构化抽象的已定义类。 几乎每个数据框架都附带有自己的架构类。 从理论上讲,go可以避免通过接口(用于标准),静态类型化(以确保在正确的时机使用正确的数据类型)以及用于定义结构和逻辑的结构的组合来避免这种情况。

这是一个有用的示例,该示例使用接口CoordinateData和函数calculateDistance来计算世界上最差的GIS分析平台中两个坐标元组之间的距离:)

package mainimport (	"fmt"	"math")// A Resource we're trying to accesstype CoordinateData interface {	calculateDistance(latTo, lonTo float64) float64}

然后,我们实现地理空间数据和Haversine函数(以近似地球上的距离):

type GeospatialData struct {	lat, lon float64}const earthRadius = float64(6371)func (d GeospatialData) calculateDistance(latTo, lonTo float64) float64 {	// Haversine distance	var deltaLat = (latTo - d.lat) * (math.Pi / 180)	var deltaLon = (lonTo - d.lon) * (math.Pi / 180)		var a = math.Sin(deltaLat / 2) * math.Sin(deltaLat / 2) + 		math.Cos(d.lat * (math.Pi / 180)) * math.Cos(latTo * (math.Pi / 180)) *		math.Sin(deltaLon / 2) * math.Sin(deltaLon / 2)	var c = 2 * math.Atan2(math.Sqrt(a),math.Sqrt(1-a))		return earthRadius * c}

在简单的二维平面上也是如此:

type CartesianPlaneData struct {	x, y float64}func (d CartesianPlaneData) calculateDistance(xTo, yTo float64) float64 {	// Simple 2-dimensional Euclidean distance 	dx := (xTo - d.x)	dy := (yTo - d.y)	return math.Sqrt( dx*dx + dy*dy )}

在这种情况下,main()函数仅计算两种完全不同的距离类型:

func main() {	atlanta := GeospatialData{33.753746, -84.386330}	distance := atlanta.calculateDistance(33.957409, -83.376801) // to Athens, GA	fmt.Printf("The Haversine distance between Atlanta, GA and Athens, GA is %v\n", distance)	pointA := CartesianPlaneData{1, 1}	distanceA := pointA.calculateDistance(5, 5) 	fmt.Printf("The Pythagorean distance from (1,1) to (5,5) is %v\n", distanceA)}

当然,这是一个非常简单的示例,仅将结构强制应用到您的结构上。 但是,与Python相比,我的选择受到限制:

class CoordinateData:    def calculateDistance(self, latTo, lonTo):        pass class GeospatialData(CoordinateData):    def __init__(self, lat, lon):        self.lat = lat        self.long = lon    def calculateDistance(self, latTo, lonTo):        # Haversine goes here :)        return 95.93196816811724class CartesianPlaneData(CoordinateData):    def __init__(self, x, y):        self.x = y        self.x = y    # Let's not implement calculateDistance()if __name__ == "__main__":    atlanta = GeospatialData(33.753746, -84.386330)    distance = atlanta.calculateDistance(33.957409, -83.376801) # to Athens, GA    print('The Haversine distance between Atlanta, GA and Athens, GA is {}'.format(distance))    pointA = CartesianPlaneData(1,1)    distanceA = pointA.calculateDistance(5, 5)    print('The Pythagorean distance from (1,1) to (5,5) is {}'.format(distanceA))    print('pointA is of type {}'.format(pointA.__class__.__bases__))

这是有效的Python-CartesianPlaneData是CoordinateData的子类(而不是接口-Python使用Duck类),因此,仅使用不带返回类型(参见上面的静态类型与动态类型)的calculateDistance方法即可运行并返回:

python3 interface_example.py
The Haversine distance between Atlanta, GA and Athens, GA is 95.93196816811724
The Pythagorean distance from (1,1) to (5,5) is None
pointA is of type (<class '__main__.CoordinateData'>,)

Python仍然允许接口抽象,并且您绝对可以使用类来定义层次结构和逻辑,正如我对下面的PluginInterceptor所做的那样,以确定自定义插件是否是已定义基类的一部分; 但是,在运行时不会强制执行此操作,如果执行不正确,可能会令人瞠目结舌。

class PluginInterceptor:    """Loads all allowed plugins, when they are a subclass of `BasePlugin` and have the constant `name` set (not `__name__`)    """    def __init__(self):        self.cls = BasePlugin        self.allowed_plugins = self.__load__allowed_plugins__()            def __get_all_subclasses__(self, cls):        return set(cls.__subclasses__()).union(            )         def __load__allowed_plugins__(self):        __allowed_plugins__ = {}        for cls in self.__get_all_subclasses__(self.cls):            if cls.name:                __allowed_plugins__ = cls        return __allowed_plugins__

https://github.com/otter-in-a-suit/scarecrow/blob/master/plugin_base/interceptor.py#L5

您还将在下面的Mandelbrot示例中找到使用的结构示例。

指针

go知道指针,但没有指针算术。 go中的指针用于按指针传递操作,而不是按值传递。 我不会过多地介绍这个例子,并为您提供有关Python的精彩文章。

package mainimport "fmt"func main() {	i, j := 42, 2701	p := &i         // point to i	fmt.Println(*p) // read i through the pointer	*p = 21         // set i through the pointer	fmt.Println(i)  // see the new value of i	p = &j         // point to j	*p = *p / 37   // divide j through the pointer	fmt.Println(j) // see the new value of j}

(来自https://tour.golang.org/moretypes/1)

我的简短总结是:作为一名开发人员,我可以控制我是否要使用标准的C样式行为-传递值-还是仍然按值传递的指针-但在这种情况下 ,指向作为函数参数的值的指针。

与Mandelbrot集并发

简单,开箱即用,易于使用的并发性是最精彩的事情之一。 从同步到并发,总共需要两个字母-go-。

让我们使用一种"令人满意的并行"算法,即Mandelbrot集。

Mandelbrot集是复数c的集合,当从z = 0迭代时,函数f_c(z)=z²+ c不会发散,即序列f_c(0,f_c(f_c(0)), 等等,仍然以绝对值为界。

https://en.wikipedia.org/wiki/Mandelbrot_set

> Output of the Mandelbrot algorithm; CC BY-SA 3.0

Python

我们先来看一下Python。

Python提供了多种表示并发的方法-线程,多处理,子进程,并发。仅举几例-但是选择正确的方法并编写清晰的代码是一个挑战。 请允许我引用文档:

本章描述的模块为并发执行代码提供支持。 适当的工具选择取决于要执行的任务(CPU约束与IO约束)和首选的开发方式(事件驱动的协作式多任务处理与抢占式多任务处理)https://docs.python.org/3/library/concurrency。 html

我不会详细介绍什么情况下是正确的选择,Global Python Interpreter(GIL)如何对其产生影响或一切如何在幕后进行,因为有数百篇关于该主题的精彩文章可以轻松地找到。 在网上找到。 但是,我想重点介绍的是代码样式,易用性和性能。

单线程

可以这样表示(由danyaal进行,并由您进行实际调整):

请记住,有更多,更快,经过优化的版本,但是也更加复杂。 以下示例应该简单明了,并且可以在go和Python之间以几乎1:1的比例进行翻译

首先,我们定义算法的迭代。 这是可以并行运行的部分,我们将在稍后介绍。

import numpy as npimport matplotlib.pyplot as plt# counts the number of iterations until the function diverges or# returns the iteration threshold that we check untildef countIterationsUntilDivergent(c, threshold):    z = complex(0, 0)    for iteration in range(threshold):        z = (z*z) + c        if abs(z) > 4:            break            pass        pass    return iteration

下一个功能是一个整体,但最后只创建实轴和虚轴,将它们分配给二维数组,然后运行循环。

def mandelbrot(threshold, density):    # location and size of the atlas rectangle    # realAxis = np.linspace(-2.25, 0.75, density)    # imaginaryAxis = np.linspace(-1.5, 1.5, density)    realAxis = np.linspace(-0.22, -0.219, 1000)    imaginaryAxis = np.linspace(-0.70, -0.699, 1000)    realAxisLen = len(realAxis)    imaginaryAxisLen = len(imaginaryAxis)    # 2-D array to represent mandelbrot atlas    atlas = np.empty((realAxisLen, imaginaryAxisLen))    print('realAxisLen: {}, imaginaryAxisLen: {}'.format(realAxisLen, imaginaryAxisLen))        # color each point in the atlas depending on the iteration count    for ix in range(realAxisLen):        for iy in range(imaginaryAxisLen):            cx = realAxis            cy = imaginaryAxis            c = complex(cx, cy)            atlas = countIterationsUntilDivergent(c, threshold)            pass        pass    return atlas.T

计算发生在单个线程上,如下所示:

> htop

多线程

现在,要在多个线程中运行它,我们可以使用多处理模块并像这样运行它。

可以简化calc_row()函数,但它表明我们在做不同的工作:逐行计算图像,而不是一次计算一个点。

import multiprocessing as mpimport itertoolsdef calc_row(cx, cy, threshold=120):    c = complex(cx<1>, cy<1>)    return (cx<0>, cy<0>, countIterationsUntilDivergent(c, threshold))

接下来,我做出了一个有问题的决定,即使用starmap和Pool简化循环,将嵌套循环的排列直接作为参数。

换句话说,无论我们为进程池提供多少个进程,我们都在运行calc_row(cx,cy,threshold)。 多重处理库负责分别从列表或迭代器传递参数。

我们还返回了一个看起来很时髦的元组,因此我们可以跟踪图像中的索引。

def mandelbrot_multi(threshold, density, cpus=4):    realAxis = np.linspace(-0.22, -0.219, 1000)    imaginaryAxis = np.linspace(-0.70, -0.699, 1000)    realAxisLen = len(realAxis)    imaginaryAxisLen = len(imaginaryAxis)    atlas = np.empty((realAxisLen, imaginaryAxisLen))        # Create list of permutations    realAxis = <(i,e ) for i,e in enumerate(realAxis)>     imaginaryAxis = <(i,e ) for i,e in enumerate(imaginaryAxis)>     paramlist = list(itertools.product(realAxis, imaginaryAxis))    paramlist = list(map(lambda t: t + (threshold,),paramlist))        # Create a multiprocessing pool    pool = mp.Pool(cpus)        n = pool.starmap(calc_row, paramlist)    pool.close()    pool.join()    return n, atlas

哪个更巧妙地使用了我们的可用资源:

> htop

从性能的角度来看,我们正在考虑单个CPU上的8.4s和2.53s并发,由于使用了多处理模块,因此显着增加了内存开销。

当然,有很多不同的方法可以加快这一步-Cython,优化的numpy,tensorflow等,但就现成的并发性而言,让我们进行比较。 我不擅长选择示例,分形很漂亮。 :)

Go

让我们看看它的外观。

单线程

大多数go实现都使用Image包在go中生成图像-这对于一个独立的项目是有意义的。 但是,在这里,我将数组写入磁盘并以numpy和Python读取,以使代码简洁。 Python和go的性能数字只是计算,而不是I / O或绘制像素!

首先,我们导入所需的程序包并编写一个np.linespace()等效项,以指定间隔返回相等间隔的数字。

package mainimport (	"bytes"	"fmt"	"log"	"math/cmplx"	"os"	"strings"	"time"	"encoding/binary")func linspace(start, end float64, num int) <>float64 {	result := make(<>float64, num)	step := (end - start) / float64(num-1)	for i := range result {		result = start + float64(i)*step	}	return result}

代码的其余部分应该看起来很熟悉-请注意非常具体的强类型数据类型和返回类型。

func countIterationsUntilDivergent(c complex128, threshold int64) int64 {	z := complex(0, 0)	var ix int64 = 0	for i := int64(0); i < threshold; i++ {		ix = i		z = (z * z) + c		if cmplx.Abs(z) > 4 {			return i		}	}	return ix}func mandelbrot(threshold, density int64) <><>int64 {	realAxis := linspace(-0.22, -0.219, 1000)	imaginaryAxis := linspace(-0.70, -0.699, 1000)	fmt.Printf("realAxis %v\n", len(realAxis))	fmt.Printf("imaginaryAxis %v\n", len(imaginaryAxis))	atlas := make(<><>int64, len(realAxis))	for i := range atlas {		atlas = make(<>int64, len(imaginaryAxis))	}	fmt.Printf("atlas %v\n", len(atlas))	for ix, _ := range realAxis {		for iy, _ := range imaginaryAxis {			cx := realAxis			cy := imaginaryAxis			c := complex(cx, cy)			//fmt.Printf("ix, iy: %v %v\n", ix, iy)			atlas = countIterationsUntilDivergent(c, threshold)		}	}	return atlas}

多线程

Go通过使用称为goroutines的概念使这一过程变得容易得多。 无需处理Python mutltiprocessing模块,池,地图与星图以及Python解释器的复杂性,我们只需使用go指令即可。

正如我之前提到的,这里的代码故意简单,可以进行简单的优化,但是我试图使go代码尽可能接近Python代码。 请原谅任何简化。

首先,我们将使用Python重新创建calc_row方法,这一次使用一个结构返回索引和值,因为我们将在第二个通道中使用的通道不会采用多种返回类型:

type triple struct {	ix, iy int64	c      int64}func calcRow(ix, iy int64, c complex128, threshold int64) triple {	return triple{ix, iy, countIterationsUntilDivergent(c, threshold)}}

我们的主要功能将使用2个概念:通道和上述goroutine。

goroutine有一个简单的模型:它是在相同地址空间中与其他goroutine同时执行的函数。 go docs将其与Unix shell&运算符进行了比较,我发现这是一个很好的类比。

我们正在使用的通道是一个缓冲通道,它充当并发功能的管道,因为非缓冲通道本质上是阻塞的。

这就导致了下面的代码包装了内部循环(请参见上文,以了解微不足道的优化及其在此处的不足—我相信,即使在goroutine中指向WaitGroup的指针和较小的通道缓冲区也可以加快此速度,但是我没有 尚未在goroutine中对其进行测试。

func mandelbrot(threshold, density int64) <><>int64 {	realAxis := linspace(-0.22, -0.219, 1000)	imaginaryAxis := linspace(-0.70, -0.699, 1000)	atlas := make(<><>int64, len(realAxis))	for i := range atlas {		atlas = make(<>int64, len(imaginaryAxis))	}	// Make a buffered channel	ch := make(chan triple, int64(len(realAxis))*int64(len(imaginaryAxis)))	for ix, _ := range realAxis {		go func(ix int) {			for iy, _ := range imaginaryAxis {				cx := realAxis				cy := imaginaryAxis				c := complex(cx, cy)				res := calcRow(int64(ix), int64(iy), c, threshold)				ch <- res			}		}(ix)	}	for i := int64(0); i < int64(len(realAxis))*int64(len(imaginaryAxis)); i++ {		select {		case res := <-ch:			atlas = res.c		}	}	return atlas}

现在,我们开始在单个CPU上查看0.38s,并在代码中同时查看0.18s,尽管类似,但更为简洁。

最终表现

我只是要离开这里。 正如我之前概述的那样,go和Python代码都可以进一步优化,但是我们仍然可以将速度提高约45倍

> Generated using matplotlib; CC BY-SA 3.0

现实生活中的注意事项

谈论理论概念可能很有趣,但这只是难题的一小部分。 虽然我确定2020年将是Haskell广泛用于生产用途的一年,但我选择,使用和推荐人们学习语言的方法主要是基于现实生活的使用,而不是学术理想。

语言受欢迎程度

这总是很有趣的。

根据StackOverflow,与go相关的问题的普及程度甚至无法与Python的主要参与者接近。

> https://insights.stackoverflow.com/trends?tags=go%2Cpython

与其他一些大大小小的参与者(Java,Haskell,Scala和Lisp)进行比较,结果相似:

> https://insights.stackoverflow.com/trends?tags=go%2Cpython%2Cjava%2Cscala%2Chaskell%2Clisp

现在:"问问题的百分比"是一个很好的指标吗? 可能不是。 Python是一种非常流行的语言,很容易学习-我本人只是为它准备培训,并且自然会吸引大量的初学者和经验丰富的专业人员。

Google趋势显示了类似的故事:

> https://trends.google.com

(红色-Python;蓝色-go)

我认为可以说Python更加流行了-但是那行话至少有一个利基,如果不是一个上升的轨迹的话。 借助欺骗性可视化功能,我们可以放大上面的StackOverflow图:

> https://insights.stackoverflow.com/trends?tags=go

并且确实要找到向上的轨迹。

此外,如果可以相信StackOverflow的年度开发人员调查,那么当被问及"最受欢迎的技术"时,该比率将从2018年的7.2%上升到2019年的8.8%,到2020年上升到9.4%。

根据同一项调查,美国围棋程序员的年薪也排在第二位(高于2019年的第三名),年薪为140,000美元,仅次于Scala(150,000美元)。

还有希望! :)

生态系统

解释前两张图的部分原因是Python的生态系统非常广泛-后端,前端,统计,机器学习,深度学习,图分析,GIS,机器人技术-它在那里,它将有成千上万的贡献者和数十或数百个 数以千计的用户。

有一些等效项,我尝试对它们进行总结,以及它们在GitHub上的相对受欢迎程度:

> Links here: https://chollinger.com/blog/2020/06/a-data-engineering-perspective-on-go-vs.-python-pa

显然,至少从数据工程和数据科学的角度来看,go生态系统还有很长的路要走。

<1> Apache Beam不能替代Spark

学习曲线

这是主观的-我个人发现Python仅在表面上更容易学习。 如果您确实想了解基础概念,体系结构和库,那么Python确实是一个兔子洞,与Java不同。

另一方面,go是一种相当简单的语言,专注于某些元素。 请允许我引用:

在设计Go时,至少在Google来说,Java和C ++是编写服务器的最常用语言。 我们认为这些语言需要太多的簿记和重复。 一些程序员的反应是转向效率更高,更流畅的语言(如Python),以效率和类型安全为代价。 我们认为用一种语言就能具有效率,安全性和流动性。

Go尝试减少两种词义的打字量。 在整个设计过程中,我们试图减少混乱和复杂性。 没有前向声明,也没有头文件; 一切都只被声明一次。 初始化具有表现力,自动且易于使用。 关键字语法简洁明了。 口吃(foo.Foo * myFoo = new(foo.Foo))通过使用:=声明并初始化构造简单地派生而减少。 也许最根本的是,没有类型层次结构:类型就是,它们不必宣布其关系。 这些简化使得Go可以表达而又易于理解,而不会牺牲复杂性。

另一个重要原则是保持概念正交。 可以为任何类型实现方法; 结构代表数据,而接口代表抽象; 等等。 正交性使人们更容易理解事物组合时会发生什么。

https://golang.org/doc/faq#creating_a_new_language

快速浏览一下例如LinkedIn学习,总共显示了4门go语言课程和168门Python语言课程。

以我自己的经验,最有用的事情是通过示例进行操作并实际阅读文档。 另一方面,Python是来自像我,大学和大公司这样的人的更广泛的外部教程,课程,认证,博客文章<&mldr;>。

查询" go language tutorial"在Google上返回了17亿结果,而" python language tutorial"的结果为1620亿

Apache Spark

现在,我们已经全面讨论了go vs Python。 但是,这篇文章的揭幕战如何-Beam和Dataflow从何而来?

本节将简短介绍本文的第2部分

通常,beam go SDK确实提供了运行相对简单的工作所需的核心功能。 但是,在撰写本文时(2020-06-11),它的确存在缺点。

转变

让我们看一下Beam各种语言的可用转换。 我已经挑选了几对樱桃,但可以随时参考总体文档以获取完整图片:

> https://chollinger.com/blog/2020/06/a-data-engineering-perspective-on-go-vs.-python-part-1/#transf

对于使用Python的人们来说,最显着的不同之处在于管道的整体外观。

这是在Python中完成的操作(注意:没有什么阻止您调用data.apply(func)的方法,因为运算符只是重载了):

class CountWords(beam.PTransform):  def expand(self, pcoll):    return (        pcoll        # Convert lines of text into individual words.        | 'ExtractWords' >>        beam.FlatMap(lambda x: re.findall(r'+', x))        # Count the number of times each word occurs.        | beam.combiners.Count.PerElement())counts = lines | CountWords()

在运行中,它看起来更像是常规的旧函数:

func CountWords(s beam.Scope, lines beam.PCollection) beam.PCollection {	s = s.Scope("CountWords")	// Convert lines of text into individual words.	col := beam.ParDo(s, extractFn, lines)	// Count the number of times each word occurs.	return stats.Count(s, col)}

我们将在第2部分中详细探讨这些细节。

输入输出

I / O可能是go sdk中最有限的部分,因为很多连接器都不可用。

请查看此链接以获取最新概述。

> https://beam.apache.org/documentation/io/built-in/

如果我必须给出一个总结:请继续支持基本的Google Cloud服务和本地开发,而Java几乎涵盖了所有情况。 <2>

<2>请记住,某些连接器(例如DatabaseIO)本质上是特定于语言的

运行程序

最后,寻找可用的运行程序,go或多或少地局限于Direct和Dataflow,这与我在I / O上的声明是一致的。

> https://chollinger.com/blog/2020/06/a-data-engineering-perspective-on-go-vs.-python-part-1/#runner

(是)表示这些运行程序有局限性

逐行示例

我建议通过WordCount。

WordCount是一个很好的例子,它演示了以下概念:

· 创建管道

· 将转换应用于管道

· 读取输入

· 应用ParDo变换

· 应用SDK提供的转换

· 写入输出(在此示例中:写入文本文件)

· 运行管道

为了简洁起见,目前我将不赘述这些细节。

结论

首先要提出的问题之一是:比较这两种语言是否有意义? 我的回答可能很明显-尽管go可能是为不同的用例(通用脚本和机器学习用例与系统/"云"编程)设计的,但从数据工程的角度来看,上面概述的概念仍然让我兴奋 。

go有很多概念,可以在没有大量pip依赖关系树的情况下工作,产生干净的代码,易于编译,非常快,并且(在我看来)对于将来的数据和ML用例具有很大的潜力。

总结:go和Python显然是非常不同的语言-希望我在上面精选的示例中成功地概述了该语言。

下一步

正如我在本文中仅介绍了Apache Beam的内容一样,下一篇将重点讨论以下问题,以将Dataflow用作"狂奔"的真实示例:

· go Beam SDK的成熟程度如何?

· 它支持什么? 有什么不见了?

· 基本区别是什么?

· (如何)我们可以在GCP上运行数据流作业?

所有开发和基准测试都是在GNU / Linux ,在2019年的System76 Gazelle笔记本电脑上具有12个Intel i7–9750H vCore @ 4.5Ghz和16GB RAM

最初于2020年6月11日发布在https://chollinger.com上。

(本文翻译自Christian Hollinger的文章《A Data Engineering Perspective on Go vs. Python (Part 1)》,参考:https://towardsdatascience.com/a-data-engineering-perspective-on-go-vs-python-part-1-5dfb8bc08e7)

2024-05-10

后面没有了,返回>>电动车百科