减小在AWS Lambda部署时Scipy和Numpy的大小

17

我正在尝试在 AWS Lambda 上部署一个 Python 应用程序。它有几个较大的 Python 依赖项,其中最大的是 scipy 和 numpy。结果我的应用程序比允许的 250MB 大得多。

在尝试找到一种方法来减小大小时,我发现了这里详细介绍的方法:

https://github.com/szelenka/shrink-linalg

实质上,在使用 pip 安装时,可以通过向 c 编译器传递标志,在 scipy 和 numpy 的 Cython 编译期间省略已编译的 c 二进制文件中的调试信息。结果是 scipy 和 numpy 的大小缩小了约 50%。我能够在本地(ubuntu 16.04)运行它,并且没有任何问题地创建了二进制文件。使用的命令是:

CFLAGS="-g0 -I/usr/include:/usr/local/include -L/usr/lib:/usr/local/lib" pip install numpy scipy --compile --no-cache-dir --global-option=build_ext --global-option="-j 4"

问题在于为了在 AWS Lambda 上运行,二进制文件必须在与 Lambda 运行环境类似的环境中进行编译。该环境的镜像可以在此处找到:

https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html

在将镜像加载到 EC2 实例后,在安装了一些依赖项后尝试运行相同的 pip 安装。

sudo yum install python36 python3-devel blas-devel atlas atlas-devel lapack-devel atlas-sse3-devel gcc gcc-64 gcc-gfortran gcc64-gfortran libgfortran, gcc-c++ openblas-devel python36-virtualenv

NumPy编译正常,但Scipy没有。Cython没有引起任何问题,但Fortran编译出现了以下错误:

error: Command "/usr/bin/gfortran -Wall -g -Wall -g -shared build/temp.linux-x86_64-3.6/build/src.linux-x86_64-3.6/scipy/integrate/_test_odeint_bandedmodule.o build/temp.linux-x86_64-3.6/build/src.linux-x86_64-3.6/build/src.linux-x86_64-3.6/scipy/integrate/fortranobject.o build/temp.linux-x86_64-3.6/scipy/integrate/tests/banded5x5.o build/temp.linux-x86_64-3.6/build/src.linux-x86_64-3.6/scipy/integrate/_test_odeint_banded-f2pywrappers.o -L/usr/lib64/atlas -L/usr/lib/gcc/x86_64-amazon-linux/6.4.1 -L/usr/lib/gcc/x86_64-amazon-linux/6.4.1 -L/usr/lib64 -Lbuild/temp.linux-x86_64-3.6 -llsoda -lmach -llapack -lptf77blas -lptcblas -latlas -lptf77blas -lptcblas -lpython3.6m -lgfortran -o build/lib.linux-x86_64-3.6/scipy/integrate/_test_odeint_banded.cpython-36m-x86_64-linux-gnu.so -Wl,--version-script=build/temp.linux-x86_64-3.6/link-version-scipy.integrate._test_odeint_banded.map" failed with exit status 1

我已经尝试重新安装gfortran以及整个gcc集合,但都没有什么好运。不幸的是,我对Fortran编译器的经验非常有限。如果有人有任何想法或者已经编译好了c二进制文件的版本,我会非常感激。


1
这是错误信息的最后一行(也是最不具信息性的)... 它首先告诉你的东西可能更有用。 - DavidW
很遗憾,Pip的回溯信息并不是很有用。在此之前,它只会显示所有成功编译的内容。 - joek575
2
你可能能够以详细模式运行pip。几乎肯定会有更多的信息(即使它被隐藏了)。 - DavidW
如果没有提供详细模式,就应该有一些日志文件。 - Vladimir F Героям слава
我在DOCKER中做类似的事情,有一个适用于Amazon Linux的Docker镜像,通过一些挂载操作,你可以创建出不错的构建过程。 - Jan Zyka
4个回答

3
使用Serverless上的serverless-python-requirements包,帮助我简化了整个过程,并且减小了软件包的大小。肯定会建议您查看它。
这是我遵循的指南:guide that I followed Serverless python-requirements plugin 确保将strip标志设置为false,以避免剥离二进制文件,导致“ELF加载命令地址/偏移量未正确对齐”的问题。
这就是我的最终serverless.yml,使我能够将 sklearn + cv2 打包成一个层:
custom:
  pythonRequirements:
    dockerizePip: true
    useDownloadCache: true
    useStaticCache: false
    slim: true
    strip: false
    layer:
      name: ${self:provider.stage}-cv2-sklearn
      description: Python requirements lambda layer
      compatibleRuntimes:
        - python3.8
      allowedAccounts:
        - '*'

0

0

0

好的,我解决了这个问题,虽然方法有点取巧。

我传递给pip的标志旨在减小c依赖项的大小,而不是fortran依赖项的大小。因此,使用通常通过pip下载的预编译fortran依赖项实际上没有任何问题。

首先,我在一个名为sp的文件夹中创建了未更改的scipy软件包的参考版本:

pip install scipy -t sp

接着,我创建了一个Go程序,作为gfortran编译器的包装器(或者技术上说是/usr/bin中gfortran编译器的链接)。

package main

import "os"
import "strings"
import "io/ioutil"
import "log"
import "os/exec"
import "fmt"


func checkErr(err error) {
    if err != nil {
        log.Fatal(err)
    }
}


func exists(path string) (bool, error) {
    _, err := os.Stat(path)
    if err == nil { return true, nil }
    if os.IsNotExist(err) { return false, nil }
    return true, err
}


func copyr(src string, dst string) {
    // Read all content of src to data
    data, err := ioutil.ReadFile(src)
    checkErr(err)
    // Write data to dst
    err = ioutil.WriteFile(dst, data, 0644)
    checkErr(err)
}


func main() {

    search_folder := "/home/ec2-user/sp/scipy"
    wrapped_compiler := "/usr/bin/inner_gfortran"
    argsWithProg := os.Args
    noProg := os.Args[1:]
    primed := 0
    check := "-o"
    var (
        cmdOut []byte
        err    error
    )
    for _, el := range argsWithProg {
        if primed == 1{
            primed = 0
            s := strings.Split(el, "scipy")
            if len(s) != 2{
                continue
            }
            src := search_folder + s[1]
            src_exi, _ := exists(src)
            if src_exi == false {
                continue
            }
            primed = 2
            dir_parts := strings.Split(el, "/")
            dir_parts = dir_parts[:len(dir_parts)-1]
            dir := strings.Join(dir_parts,"/")
            exi, _ := exists(dir)
            if exi == false {
                os.MkdirAll(dir, os.ModePerm)
            }
            os.Create(el)
            copyr(src, el)

        }
        if el == check{
            primed = 1
        }
    }
    if primed == 0 {
        if cmdOut, err = exec.Command(wrapped_compiler, noProg...).Output(); err != nil {
            fmt.Fprintln(os.Stderr, "There was an error running fortran compiler: ", err)
            os.Exit(1)
        }
        os.Stdout.Write(cmdOut)
    }

}

将实际编译器移动到inner_gfortran

sudo mv /usr/bin/gfortran /usr/bin/inner_gfortran

将 go 包装器放到它的位置

包装器将大多数指令传递给编译器,但如果指令是将 FORTRAN 程序编译为 Scipy,并且已经在我参考版本的 Scipy 中存在已编译的二进制文件,则包装器只需将参考版本复制到正在编译的新版本中。

就这样。Scipy 和 Numpy 的精简版现在可以在 Python 3.6 的 AWS Lambda 上运行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接