65.9K
CodeProject 正在变化。 阅读更多。
Home

Go 处理大数据。

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2017 年 4 月 19 日

CPOL

9分钟阅读

viewsIcon

7007

借助 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* 机器上,这就像以下操作一样简单

  1. 下载源代码。
  2. 运行安装脚本。
  3. 设置必要的环境变量(也可以使用提供的 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 时,我们有几个选项

  1. 通过包装器函数直接从 Go 程序调用 Intel DAAL。
  2. 创建一个可重用库,包装特定的 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.cxxcholesky.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 数据资源

Intel DAAL 资源

关于作者

Daniel (@dwhitena) 是一位拥有博士学位的资深数据科学家,与 Pachyderm (@pachydermIO) 合作。Daniel 开发创新的分布式数据管道,其中包括预测模型、数据可视化、统计分析等。他曾在世界各地的会议(ODSC、Spark Summit、Datapalooza、DevFest Siberia、GopherCon 等)上发表演讲,与 Ardan Labs (@ardanlabs) 教授数据科学/工程,维护 Jupyter 的 Go 内核,并积极帮助组织对各种开源数据科学项目的贡献。

© . All rights reserved.