Pandas向量化计算与for循环对比

对pandas DataFrame数据进行相同类型的计算时,对比使用向量化方式与for循环方式的时间开销差异。

TLDR

浮点数计算速度:apply比for循环快4.8倍,向量化比for循环快6900倍。

字符串计算速度:apply比for循环快5.0倍,向量化比for循环快580倍。

因此建议尽量避免使用for循环的方式处理dataframe数据。

一、测试环境:

Apple M2,16GB,SSD

python 3.11.7

pandas 2.1.4

二、求和两列浮点数

用不同的内存对象计算两列之和(new_col=A+B),比较所消耗的时间,列A和列B都是1~100之间的浮点数。

file

向量化速度提升倍数:

file

apply比for循环提升4.8倍左右,向量化比for循环提升3000~9000倍(平均6900倍)。

三、字符串连接

用不同的内存对象将两列字符串连接,比较所消耗的时间,列A和列B都是8字节的字符串。

file

向量化速度提升倍数:

file

apply比for循环提升5.0倍左右,向量化比for循环提升430~650倍(平均580倍)。

Python代码单元测试和性能测试

本文介绍如何使用pytest对python工程进行单元测试和统计测试覆盖率,同时介绍如何测试python程序的运行速度和内存消耗。

一、工程目录结构

测试用例统一放在tests目录下,tests内部目录结构与被测代码的目录结构相同,以便快速定位测试用例代码。

file

如果环境还没有安装过pytest,先执行pip install pytest进行安装。

二、测试用例内容

测试用例的原则是逻辑尽量简单直白,这样当用例运行失败时能够较快的通过调试定位到问题原因,建议在测试代码里添加必要的注释。另外注意下面几个规则:

  • 文件名必须以test_开头,这样pytest命令可以正确识别此文件为测试用例。
  • 文件里每个测试方法必须以test_开头,原因同上。
  • 如果测试用例引用外部文件,可将文件放在同目录下,然后用os.path.dirname(file)引用py文件所在目录,确保从各个目录执行此测试用例都能正确找到外部文件。
  • 如果测试用例生成新的文件,建议用pytest提供的tmp_path参数(文档)作为输出目录,此临时目录下的文件会被自动清理,以免影响测试用例的下次运行。

以下是一个测试用例的例子(test_adapter_csv.py):

from acme.datasets.adapter import *
import os
import pandas as pd

# 验证正常加载csv文件
def test_read_file_to_df():
    df = CSVHandler.read_file_to_df(os.path.dirname(__file__), file_type=CSVInputFile.BUFFER)
    assert len(df) == 16

# 验证数据正常写出到csv文件
def test_write_df_to_csv(tmp_path):
    df = pd.DataFrame()
    CSVHandler.write_df_to_csv(df, path=str(tmp_path), file_type=CSVOutputFile.LOT_FLOW)
    assert os.path.exists(os.path.join(str(tmp_path), 'RESULT.csv'))

为提高代码覆盖率,我们不能只验证输入正常的情况,还应该对处理异常的情况进行验证:

# 验证要加载的csv文件不存在的情况
def test_read_file_to_df_not_exist():
    with pytest.raises(FileNotFoundError):
        df = CSVHandler.read_file_to_df(os.path.dirname(__file__), file_type=CSVInputFile.DEMAND)

三、执行测试用例

方式1、在IDE里可直接执行test_xxx.py文件里的指定方法
file

方式2、在IDE里右键点击tests目录选择执行所有测试用例
file

方式3、在命令行里执行pytest命令可以执行所有测试用例
file

四、统计测试覆盖率

PyCharm专业版里内置了覆盖率工具,在运行时选择"Run with coverage“即可,这里介绍PyCharm社区版如何统计测试覆盖率。

首先确保已安装测试覆盖率组件:

pip install pytest-cov
pip install pytest-html

并且确保在tests目录内的每个目录下已包含init.py文件(否则统计时会忽略此目录下的测试用例)。
file

然后在命令行里执行:

pytest --cov=acme --cov-report=html

即可生成测试覆盖率结果文件,放在htmlcov目录下,打开其中的index.html可查看结果。

即使只有一个测试用例,工程的总体覆盖率也已经达到28%,这是因为有很多import、def代码在加载模块时被覆盖到,而实际的业务逻辑代码并没有被覆盖。
file

file

如果忽略 --cov-report=html 参数则会在控制台里输出每个py文件的覆盖率报告,但不会包含逐行的覆盖率结果。
file

五、性能测试(时间)

方案1:cProfile+Snakeviz

首先安装pytest-profiling组件和snakeviz查看工具:

pip install pytest-profiling
pip install snakeviz

在命令行里执行pytest时添加–profile参数即可统计运行时间:

pytest tests/datasets/test_adapter_csv.py --profile

file

同时会生成二进制格式的统计文件:
file

使用snakeviz工具查看指定prof文件的内容:

snakeviz prof/combined.prof

file

方案2、Viztracer(推荐)

viztracer查看结果的交互方式更加友好,有利于更快速的定位问题,因此推荐使用。

首先安装viztracer组件:

pip install viztracer

用viztracer命令运行指定的测试用例:

viztracer --min_duration 0.1ms tests/datasets/test_adapter_csv.py
vizviewer result.json

运行结束后会生成result.json文件:
file

用vizviewer命令查看result.json的内容(会自动启动浏览器):

vizviewer result.json

file

六、性能测试(内存)

a. 整体内存分析

首先安装必要的组件:

pip install memory_profiler
pip install matplotlib

使用mprof运行指定的测试用例:

mprof run tests/datasets/test_adapter_csv.py

每次运行结束后会生成一个.dat文件:
file

使用mprof plot命令查看最新一个.dat文件的内容,若不指定文件名则自动查看最新一个文件,图形展示了测试用例执行过程中消耗的内存变化情况:

mprof plot mprofile_20240410185255.dat

file

b. 逐行内存分析

有时我们希望分析某个函数或者某几行代码的内存占用,可以用@profile修饰要分析的函数,然后在PyCharm里点击测试函数旁边的运行按钮启动测试用例(命令行里启动不行)。当运行完成后,在控制台里会输出此函数的逐行内存变化。

file

逐行内存占用情况如下图所示,从图中可以看到,读取DataFrame的操作新增占用了0.6MB的内存。

file

我们也可以用@profile标记多个函数,在PyCharm里批量运行测试用例,这样在测试报告里可以分别查看这些函数的逐行内存占用情况:
file

内存向量化计算性能比较

本文比较pandas、polars、duckdb和arrow等几种类库,在内存中进行向量化计算的执行时间差异。

测试环境

硬件:
Apple M2,16GB,SSD

软件:
python 3.11.7
pandas 2.1.4
pyarrow 14.0.2
duckdb 0.10.1
polars 0.20.15

求和两列浮点数

用不同的内存对象计算两列之和(new_col=A+B),比较所消耗的时间,列A和列B都是1~100之间的浮点数。

file

内存压力大时耗时会显著增加,例如下面的pandas和pandas_on_arrow曲线:

file

计算指定列平均值

用不同的内存对象计算指定列的平均值,比较所消耗的时间,指定列是1~100之间的浮点数。

file

字符串连接

用不同的内存对象将两列字符串连接,比较所消耗的时间,列A和列B都是8字节的字符串。

file

Anaconda虚拟环境离线迁移

项目中遇到需要在离线环境下为anaconda安装python 3.8虚拟环境的问题,即服务器不能连接互联网的环境,这里记录一下解决方法。

一、环境信息

> uname -a
Linux taos-1 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

> conda --version
conda 4.5.4

二、在线安装虚拟环境

如果已经有一个需要迁移的虚拟环境,则可以跳过这一步。

先找一台在线的服务器,安装anaconda所需的虚拟环境,我这里虚拟环境是python 3.8加上pandasnumpymatplotlib等几个常用软件包。

> conda create -n python38 python=3.8
> conda install -n python38 pandas numpy matplotlib

三、备份虚拟环境

使用conda pack命令完整备份虚拟环境:

> conda install -c conda-pack
> conda pack -n python38 -o env_python38_pack.tar.gz

备份得到的是一个.tar.gz压缩包,如果查看压缩包里的内容,目录结构如下图所示:

file

四、离线恢复虚拟环境

离线恢复不需要conda-pack命令,只需要将打好的虚拟环境包复制到目标环境的临时目录,然后解压缩到anaconda的envs目录下即可:

> mkdir /root/anaconda3/envs/python38
> tar zxvf /tmp/env_python38_pack.tar.gz -C /root/anaconda3/envs/python38

输入conda info -e命令可以看到虚拟环境已经添加上了:

file

五、补充说明

1. 是否可以直接备份env下的目录?

一般不可以,按 conda-pack 官网给出的理由如下:

A tool like conda-pack is necessary because conda environments are not relocatable. Simply moving an environment to a different directory can render it partially or completely inoperable. conda-pack addresses this challenge by building archives from original conda package sources and reproducing conda’s own relocation logic.

网上有些文章提到可以直接备份envs/python38目录然后用下面的命令恢复:

> conda create -n [name] --clone [path] --offline

但经过测试当虚拟环境有依赖包时,离线恢复环境会失败,提示下面的错误,原因可能是虚拟环境的依赖包与目标环境pkgs目录下的包版本不同:

RuntimeError('EnforceUnusedAdapter called with url https://repo.anaconda.com/pkgs/main/linux-64/ca-certificates-2022.10.11-h06a4308_0.tar.bz2\nThis command is using a remote connection in offline mode.\n',)

file

观察源anaconda下的pkgs目录可以发现,conda install安装新的虚拟环境时有一些依赖包是安装在pkgs目录下的。那么如果备份时将pkgs目录也备份下来是否可以呢?我认为有以下缺点:

  1. pkgs目录比较大不方便网络传输(一般2GB~3GB);
  2. 恢复时需要处理envspkgs两个目录比较繁琐;
  3. 对目标环境的侵入性比较高,因为会覆盖pkgs下的一些文件。

如果只备份pkgs下的部分新安装的目录,比如按修改时间过滤,则可能遇到类似下面的错误:

FileNotFoundError: [Errno 2] No such file or directory: '/root/anaconda3/pkgs/libstdcxx-ng-11.2.0-h1234567_1/info/index.json'

2. 是否可以用conda export命令备份?

如果目标环境是在线的可以用conda export命令备份,然后用conda create -f恢复。但对离线的情况不适用。

> conda env export -n python38 > /tmp/python38.yml
> cat /tmp/python38.yml
name: python38
channels:
  - defaults
dependencies:
  - ca-certificates=2022.10.11=h06a4308_0
  - certifi=2022.9.24=py38h06a4308_0
  - ld_impl_linux-64=2.38=h1181459_1
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.3=h5eee18b_3
  - openssl=1.1.1q=h7f8727e_0
  - pip=22.2.2=py38h06a4308_0
  - python=3.8.13=haa1d7c7_1
  - readline=8.2=h5eee18b_0
  - setuptools=65.5.0=py38h06a4308_0
  - sqlite=3.39.3=h5082296_0
  - tk=8.6.12=h1ccaba5_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.2.6=h5eee18b_0
  - zlib=1.2.13=h5eee18b_0
prefix: /disk1/anaconda3/envs/python38

# 恢复环境
> conda env create -f python38.yaml

综上,建议用conda pack命令备份anaconda的虚拟环境。

六、参考链接

https://www.anaconda.com/blog/moving-conda-environments
https://conda.github.io/conda-pack/
https://www.jianshu.com/p/adb927fa0091
https://blog.csdn.net/snowing118102/article/details/84319736
https://blog.csdn.net/CREATOR_Jay_xu/article/details/91971446

在Python里访问Java对象 – Py4J

Py4J是一套java+python软件包,利用它开发者可以在python里动态的访问jvm里的java对象,其基本原理是在java端创建一个Socket服务,在python端使用Socket客户端调用,Py4J在python语法上的进行了精心的包装,使得在python里的这些远程java对象使用起来与本地的python对象十分接近。

环境信息

Java Python Py4J
11.0.10 3.8.5 0.10.9.7

Java服务端

创建一个新的java工程,并在pom.xml里引入py4j依赖:

<dependency>
    <groupId>net.sf.py4j</groupId>
    <artifactId>py4j</artifactId>
    <version>0.10.9.7</version>
</dependency>

以下借用py4j官方提供的例子。首先定义一个Java类提供一些功能,这里是用一个堆栈类作为例子,提供了push, pop等功能:

package py4j.examples;

import java.util.LinkedList;
import java.util.List;

public class Stack {
    private List<String> internalList = new LinkedList<String>();

    public void push(String element) {
        internalList.add(0, element);
    }

    public String pop() {
        return internalList.remove(0);
    }

    public List<String> getInternalList() {
        return internalList;
    }

    public void pushAll(List<String> elements) {
        for (String element : elements) {
            this.push(element);
        }
    }
}

然后要提供一个对应的EntryPoint类作为服务提供者,这个类实现main函数,执行后会启动一个Socket服务,用于接收python客户端的请求。

package py4j.examples;

import py4j.GatewayServer;

public class StackEntryPoint {

    private Stack stack;

    public StackEntryPoint() {
        stack = new Stack();
        stack.push("Initial Item");
    }

    public Stack getStack() {
        return stack;
    }

    public static void main(String[] args) {
        // 默认端口号25335,可以在构造方法里配置其他端口号
        GatewayServer gatewayServer = new GatewayServer(new StackEntryPoint());
        gatewayServer.start();
        System.out.println("Gateway Server Started");
    }

}

直接执行StackEntryPoint就启动了Java服务端。

Python客户端

首先安装py4j的最新版本:

> pip install py4j

在python命令行里执行下面的代码就可以获得一个Stack对象,其中.entry_point得到的是GatewayServer里的StackEntryPoint实例。

>>> from py4j.java_gateway import JavaGateway
>>> gateway = JavaGateway()
>>> stack = gateway.entry_point.getStack()

测试一下这个Stack对象的功能:

>>> stack
JavaObject id=o0

>>> stack.getInternalList()
['Initial Item']

>>> stack.push('item2')
>>> stack.getInternalList()
['item2', 'Initial Item']

Stack里可以直接push字符串,也可以push一个java的list:

>>> list = gateway.jvm.java.util.ArrayList()
>>> list.add('item3')
>>> list.add('item4')
>>> stack.pushAll(list)
>>> stack.getInternalList()
['item4', 'item3', 'item2', 'Initial Item']

但不可以直接push一个python的list,将会抛出异常。需要借助py4j提供的ListConverter先转换为java list:

>>> stack.pushAll(['item3','item4'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\zhanghao\anaconda3\lib\site-packages\py4j\java_gateway.py", line 1314, in __call__
    args_command, temp_args = self._build_args(*args)
  File "C:\Users\zhanghao\anaconda3\lib\site-packages\py4j\java_gateway.py", line 1283, in _build_args
    [get_command_part(arg, self.pool) for arg in new_args])
  File "C:\Users\zhanghao\anaconda3\lib\site-packages\py4j\java_gateway.py", line 1283, in <listcomp>
    [get_command_part(arg, self.pool) for arg in new_args])
  File "C:\Users\zhanghao\anaconda3\lib\site-packages\py4j\protocol.py", line 298, in get_command_part
    command_part = REFERENCE_TYPE + parameter._get_object_id()
AttributeError: 'list' object has no attribute '_get_object_id'

如果要在python里使用任意的Java对象,可以用gateway.jvm引用完整的java类名来创建对象实例:

>>> random = gateway.jvm.java.util.Random()
>>> random.nextInt()
433815240

所以在本文的例子里,gateway.entry_point.getStack()gateway.jvm.py4j.examples.Stack()基本是等价的,除了前者是单例的,初始化的内容有所不同。

参考资料

https://www.py4j.org/getting_started.html

Python处理csv速度慢问题一例和解决

问题现象

一段python代码处理一个60万行csv文件耗时过长,从内存增长图和作业日志上看,处理此csv文件用了十几分钟,但在本地python命令行里测试读取200万行的csv(列数相同)文件只需要不到30秒,因此猜测是在pandas处理此文件数据时存在未优化的代码。

file

分析和解决

经过排查,发现是在讲csv文件里的时间戳转换为datetime类型时,消耗了大量时间,更换timestamp转datetime的函数:

# input[key]['k_ts'] = input[key]['k_ts'].apply(lambda x:pd.to_datetime(x, utc=True, unit='ms').tz_convert('Asia/Shanghai'))

input[key]['k_ts'] = input[key]['k_ts'].apply(lambda x:datetime.datetime.fromtimestamp(x/1000))

同时反方向的datetime转换到timestamp函数也做相应更改:

# output[key][ts_col] = output[key][ts_col].apply(lambda x:x.timestamp() * 1000).astype('int64')

output[key][ts_col] = output[key][ts_col].apply(lambda x:int(time.mktime(x.timetuple())*1e3 + x.microsecond/1e3))

修改后问题解决。

pytorch环境搭建和简单的人脸识别应用

注:本文是一边安装一边记录形成的,其中有不少问题是由于centos版本较低导致的,建议使用高版本(>=7.6)安装以节约时间。

安装python

使用Anaconda管理环境

设置anaconda使用tuna(清华大学镜像):

conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --set show_channel_urls yes

tuna还维护了一些第三方镜像,包括conda-forge, menpo等,需要的话也可以像上面这样添加,tuna页面有说明。

在conda环境里安装pytorch:

conda install pytorch-cpu -c pytorch

CUDA toolkit下载:https://developer.nvidia.com/accelerated-computing-toolkit

cuDNN下载:https://developer.nvidia.com/cudnn

用pip安装dlib(win下未成功,linux下没试):

https://pypi.org/simple/dlib/ 下载.whl格式的dlib,然后用pip安装:

pip install xxx.whl

用conda安装dlib:

用conda安装dlib(如果已经将menpo源添加到conda,则可以省略-c menpo选项):

conda install -c menpo dlib

conda提示Killed:

Update 201902: 另一个引发Killed的原因是内存不足,例如这台ec2上只有约300MB可用内存,经常出现Killed,增加512MB(总共800MB可用内存)后Killed的情况就少了很多。

这里遇到一个坑:用conda安装各种包时总报错“Killed”,网上也没找到类似问题,最后发现居然是磁盘空间满了,原来anaconda3居然占用了接近4GB的空间把硬盘填满了。另一个原因,需要conda update conda更新一下conda版本,之后就能正常安装了。

解决上述磁盘空间满问题后,发现conda install dlib时还会报错"Killed",加-vvv参数后发现可能与TUNA源有关:

从~/.condarc里删除相关TUNA源后问题即解决(应该主要是conda-forge这个镜像源的问题)。

偶尔还会遇到killed提示,但再试一次通常可以成功,因此猜测网络条件也会产生影响。

dlib与python版本:

在python 3.5环境里安装的dlib,虽然安装成功,但import dlib失败:

提示“ImportError: /root/miniconda3/envs/py35/lib/python3.5/site-packages/dlib.cpython-35m-x86_64-linux-gnu.so: undefined symbol: _PyThreadState_UncheckedGet”:

重新换python 3.6环境里安装dlib,可以import dlib成功:

升级glibc

由于是在centos6.5环境里,glibc版本比较低(2.12),而menpo源的dlib 19要求glibc 2.14,因此需要手工升级glibc。链接

安装必要的python库

conda install dlib -c menpo
conda install opencv -c menpo
conda install numpy
pip install imutils

如果安装opencv报内存不足(300MB剩余内存有此问题,700MB可以通过),可以尝试用pip安装opencv:

pip install opencv-python==3.4.1.15

Python编码问题

print()命令打印来自mysql的中文时报错如下:

UnicodeEncodeError: 'ascii' codec can't encode character '\xe9'

解决(绕过)方法:用logging.info()替代print()命令。

import logging
logging.basicConfig(level=logging.DEBUG,format='[%(levelname)s][%(asctime)s] %(message)s')
logging.info("my messages")

注意,默认logging输出到的是stderr而不是stdout。

一个简单的人脸检测程序

检测一张图片里的多个人脸,用绿色矩形标记并输出为图片。参考链接

from imutils import face_utils
import imutils
import dlib
import cv2
image = cv2.imread('/root/person01.jpg')
image = imutils.resize(image, width=500)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
detector = dlib.get_frontal_face_detector()
rects = detector(gray, 1)
for (i, rect) in enumerate(rects):
    (x, y, w, h) = face_utils.rect_to_bb(rect)
    cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 1)
cv2.imwrite('/root/output.jpg',image)

python连接mysql数据库

过去常用的MySQLdb是c语言实现的,目前更建议使用MySQL官方提供的Connector/Python,是一个纯python的实现。

pip install mysql-connector-python

不过Connector/Python对mysql服务器版本有要求,例如2.0版以上就无法连接5.1版的mysql服务器,会报Bad Handshake错误。

另一个纯python的实现是PyMySQL,使用起来兼容性比较好。安装方法是:

pip install PyMySQL

用PyMySQL的一个简单例子:

import pymysql.cursors
cnx = pymysql.connect(user='xxx',password='xxx', host='localhost', database='xxx', charset='utf8mb4')
try:
    with cnx.cursor() as cursor:
        sql = "SELECT id, name, gender FROM user"
        cursor.execute(sql)
        for row in cursor:
            print(row)
finally:
    cnx.close()

安装tensorflow

tensorflow支持python3.5,所以用conda创建一个python 3.5环境后安装(参考链接):

conda create -n tensorflow python=3.5
source activate tensorflow
pip install tensorflow

如果是在centos 6上,由于自带的gcc是4.4.7版本过低,所以需要升级到gcc 4.8版本。直接下载编译的时间比较长(也比较容易遇到问题),可以使用devtools2辅助升级(参考链接):

wget http://people.centos.org/tru/devtools-2/devtools-2.repo -O /etc/yum.repos.d/devtools-2.repo
yum install devtoolset-2-gcc devtoolset-2-binutils devtoolset-2-gcc-c++
ln -s /opt/rh/devtoolset-2/root/usr/bin/* /usr/local/bin/

此时执行tensorflow程序可能会提示找不到库GLIBCXX_3.4.17的问题:

ImportError: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.17' not found (required by /root/miniconda3/envs/tensorflow/lib/python3.5/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so)

此时可以从conda自带的lib目录里复制一个libstdc++.so.6.0.24版本并更新软连接解决:

cp /root/miniconda3/lib/libstdc++.so.6.0.24 /usr/lib64/
ln -s /usr/lib64/libstdc++.so.6.0.24 /usr/lib64/libstdc++.so.6

这时执行tensorflow程序遇到glibc版本过低的提示:

ImportError: /lib64/libc.so.6: version `GLIBC_2.16' not found

查看系统的glibc版本是2.14,因此需要升级。但是按照前述升级到glibc 2.14的方法(链接),升级为2.16时却遇到了下面的问题:

error while loading shared libraries: __vdso_time: invalid mode for dlopen(): Invalid argument

由于libc.so.6有问题,导致一些基本命令(例如rm)不可用,此时要切换回glibc 2.14的方法如下:

LD_PRELOAD=/opt/glibc-2.14/lib/libc-2.14.so rm -f /lib64/libc.so.6
LD_PRELOAD=/opt/glibc-2.14/lib/libc-2.14.so ln -s /opt/glibc-2.14/lib/libc-2.14.so /lib64/libc.so.6

原因(可能)是因为升级glibc时没有包含完整环境,使用下面的configure参数:

../configure --prefix=/opt/glibc-2.17 --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin

上面命令有可能在check过程中出错(不支持--no-whole-archive),原因是binutils版本过低(2.23),需要升级到2.25,方法见链接

升级binutils后虽然能够支持--no-whole-archive参数了,但升级glibc 2.16后的异常却没有任何变化。

至此,决定升级linux版本解决(从centos 6.5升级到7.6)。Update: 在centos 7.6下安装tensorflow十分顺利。

 

参考资料:

https://github.com/jcjohnson/pytorch-examples

Anaconda使用总结