利用配有 Go* 编程语言的英特尔® 数据分析加速库 (英特尔® DAAL)支持批量、在线和分布式处理
最热门的现代基础设施项目均Go*,包括 Kubernetes*、Docker*、Consul*、etcd* 等。Go 成为了广泛应用于开发运营、web 服务器和微服务的 go to语言。它简单易学、便于快速部署,还为开发人员提供了一套出色的工具。
由于业务越来越受数据驱动,需要在公司基础设施的不同层次集成计算密集型算法,包括应用 Go 的层级。因此,有必要关注如何将机器学习、分布式数据转换和在线数据分析集成至蓬勃发展的基于 Go 的系统中。
为了在 Go 中提供高性能、可扩展的强大数据处理,可以在 Go 程序中使用英特尔® 数据分析加速库(英特尔® DAAL)。该库为一系列实用任务提供现成的批量、在线和分布式算法:
由于 Go 实现了与 C/C++ 的良好交互,我们可以将这个功能轻松地添加至 Go 程序,通过利用面向即购即用架构而优化的英特尔库来实现。如此处所示,进行某些操作时(如主组件分析),英特尔 DAAL 的速度比 Spark* 和 MLlib* 的速度高出 7 倍。哇!我们要抓紧时间探索如何利用上述性能升级 Go 应用。
安装英特尔® DAAL
英特尔 DAAL 以开源方式提供,可以通过以下指令安装。Linux* 设备上的指令非常简单:
- 下载开源代码。
- 运行安装脚本。
- 设置必要的环境变量(可以通过给出的 shell 脚本设置)。
将英特尔 DAAL 集成至 Go 程序前,请确保一切正常运行。根据英特尔 DAAL 文档中的各种入门指南进行操作。具体而言,这些入门指南提供面向 Cholesky 分解的示例英特尔 DAAL 应用,随后我们将利用 Go 重新创建 Cholesky 分解。Cholesky 分解的原始 C++ 示例如下所示:
/******************************************************************************* !版权所有 © 2014-2017,英特尔公司。保留所有权利。 ! !本文所含的源代码、信息和材料 !归英特尔公司及其供应商或许可方所有, !材料的所有权仍归英特尔及其供应商或许可方所有。材料 !包含了英特尔及其供应商或许可方的 !专有信息。这些材料受全球版权法律和条约条款 !的保护。不经英特尔事先书面许可,不得以任何形式使用、复制、翻制、 !修改、出版、上传、张贴、传输、发布或公开 !这些材料。本材料 !未通过明确、隐含、诱导、禁止反言或其它任何方式, !授予您任何专利、版权或其它知识产权 !的许可。任何知识产权 !的授权必须得到英特尔 !的书面批准。 ! !*其他的名称和品牌可能是其他所有者的资产。 ! !除非经过英特尔的书面同意认可,不得以任何方式移除或更改 !本声明或英特尔及其供应商或许可方材料中的 !其他声明。 ! !******************************************************************************* !内容: ! Cholesky 分解示例程序。 !******************************************************************************/ #include "daal.h" #include <iostream> using namespace daal; using namespace daal::algorithms; using namespace daal::data_management; using namespace daal::services; const size_t dimension = 3; double inputArray[dimension *dimension] = { 1.0, 2.0, 4.0, 2.0, 13.0, 23.0, 4.0, 23.0, 77.0 }; int main(int argc, char *argv[]) { /* Create input numeric table from array */ SharedPtr<NumericTable> inputData = SharedPtr<NumericTable>(new Matrix<double>(dimension, dimension, inputArray)); /* Create the algorithm object for computation of the Cholesky decomposition using the default method */ cholesky::Batch<> algorithm; /* Set input for the algorithm */ algorithm.input.set(cholesky::data, inputData); /* Compute Cholesky decomposition */ algorithm.compute(); /* Get pointer to Cholesky factor */ SharedPtr<Matrix<double> > factor = staticPointerCast<Matrix<double>, NumericTable>(algorithm.getResult()->get(cholesky::choleskyFactor)); /* Print the first element of the Cholesky factor */ std::cout << "The first element of the Cholesky factor:"<< (*factor)[0][0]; return 0; }
尝试进行编译和运行,确保成功安装了英特尔 DAAL,还让您提前体验了一次 Go 中的操作。如果对英特尔 DAAL 安装有任何问题,可以在英特尔 DAAL 论坛中讨论,对我而言,它是宝贵的资源,有助于熟练使用英特尔 DAAL。
在 Go 中使用英特尔 DAAL
可以利用以下几种方式在 Go 中使用英特尔 DAAL:
- 利用包装函数从 Go 程序中直接调用英特尔 DAAL。
- 创建一个包装特定英特尔 DAAl 功能的可复用库。
下面将展示两种方法,访问此处查看本文使用的全部代码。这只是一个示例,建议最终为存储库添加更多的 Go 和 英特尔 DAAL 示例。试验完毕后,提交您的 Pull 请求。我非常期待看到您的成果!
如果您刚刚接触 Go,可以在阅读本教程前,花点时间熟悉 Go。事实上,本地安装 Go 不是学习 Go 的必要条件。您可以参加在线 Go 概览课程,并使用< a href="https://play.golang.org/" target="_blank">Go Playground,一切就绪后,本地安装 Go。
从 Go 中直接调用英特尔 DAAL
Go 实际提供一款称为 cgo的工具,生成调用 C 代码的 Go 程序包。在本例中,利用 cgo 实现 Go 程序与英特尔 DAAL 的互操作。
请注意:互联网上深入讨论了在 Go 程序中使用 cgo 的利弊(特别建议您查看 Dave Cheney 的讨论和 Cockroach 实验室* 撰写的一篇文章)。选择 cgo 之前,您应该考虑这些成本,至少知道成本的存在。在本示例中,为了利用高度优化的分布式英特尔 DAAL 库,我们乐意寻找均衡利用 cgo 的方法,极有可能改善特定的数据密集型或计算密集型使用案例。
为了在示例 Go 程序中集成英特尔 DAAL Cholesky 分解功能,需要创建以下目录结构(在 $GOPATH中):
cholesky`
├── cholesky.go`
├── cholesky.hxx`
└── cholesky.cxx`
cholesky.go
文件就是利用英特尔 DAAL Cholesky 分解功能的 Go 程序。cholesky.cxx
和 cholesky.hxx
文件是包含英特尔 DAAL 的 C++ 定义/声明文件,将等待包装的英特尔 DAAL 功能的信号传输给 cgo。让我们逐一查看。
首先查看 *.cxx
文件:
#include "cholesky.hxx" #include "daal.h" #include <iostream> using namespace daal; using namespace daal::algorithms; using namespace daal::data_management; using namespace daal::services; int choleskyDecompose(int dimension, double inputArray[]) { /* Create input numeric table from array */ SharedPtr<NumericTable> inputData = SharedPtr<NumericTable>(new Matrix<double>(dimension, dimension, inputArray)); /* Create the algorithm object for computation of the Cholesky decomposition using the default method */ cholesky::Batch<> algorithm; /* Set input for the algorithm */ algorithm.input.set(cholesky::data, inputData); /* Compute Cholesky decomposition */ algorithm.compute(); /* Get pointer to Cholesky factor */ SharedPtr<Matrix<double> > factor = staticPointerCast<Matrix<double>, NumericTable>(algorithm.getResult()->get(cholesky::choleskyFactor)); /* Return the first element of the Cholesky factor */ return (*factor)[0][0]; }
然后查看 *.hxx
文件:
#ifndef CHOLESKY_H #define CHOLESKY_H // __cplusplus gets defined when a C++ compiler processes the file. // extern "C" is needed so the C++ compiler exports the symbols w/out name issues. #ifdef __cplusplus extern "C" { #endif int choleskyDecompose(int dimension, double inputArray[]); #ifdef __cplusplus } #endif #endif
这些文件定义了一个以 C++ 编写的 choleskyDecompose 包装程序函数,该函数利用英特尔 DAAL Cholesky 分解功能计算输入矩阵的 Cholesky 分解,并输出 Cholesky 因数的第一个因素(和英特尔 DAAL 入门指南的内容相似)。请注意,在本示例中,输入阵列的长度等于矩阵的长度(即 3 x 3 矩阵与长度为 9 的输入阵列对应)。需要将 extern “C”
置于 *.hxx
文件中。这样做将使 cgo 调用的 C++ 编译器知道我们需要导出 C++ 文件中定义的相关名称。
在 *.cxx
和 *.hxx
文件中定义 Cholesky 分解包装程序函数后,可以从 Go 中直接调用该函数。cholesky.go
如下所示:
package main // #cgo CXXFLAGS:-I$DAALINCLUDE // #cgo LDFLAGS:-L$DAALLIB -ldaal_core -ldaal_sequential -lpthread -lm // #include "cholesky.hxx" import "C" import ( "fmt""unsafe" ) func main() { // Define the input matrix as an array. inputArray := [9]float64{ 1.0, 2.0, 4.0, 2.0, 13.0, 23.0, 4.0, 23.0, 77.0, } // Get the first Cholesky decomposition factor. data := (*C.double)(unsafe.Pointer(&inputArray[0])) factor := C.choleskyDecompose(3, data) // Output the first Cholesky dcomposition factor to stdout. fmt.Printf("The first Cholesky decomp. factor is:%d\n", factor) }
下面将概述各个步骤,介绍其中的原理。首先告知 Go 需要在编译程序时使用 cgo 以及利用特定的标记编译:
// #cgo CXXFLAGS:-I$DAALINCLUDE // #cgo LDFLAGS:-L$DAALLIB -ldaal_core -ldaal_sequential -lpthread -lm // #include "cholesky.hxx" import "C"
为了使用 cgo,需要导入“C”
,C 是一个伪程序包,利用 C 通知 Go 我们正在使用cgo。如果导入“C”后立即出现了一条注释(被称为引言),编译程序包的 C++ 部件时将注释用作标头。
利用 CXXFLAGS 和 LDFLAGS 指定编译过程中 cgo 使用的编译和链接标记,可以通过 // #include "cholesky.hxx”
添加 C++ 函数。我使用安装 gcc 的 Linux 编译本示例,因此,使用上文中给出的标签。您也可以依据 本指南决定如何将应用连接至英特尔 DAAL。
然后,编写 Go 代码(和其他程序的编写方式一样),利用 C.choleskyDecompose()
访问包装函数:
// Define the input matrix as an array. inputArray := [9]float64{ 1.0, 2.0, 4.0, 2.0, 13.0, 23.0, 4.0, 23.0, 77.0, } // Get the first Cholesky decomposition factor. data := (*C.double)(unsafe.Pointer(&inputArray[0])) factor := C.choleskyDecompose(3, data) // Output the first Cholesky dcomposition factor to stdout. fmt.Printf("The first Cholesky decomp. factor is:%d\n", factor)
使用 cgo 的一个独有特性是需要将 float64 片段的第一个要素指示器转换为不安全指示器,继而面向 choleskyDecompose
函数明确转换为 *C.double
(兼容 C++)指示器。不安全包,顾名思义,帮助我们绕过 Go 程序的类型安全。
非常好!现在,我们有了一个调用英特尔 DAAL Cholesky 分解的 Go 程序。现在我们开始创建并运行这个程序。像往常一样,利用 go build
创建:
$ ls cholesky.cxx cholesky.go cholesky.hxx $ go build $ ls cholesky cholesky.cxx cholesky.go cholesky.hxx $ ./cholesky The first Cholesky decomp. factor is:1 $
我们获得了预期的输出!事实上,第一个 Cholesky 分解因数为 1。我们成功了,可以从 Go 中直接利用英特尔 DAAL 的功能!但是,由于不安全包和 C 位,这个 Go 程序有点与众不同。而且,它属于一次性解决方案。现在,为了使我们的 Go 程序包和其他 Go 程序包一样能够导入,需要将它转换为可复用 Go 程序包。
利用英特尔 DAAL 创建可复用 Go 程序包
为了创建包装英特尔 DAAL 功能的 Go 程序包,需要使用一种名为 SWIG*的工具。除 cgo 以外,Go 具有在构建时调用 SWIG 的功能,编译包装 C/C++ 功能的 Go 程序包。为了启用这种构建,需要创建以下目录结构:
choleskylib
├── cholesky.go
├── cholesky.hxx
├── cholesky.cxx
└── cholesky.swigcxx
*.cxx
和 *.hxx
包装程序文件可以同时存在,现在需要添加一个 *.swigcxx
文件,如下所示:
%{ #include "cholesky.hxx" %} %include "cholesky.hxx"
该文件指示 SWIG 工具生成面向 Cholesky 函数的包装代码,可以将代码用作 Go 程序包。
由于现在正在创建可复用 Go 程序包(而非独立的 Go 应用),不需要将 package main 或 function main 纳入 *.go
文件。只需要定义程序包名称。本示例称其为 cholesky
,意味着 cholesky.go
如下所示:
package cholesky // #cgo CXXFLAGS:-I$DAALINCLUDE // #cgo LDFLAGS:-L$DAALLIB -ldaal_core -ldaal_sequential -lpthread -lm import "C"
(再次提供标头标记。)
现在,可以在本地创建并安装程序包:
$ ls cholesky.cxx cholesky.go cholesky.hxx cholesky.swigcxx $ go install $
通过上述操作,创建了所需的全部二进制和库,当 Go 程序使用这个程序包时,可以调用这些二进制和库。Go 会发现目录中存在一个 *.swigcxx
文件,随后它会自动利用 SWIG 创建程序包。
太棒了;我们创建了使用英特尔 DAAL 的 Go 程序包。我们看一下应该如何导入并使用这个程序包:
package main import ( "fmt""github.com/dwhitena/daal-go/choleskylib" ) func main() { // Define the input matrix as an array. inputArray := [9]float64{ 1.0, 2.0, 4.0, 2.0, 13.0, 23.0, 4.0, 23.0, 77.0, } // Get the first Cholesky decomposition factor. factor := cholesky.CholeskyDecompose(3, &inputArray[0]) // Output the first Cholesky dcomposition factor to stdout. fmt.Printf("The first Cholesky decomp. factor is:%d\n", factor) }
好极了!和直接包装英特尔 DAAL 相比,这样看起来整洁多了。按照导入其他 Go 程序包的方式导入 Cholesky 程序包,并将包装函数命名为 cholesky.CholeskyDecompose(...)
。此外,SWIG 帮助我们处理了所有的不安全因素。现在我们可以将原始 float64 片段的第一个元素的地址传输给 cholesky.CholeskyDecompose(...)
。
和其他 Go 程序类似,利用 go build
编译与运行:
$ ls main.go $ go build $ ls example main.go $ ./example The first Cholesky decomp. factor is:1 $
哇!答案正确。现在可以在另一个需要 Cholesky 分解的 Go 程序中使用这个程序包。
结论/资源
利用英特尔 DAAL、cgo 和 SWIG,能够将经过优化的 Cholesky 分解集成至 Go 程序中。但是,这项技术不仅限于 Cholesky 分解,您可以创建 Go 程序和程序包,使它们以同样的方式利用英特尔 DAAL 中的任何算法。这意味着您可以在 Go 应用中实施批量、在线和分布式神经网络、集群、加速、协同过滤等各种操作。
如欲获取本文使用的全部代码,请访问此处。
Go 数据资源:
- 请加入 Gophers on Slack,在#数据科学频道与利用 Go 从事大数据、数据分析、机器学习研究的专业人士进行交流。
- 请查看 GopherData organization,它聚集了一批 Go 数据管理、处理、分析、机器学习和可视化工具领域的用户和开发人员。
- 请在 Twitter 上关注GopherData。
- 请浏览(并维护)不断更新的与数据相关的 Go 工具列表。
英特尔 DAAL 资源:
- 请查看英特尔 DAAL 文档。
- 请充分利用在线培训。
- 邀您参加英特尔 DAAL 论坛。
关于作者
Daniel (@dwhitena) 是一名拥有博士学位的数据科学家,目前就职于 Pachyderm (@pachydermIO)。Daniel 开发了创新型、分布式数据管线,包含预测模型、数据可视化、统计分析等。他在全球各大会议上发表演讲(ODSC、Spark 峰会、Datapalooza、DevFest Siberia、GopherCon等),在 Ardan 研究院 (@ardanlabs) 教授数据科学/工程,面向 Jupyter 维护 Go 内核,并积极支持各种开源数据科学项目的筹办。