Go 处理大数据。
借助 Intel DAAL、cgo 和 SWIG,我们能够将优化的 Cholesky 分解直接集成到我们的 Go 程序中。
将 Intel® 数据分析加速库 (Intel® DAAL) 与 Go* 编程语言结合使用,以实现批处理、在线和分布式处理
最热门的现代基础设施项目都由 Go* 提供支持,包括 Kubernetes*、Docker*、Consul*、etcd* 等等。Go 正在成为 devops、web 服务器和微服务的首选语言。它易于学习、易于部署、速度快,并为开发人员提供了一套出色的工具。
但是,随着业务变得越来越数据驱动,需要在公司基础设施的各个层面集成计算密集型算法,包括 Go 发挥作用的那些层面。因此,很自然地会问,我们如何才能将机器学习、分布式数据转换和在线数据分析等功能集成到我们蓬勃发展的基于 Go 的系统中。
在 Go 中提供强大、高性能和可扩展数据处理的一种途径是在我们的 Go 程序中利用 Intel® 数据分析加速库 (Intel® DAAL)。该库已经为大量有用的任务提供了批处理、在线和分布式算法
由于 Go 提供了一种与 C/C++ 接口的便捷方式,我们可以毫不费力地将此功能引入我们的 Go 程序中。通过这样做,我们可以直接利用 Intel 对这些库的架构优化。正如此处所示,对于某些操作(例如主成分分析),Intel DAAL 比 Spark* 加 MLlib* 快七倍。哇!我想是时候探索如何利用这种强大的功能来提升我们的 Go 应用程序了。
安装 Intel® DAAL
Intel DAAL 作为开源提供,可以按照这些说明进行安装。在我的 Linux* 机器上,这就像以下操作一样简单
- 下载源代码。
- 运行安装脚本。
- 设置必要的环境变量(也可以使用提供的 shell 脚本完成)。
在尝试将 Intel DAAL 集成到任何 Go 程序之前,最好确保一切正常工作。您可以通过遵循 Intel DAAL 文档中各种入门指南来完成此操作。具体来说,这些入门指南提供了一个 Cholesky 分解的 Intel DAAL 应用程序示例,我们将在下面用 Go 重新创建它。Cholesky 分解的原始 C++ 示例如下所示
/*******************************************************************************
! Copyright(C) 2014-2017 Intel Corporation. All Rights Reserved.
!
! The source code, information and material ("Material") contained herein is
! owned by Intel Corporation or its suppliers or licensors, and title to such
! Material remains with Intel Corporation or its suppliers or licensors. The
! Material contains proprietary information of Intel or its suppliers and
! licensors. The Material is protected by worldwide copyright laws and treaty
! provisions. No part of the Material may be used, copied, reproduced,
! modified, published, uploaded, posted, transmitted, distributed or disclosed
! in any way without Intel's prior express written permission. No license
! under any patent, copyright or other intellectual property rights in the
! Material is granted to or conferred upon you, either expressly, by
! implication, inducement, estoppel or otherwise. Any license under such
! intellectual property rights must be express and approved by Intel in
! writing.
!
! *Third Party trademarks are the property of their respective owners.
!
! Unless otherwise agreed by Intel in writing, you may not remove or alter
! this notice or any other notice embedded in Materials by Intel or Intel's
! suppliers or licensors in any way.
!
!*******************************************************************************
! Content:
! Cholesky decomposition sample program.
!******************************************************************************/
#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;
}
尝试编译并运行此代码以确保您的 Intel DAAL 安装成功。它还将让您了解我们将在 Go 中做什么。有关 Intel DAAL 安装的任何问题都可以在 Intel DAAL 论坛中讨论(这对我熟悉 Intel DAAL 来说是一个很好的资源)。
在 Go 中使用 Intel DAAL
在 Go 中使用 Intel DAAL 时,我们有几个选项
- 通过包装器函数直接从 Go 程序调用 Intel DAAL。
- 创建一个可重用库,包装特定的 Intel DAAL 功能。
我将在下面演示这两个选项,所有使用的代码都可以在此处找到。这只是一个示例,最终,将更多 Go 加 Intel DAAL 示例添加到此存储库中会很棒。在您实验时,请提交您的 Pull Request。我很高兴看到您创造了什么!
如果您是 Go 新手,则应在继续本教程之前先熟悉一下。事实上,您无需在本地安装 Go 即可开始学习。您可以参加在线Go 之旅并使用Go Playground,然后当您准备好时,在本地安装 Go。
直接从 Go 调用 Intel DAAL
Go 实际上提供了一个名为 cgo 的工具,它能够创建调用 C 代码的 Go 包。在这种情况下,我们将使用 cgo 使我们的 Go 程序与 Intel DAAL 互操作。
注意:在 Internet 上广泛讨论了使用 cgo 与 Go 程序进行权衡的各种问题(特别是,请参阅 Dave Cheney 的讨论或来自 Cockroach Labs* 的这篇文章)。在选择使用 cgo 时,您应该考虑这些成本,或者至少了解它们。在这种情况下,我们说我们愿意接受 cgo 的权衡,以便利用高度优化和分布式的 Intel DAAL 库,这种权衡在某些数据密集型或计算密集型用例中可能是合理的。
为了将 Intel DAAL Cholesky 分解功能集成到示例 Go 程序中,我们需要创建一个如下所示的目录结构(在我们的 $GOPATH 中)
cholesky` ├── cholesky.go` ├── cholesky.hxx` └── cholesky.cxx`
cholesky.go 文件是我们的 Go 程序,它将利用 Intel DAAL Cholesky 分解功能。cholesky.cxx 和 cholesky.hxx 文件是 C++ 定义/声明文件,它们包含 Intel DAAL 并将向 cgo 发出信号,说明我们要包装哪些 Intel DAAL 功能。让我们看看其中的每一个。
首先,让我们看看 *.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 包装器函数,它利用 Intel DAAL Cholesky 分解功能来计算输入矩阵的 Cholesky 分解并输出 Cholesky 因子的第一个元素(类似于 Intel DAAL 入门指南中所示)。请注意,在这种情况下,我们的输入是一个长度为矩阵维度(即 3 x 3 矩阵将对应于长度为 9 的输入数组)的数组。我们需要在 *.hxx 文件中包含 extern "C"
。这将使 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,我们需要 import "C"
,这是一个伪包,告诉 Go 我们正在使用 cgo。如果 "C" 的导入紧跟在一个注释之后,那么这个被称为前导的注释在编译包的 C++ 部分时用作头文件。
CXXFLAGS 和 LDFLAGS 允许我们指定 cgo 在编译期间要使用的编译和链接标志,我们可以通过 // #include "cholesky.hxx"
包含我们的 C++ 函数。我使用带有 gcc 的 Linux 编译此示例,因此这些标志如上所示。但是,您可以按照 本指南 确定如何将应用程序链接到 Intel 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 切片的第一个元素的指针转换为一个*不安全*的指针,然后可以将其显式转换为 *C.double
(与 C++ 兼容)指针,供我们的 choleskyDecompose
函数使用。unsafe 包,顾名思义,允许我们绕过 Go 程序的类型安全。
好的,太棒了!现在我们有一个调用了 Intel 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 获得了 Intel DAAL 的强大功能!但是,我们的 Go 程序看起来有点奇怪,带有 unsafe 和 C 位。此外,这有点像一次性解决方案。现在,让我们将此功能封装到一个可重用的 Go 包中,我们可以像导入任何其他 Go 包一样导入它。
使用 Intel DAAL 创建可重用 Go 包
为了创建一个包装 Intel 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 应用程序),*.go
文件不需要包含包 main 或函数 main。相反,它只需要定义我们的包名。在这种情况下,让我们称之为 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 来构建我们的包。
太棒了;我们现在有一个使用 Intel 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)
}
太棒了!这看起来比我们直接包装 Intel 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 程序中使用此包。
结论/资源
通过 Intel DAAL、cgo 和 SWIG,我们能够将优化的 Cholesky 分解直接集成到我们的 Go 程序中。然而,这些技术不仅限于 Cholesky 分解。您可以以相同的方式创建利用任何 Intel DAAL 实现算法的 Go 程序和包。这意味着您可以在 Go 应用程序中实现批处理、在线和分布式神经网络、聚类、提升、协同过滤等等。
上面使用的所有代码都可以在此处找到。
Go 数据资源
- 加入 Slack 上的 Gophers,与 #data-science 频道中从事大数据、数据分析、机器学习等 Go 相关工作的人交流。
- 查看 GopherData 组织,该组织正在汇集 Go 数据管理、处理、分析、机器学习和可视化工具的用户和开发人员。
- 在 Twitter 上关注 GopherData。
- 浏览(并贡献)不断增长的 Go 数据相关工具列表。
Intel DAAL 资源
- 查看 Intel DAAL 文档。
- 利用 在线培训。
- 参与 Intel DAAL 论坛。
关于作者
Daniel (@dwhitena) 是一位拥有博士学位的资深数据科学家,与 Pachyderm (@pachydermIO) 合作。Daniel 开发创新的分布式数据管道,其中包括预测模型、数据可视化、统计分析等。他曾在世界各地的会议(ODSC、Spark Summit、Datapalooza、DevFest Siberia、GopherCon 等)上发表演讲,与 Ardan Labs (@ardanlabs) 教授数据科学/工程,维护 Jupyter 的 Go 内核,并积极帮助组织对各种开源数据科学项目的贡献。