ONNX的结构与转换

ONNX的结构与转换

  • 1. 背景
  • 2. ONNX结构分析与修改工具
    • 2.1. ONNX结构分析
    • 2.2. ONNX的兼容性问题
    • 2.3. 修改ONNX模型
  • 3. 各大深度学习框架如何转换到ONNX?
    • 3.1. MXNet转换ONNX
    • 3.2. TensorFlow模型转ONNX
    • 3.3. PyTorch模型转ONNX
    • 3.4. PaddlePaddle模型转ONNX
      • 3.4.1. 简介
      • 3.4.2. 模型导出为ONNX协议
        • 3.4.2.1. 动态图导出ONNX协议
        • 3.4.2.2. 静态图导出ONNX协议
      • 3.4.3. ONNX模型的验证
      • 3.4.4. ONNXRuntime推理
  • 4. ONNX到目标平台

1. 背景

深度学习模型在训练完成之后,部署并应用在生产环境的这一步至关重要,毕竟训练出来的模型不能只接受一些公开数据集和榜单的检验,还需要在真正的业务场景下创造价值,不能只是为了PR而躺在实验机器上

在现有条件下,一般涉及到模型的部署就要涉及到模型的转换,而转换的过程也是随着对应平台的不同而不同,一般工程师接触到的平台分为GPU云平台、手机和其他嵌入式设备

对于GPU云平台来说,在上面部署本应该是最轻松的事情,但是实际情况往往比较复杂。有历史遗留问题,比如说3年前的古董级的模型因为效率和推理速度问题需要进行优化,也有算法团队使用了一些比较小众或者自定义的OP的问题。其实解决这类问题有非常直接的方式,例如直接用最新的框架重新训练,或者算法团队对模型做一些妥协,替换掉一些骚操作,那么对部署工程师来说问题就简单了很多。但是有些情况下(算法团队很忙或者必须效果优先),我们只能自己从框架的层面来解决这个问题,包括但不限于:实现新OP、修改不兼容的属性、修改不兼容的权重形状

当然这里无论是模型转换还是模型部署,我个人比较推荐的做法是都是使用ONNX作为中间媒介。所以我们有必要对ONNX做一个比较透彻的了解

2. ONNX结构分析与修改工具

2.1. ONNX结构分析

对于ONNX的了解,很多人可能仅仅停留在它是一个开源的深度学习模型标准,能够用于模型转换及部署但是对于其内部是如何定义这个标准,如何实现和组织的,却并不十分了解,所以在转换模型到ONNX的过程中,对于出现的不兼容不支持的问题有些茫然。

ONNX结构的定义基本都在这一个onnx.proto文件里面了,如何你对protobuf不太熟悉的话,可以先简单了解一下再回来看这个文件。当然我们也不必把这个文件每一行都看明白,只需要了解其大概组成即可,有一些部分几乎不会使用到可以忽略。

这里我把需要重点了解的对象列出来

  • ModelProto
  • GraphProto
  • NodeProto
  • AttributeProto
  • ValueInfoProto
  • TensorProto

我用尽可能简短的语言描述清楚上述几个Proto之间的关系:当我们将ONNX模型load进来之后,得到的是一个ModelProto,它包含了一些版本信息,生产者信息和一个非常重要的GraphProto;在GraphProto中包含了四个关键的repeated数组,分别是node(NodeProto类型),input(ValueInfoProto类型),output(ValueInfoProto类型)和initializer(TensorProto类型),其中node中存放着模型中的所有计算节点,input中存放着模型所有的输入节点,output存放着模型所有的输出节点,initializer存放着模型所有的权重;那么节点与节点之间的拓扑是如何定义的呢?非常简单,每个计算节点都同样会有inputoutput这样的两个数组(不过都是普通的string类型),通过inputoutput的指向关系,我们就能够利用上述信息快速构建出一个深度学习模型的拓扑图。最后每个计算节点当中还包含了一个AttributeProto数组,用于描述该节点的属性,例如Conv层的属性包含grouppadsstrides等等,具体每个计算节点的属性、输入和输出可以参考这个Operators.md文档。

需要注意的是,刚才我们所说的GraphProto中的input输入数组不仅仅包含我们一般理解中的图片输入的那个节点,还包含了模型当中所有权重。举个例子,Conv层中的W权重实体是保存在initializer当中的,那么相应的会有一个同名的输入在input当中,其背后的逻辑应该是把权重也看作是模型的输入,并通过initializer中的权重实体来对这个输入做初始化(也就是把值填充进来)

2.2. ONNX的兼容性问题

为什么要修改ONNX模型?因为ONNX版本迭代以及框架之间对OP定义的不兼容,导致转换出来的ONNX模型千奇百怪,会有一些莫名奇妙的错误。

比如在MXNet框架下有一个非常灵活的写法,可以在BatchNormalization后面直接跟FC层,如下图所示(请忽略其中的dropout,我也不知道算法同学是如何在inference/eval模式下把dropout层导出到ONNX模型里面来的):

在这里插入图片描述
使用onnxruntime进行inference的时候,报错提示FAIL : Node (pre_fc1) Op (Gemm) [ShapeInferenceError] First input does not have rank 2,那么为什么这个在MXNet下正常的模型转换成ONNX之后就出错了呢?严格来说,BatchNormalization出来output的维度为4,而FC层所接受的输入维度是2,这两者之间差了一个Flatten操作,但是MXNet(可能不限于MXNet)隐含的帮我们完成了这个步骤,所以说灵活性背后是有代价的。

这还仅仅是冰山一角,事实上你只要翻一翻onnxruntime这个repo的issue,还能看到不少类似的不兼容问题,例如BatchNormalization中的spatial属性引发的问题:

  • https://github.com/onnx/models/issues/156
  • https://github.com/microsoft/onnxruntime/issues/2175

此外还有PyTorch模型转换到ONNX模型中非常常见的reshape的问题:
在这里插入图片描述
一个简单的PyTorch中的view操作,期待出来的就是一个简单的reshape操作,但是如果你的形状参数用的是形如x.size(0)这样的话就会出现上图的问题,必须做一个强制类型转换int(x.size(0),或者用onnx-simplifier来处理一下模型

2.3. 修改ONNX模型

解决问题的最好办法是从根源入手,也就是从算法同学那边的模型代码入手,我们需要告诉他们问题出在哪里,如何修改。但是也有一些情况是无法通过修改模型代码解决的,或者与其浪费那个时间,不如我们部署工程师直接在ONNX模型上动刀解决问题。

还有一种更dirty的工作是,我们需要debug原模型和转换后的ONNX模型输出结果是否一致(误差小于某个阈值),如果不一致问题出现在哪一层,现有的深度学习框架我们有很多办法能够输出中间层的结果用于对比,而据我所知,ONNX中并没有提供这样的功能;这就导致了我们的debug工作极为繁琐

所以如果有办法能够随心所欲的修改ONNX模型就好了。要做到这一点,就需要了解上文所介绍的ONNX结构知识了。

比如说我们要在网络中添加一个节点,那么就需要先创建相应的NodeProto,参照文档设定其的属性,指定该节点的输入与输出,如果该节点带有权重那还需要创建相应的ValueInfoProtoTensorProto分别放入graph中的inputinitializer中,以上步骤缺一不可。

经过一段时间的摸索和熟悉,我写了一个小工具onnx-surgery并集成了一些常用的功能进去,实现的逻辑非常简单,也非常容易拓展。代码比较简陋,但是足以完成一些常见的修改操作

3. 各大深度学习框架如何转换到ONNX?

(需要说明的是,由于深度学习领域发展迅速,本文提到的几个框架也在快速的迭代过程中,所以希望本文提到的一些坑和bug在未来的版本当中能够逐一解决,也希望大家永远不要踩本文所提到的那些坑)

3.1. MXNet转换ONNX

MXNet官方文档给出了一个非常简单的例子展示如何转换

import mxnet as mx
import numpy as np
from mxnet.contrib import onnx as onnx_mxnet
import logging
logging.basicConfig(level=logging.INFO)

# Download pre-trained resnet model - json and params by running following code.
path='http://data.mxnet.io/models/imagenet/'
[mx.test_utils.download(path+'resnet/18-layers/resnet-18-0000.params'),
 mx.test_utils.download(path+'resnet/18-layers/resnet-18-symbol.json'),
 mx.test_utils.download(path+'synset.txt')]

# Downloaded input symbol and params files
sym = './resnet-18-symbol.json'
params = './resnet-18-0000.params'

# Standard Imagenet input - 3 channels, 224*224
input_shape = (1,3,224,224)

# Path of the output file
onnx_file = './mxnet_exported_resnet50.onnx'

# Invoke export model API. It returns path of the converted onnx model
converted_model_path = onnx_mxnet.export_model(sym, params, [input_shape], np.float32, onnx_file)

这个重点提一下MXNet转换ONNX模型可能会遇到的一些问题,不排除在未来版本MXNet修复了相关问题,也不排除未来ONNX版本更新又出现新的不兼容问题。

第一个问题与MXNet的BatchNorm层中的fix_gamma参数有关,当fix_gamma参数为True时,其含义是将gamma这个参数固定为1,即(x-mean)/var * gamma + beta;但是这里就出现了不兼容的问题,因为在ONNX当中是没有fix_gamma这个属性的,如果fix_gamma为False不会有问题,如果fix_gamma为True就会出现两者计算结果不一致问题。解决方法很直观,当fix_gamma参数为True时,我们必须手动将ONNX当中的gamma参数全部置为1

第二个问题与MXNet的Pooling层中的count_include_pad属性有关,这个问题应该是MXNet贡献者的疏忽,当Pooling层的类型为’avg’时,忘记了在生成ONNX节点时设置该属性。解决方法就是在_op_translation.py文件里增加一个分支,将这个遗漏属性补上。

count_include_pad = 1 if attrs.get("count_include_pad", "True") in ["True", "1"] else 0
# ...
# ...
# ...
elif pool_type == "avg":
  node = onnx.helper.make_node(
    pool_types[pool_type],
    input_nodes,  # input
    [name],
    count_include_pad=count_include_pad,
    kernel_shape=kernel,
    pads=pad_dims,
    strides=stride,
    name=name
  )

当然,如果你不想直接修改MXNet的导出代码,也可以直接修改ONNX模型达到同样的目的,方法可以参考上一篇文章中我写的小工具

3.2. TensorFlow模型转ONNX

tf的模型转换ONNX已经有现成的转换工具,https://github.com/onnx/tensorflow-onnx,先将tf的模型freeze_graph之后得到pb文件,再利用该转换工具即可转换为onnx模型

freeze_graph的方式网上有很多版本,我这里用的是一个老版本的方法(tensorflow==1.8.0)

# your network def
import network

input_size = (224, 224)
ckpt_model_path = "./model.ckpt"
pb_model_path = "./model.pb"
output_node_name = "your model output name"

graph = tf.Graph()
with graph.as_default():
    placeholder = tf.placeholder(
        dtype=tf.float32, shape=[None, input_size[0], input_size[1], 3], name="pb_input"
    )
    output = network(placeholder)
		
    # your can get all the tensor names if you do not know your input and output name in your ckpt with this code
    # nl = [n.name for n in tf.get_default_graph().as_graph_def().node]
    # for n in nl:
    #     print(n)

    saver = tf.train.Saver()
    sess = tf.Session(
        config=tf.ConfigProto(
            gpu_options=tf.GPUOptions(
                allow_growth=True, per_process_gpu_memory_fraction=1.0),
            allow_soft_placement=True
        )
    )
    saver.restore(sess, ckpt_model_path)

    output_graph_def = graph_util.convert_variables_to_constants(
        sess, sess.graph_def, [output_node_name]
    )
    with tf.gfile.FastGFile(pb_model_path, mode="wb") as f:
        f.write(output_graph_def.SerializeToString())
		
    # you can get the input and output name of your model.pb file
    # maybe a "import/" is needed to append before the name if you
    # get some error
    # gf = tf.GraphDef()
    # gf.ParseFromString(open('./model.pb', 'rb').read())
    # nl2 = [n.name + '=>' +  n.op for n in gf.node if n.op in ('Softmax', 'Placeholder')]
    # for n in nl2:
    #     print(n)

需要指出的是大部分tf模型的输入layout都是NHWC,而ONNX模型的输入layout为NCHW,因此建议在转换的时候加上--inputs-as-nchw这个选项,其他选项可以参考文档,非常详细

典型的转换命令如下所示:

python3 -m tf2onnx.convert --input xxxx.pb --inputs pb_input:0 --inputs-as-nchw pb_input:0 --outputs resnet_v2_101/predictions/Softmax:0 --output xxxx.onnx

注意,由于tensorflow的模型输入一般会比较灵活,输入的batch_size可以留空,可以在运行时传入不同大小的batch_size数据。但是一般在ONNX和TensorRT这些框架中,我们习惯于指定一个固定的batch_size,那如何修改呢,可以参考上一篇文章中我写的那个小工具,有一个例子展示如何修改ONNX模型的batch_size

3.3. PyTorch模型转ONNX

在PyTorch推出jit之后,很多情况下我们直接用torch scirpt来做inference会更加方便快捷,并不需要转换成ONNX格式了,当然如果你追求的是极致的效率,想使用TensorRT的话,那么还是建议先转换成ONNX的。

import torch
import torchvision

dummy_input = torch.randn(10, 3, 224, 224, device='cuda')
model = torchvision.models.alexnet(pretrained=True).cuda()

# Providing input and output names sets the display names for values
# within the model's graph. Setting these does not change the semantics
# of the graph; it is only for readability.
#
# The inputs to the network consist of the flat list of inputs (i.e.
# the values you would pass to the forward() method) followed by the
# flat list of parameters. You can partially specify names, i.e. provide
# a list here shorter than the number of inputs to the model, and we will
# only set that subset of names, starting from the beginning.
input_names = [ "actual_input_1" ] + [ "learned_%d" % i for i in range(16) ]
output_names = [ "output1" ]

torch.onnx.export(model, dummy_input, "alexnet.onnx", verbose=True, input_names=input_names, output_names=output_names)

注意上面的input_names和output_names不是必需的参数,省略也是可以的

3.4. PaddlePaddle模型转ONNX

https://www.bookstack.cn/read/paddlepaddle-2.0-zh/e1d1bbfc28a54089.md

3.4.1. 简介

ONNX (Open Neural Network Exchange) 是针对机器学习所设计的开源文件格式,用于存储训练好的模型。它使得不同的人工智能框架可以采用相同格式存储模型并交互。通过ONNX格式,Paddle模型可以使用OpenVINO、ONNX Runtime等框架进行推理。

Paddle转ONNX协议由 paddle2onnx 实现,下面介绍如何将Paddle模型转换为ONNX模型并验证正确性。

本教程涉及的示例代码,可点击 IPython 获取, 除Paddle以外,还需安装以下依赖:

    pip install paddle2onnx onnx onnxruntime // -i https://mirror.baidu.com/pypi/simple 如果网速不好,可以使用其他源下载

3.4.2. 模型导出为ONNX协议

3.4.2.1. 动态图导出ONNX协议

Paddle动态图模型转换为ONNX协议,首先会将Paddle的动态图 paddle.nn.Layer 转换为静态图, 详细原理可以参考 动态图转静态图 。然后依照ONNX的算子协议,将Paddle的算子一一映射为ONNX的算子。动态图转换ONNX调用 paddle.onnx.export() 接口即可实现,该接口通过 input_spec 参数为模型指定输入的形状和数据类型,支持TensorInputSpec ,其中 InputSpec 支持动态的shape。

关于 paddle.onnx.export 接口更详细的使用方法,请参考 API 。

    import paddle
    from paddle import nn
    from paddle.static import InputSpec
    class LinearNet(nn.Layer):
        def __init__(self):
            super(LinearNet, self).__init__()
            self._linear = nn.Linear(784, 10)
        def forward(self, x):
            return self._linear(x)
    # export to ONNX
    layer = LinearNet()
    save_path = 'onnx.save/linear_net'
    x_spec = InputSpec([None, 784], 'float32', 'x')
    paddle.onnx.export(layer, save_path, input_spec=[x_spec])
3.4.2.2. 静态图导出ONNX协议

Paddle 2.0以后将主推动态图组网方式,如果您的模型来自于旧版本的Paddle,使用静态图组网,请参考paddle2onnx的 使用文档 和 示例 。

3.4.3. ONNX模型的验证

ONNX官方工具包提供了API可验证模型的正确性,主要包括两个方面,一是算子是否符合对应版本的协议,二是网络结构是否完整。

    # check by ONNX
    import onnx
    onnx_file = save_path +  '.onnx'
    onnx_model = onnx.load(onnx_file)
    onnx.checker.check_model(onnx_model)
    print('The model is checked!')

如果模型检查失败,请到 Paddle 或 paddle2onnx 提出Issue,我们会跟进相应的问题。

3.4.4. ONNXRuntime推理

本节介绍使用ONNXRuntime对已转换的Paddle模型进行推理,并与使用Paddle进行推理的结果进行对比。

    import numpy as np
    import onnxruntime
    x = np.random.random((2, 784)).astype('float32')
    # predict by ONNX Runtime
    ort_sess = onnxruntime.InferenceSession(onnx_file)
    ort_inputs = {ort_sess.get_inputs()[0].name: x}
    ort_outs = ort_sess.run(None, ort_inputs)
    print("Exported model has been predicted by ONNXRuntime!")
    # predict by Paddle
    layer.eval()
    paddle_outs = layer(x)
    # compare ONNX Runtime and Paddle results
    np.testing.assert_allclose(ort_outs[0], paddle_outs.numpy(), rtol=1.0, atol=1e-05)
    print("The difference of results between ONNXRuntime and Paddle looks good!")

4. ONNX到目标平台

ONNX实际只是一套标准,里面只不过存储了网络的拓扑结构和权重(其实每个深度学习框架最后固化的模型都是类似的),脱离开框架是没有办法直接进行inference的。大部分框架(除了tensorflow)基本都做了ONNX模型inference的支持,这里就不进行展开了。

那么如果你想直接使用ONNX模型来做部署的话,有下列几种情况:第一种情况,目标平台是CUDA或者X86的话,又怕环境配置麻烦采坑,比较推荐使用的是微软的onnxruntime,毕竟是微软亲儿子;第二种情况,而如果目标平台是CUDA又追求极致的效率的话,可以考虑转换成TensorRT;第三种情况,如果目标平台是ARM或者其他IoT设备,那么就要考虑使用端侧推理框架了,例如NCNN、MNN和MACE等等。

第一种情况应该是坑最少的一种了,但要注意的是官方的onnxruntime安装包只支持CUDA 10和Python 3,如果是其他环境可能需要自行编译。安装完成之后推理部署的代码可以直接参考官方文档。

第二种情况要稍微麻烦一点,你需要先搭建好TensorRT的环境,然后可以直接使用TensorRT对ONNX模型进行推理;然后更为推荐的做法是将ONNX模型转换为TensorRT的engine文件,这样可以获得最优的性能。关于ONNX parser部分的代码,NVIDIA是开源出来了的(当然也包括其他parser比如caffe的),不过这一块如果你所使用的模型中包括一些比较少见的OP,可能是会存在一些坑的,比如我们的模型中包含了一个IBN结构,引入了InstanceNormalization这个OP,解决的过程可谓是一波三折;好在NVIDIA有一个论坛,有什么问题或者bug可以在上面进行反馈,专门有NVIDIA的工程师在上面解决大家的问题,不过从我两次反馈bug的响应速度来看NVIDIA还是把TensorRT开源最好,这样方便大家自己去定位bug

第三种情况的话一般问题也不大,由于是在端上执行,计算力有限,所以确保你的模型是经过精简和剪枝过的能够适配移动端的。几个端侧推理框架的性能到底如何并没有定论,由于大家都是手写汇编优化,以卷积为例,有的框架针对不同尺寸的卷积都各写了一种汇编实现,因此不同的模型、不同的端侧推理框架,不同的ARM芯片都有可能导致推理的性能有好有坏,这都是正常情况。

参考: 深度学习模型转换与部署那些事(含ONNX格式详细分析)
参考:
参考:
参考:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/113681.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

钉钉会议室无需API开发轻松连接OA、电商、营销、CRM、用户运营、推广、客服等近千款系统

钉钉会议室支持成员管理、主持人权限管理、高级会控、组织内会议全员静音、共享权限控制等会议管理能力,确保会议安全可控的进行。 官网:https://page.dingtalk.com/wow/z/dingtalk/Rax/RoomsIntro 集简云无代码集成平台,轻松连接钉钉会议室…

动态规划算法实现------转换(编辑、变换)问题

目录 一、字符串转换问题 1.1问题 1.2确定动态规则(DP、状态转移方程)、初始值 (1)插入操作实现状态转移 (2)删除操作实现状态转移 (3)替换操作实现状态转移 (4)初始值 1.3动态规划算法代码实现 (1)完整代码 (2)程序速度优化 二、矩阵变换问题 2.1问题 2.2矩阵乘法 (1)矩阵相乘…

实验记录之——git push

平时做开发的时候经常push代码不成功,如下图 经好友传授经验,有如下方法 Win cmd使用Clash(端口是7890)代理操作,在cmd中输入: set http_proxy127.0.0.1:7890 set https_proxy127.0.0.1:7890Linux export …

Elasticsearch:在你的数据上训练大型语言模型 (LLM)

过去的一两年,大型语言模型(LLM)席卷了互联网。 最近 Google 推出的 PaLM 2 和 OpenAI 推出的 GPT 4激发了企业的想象力。 跨领域构思了许多潜在的用例。 多语言客户支持、代码生成、内容创建和高级聊天机器人都是一些例子。 这些用例要求 LL…

echarts的图表立体感——实现立体柱状图和立体饼图的详细教程

😂博主:小猫娃来啦 😂文章核心:使用echarts实现立体柱状图和立体饼图的详细教程 文章目录 简单介绍立体柱状图和立体饼图环境配置实现立体柱状图实现立体饼图总结 简单介绍立体柱状图和立体饼图 立体柱状图和立体饼图是数据可视化…

Youtube DNN:Deep Neural Networks for YouTube Recommendations

1.介绍 本文主要解决的三个挑战: 大规模的推荐场景,能够支持分布式训练和提供有效率的服务。不断更新的新物料。稀疏的用户行为,包含大量的噪声。 2.推荐系统 文章包含推荐系统的两阶段模型:召回和排序。 召回网络根据用户的历…

【JAVA学习笔记】58 - 泛型

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter15/src/com/yinhai/generic_ https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter15/src/com/yinhai/customgeneric_ 一、泛型的入门和好处 1)请编写程序,…

创新工具箱!重塑手机页面原型设计体验

在2024年,随着移动设备的普及和用户对移动体验的要求不断提升,手机页面原型设计工具变得越来越重要。在这篇文章中,我将为您推荐几款在2024年非常流行且值得一试的手机页面原型设计工具。 Pixso Pixso是一款基于云端的协作设计工具&#xf…

三相电表逆相序是由于负载造成的吗

大家好,最近有蛮多客户问三相电表逆相序是由于负载造成的吗?那么答案是:是的,但是负载只是导致三相电表出现逆向序的原因之一,下面,小编来带大家一起了解下三相电表出现逆相序的原因有哪些,一起…

比亚迪今年的薪资。。

大家或许已经对比亚迪在西安的宣讲会有所耳闻,那场面真的是座无虚席。如果你稍微迟到了一些,那么你可能只能在门外或是走廊听了。 事实上,许多人早早地抵达了,只要稍微晚到,就可能错过了室内的位置。 更令人震惊的是&…

Go语言集成开发环境(IDE):GoLand 2023中文

GoLand 2023是一款由JetBrains开发的现代化、功能丰富的Go语言集成开发环境(IDE)。它提供了智能代码提示和自动完成、强大的内置调试器以及代码重构工具,帮助开发者提高编码效率并确保代码质量。GoLand 2023还支持多种版本控制系统&#xff0…

QT 信号和槽

不讲那么多大道理,直接上 前面用Python QT 发现在线程或者定时器里操作控件,有很大概率导致程序闪退,所以如果想要在线程和定时器中操作控件,需要自定义信号和槽,不知道CQT会不会有这个问题,这个经验不是很…

MySQL的3种索引合并优化⭐️or到底能不能用索引?

MySQL的3种索引合并优化⭐️or到底能不能用索引? 前言 前文我们讨论过MySQL优化回表的多种方式:索引条件下推ICP、多范围读取MRR、覆盖索引等 这篇文章我们来聊聊MySQL提供的另一种优化回表的手段:index merge 索引合并 在阅读本文前,你…

集简云slack(自建)无需API开发轻松连接OA、电商、营销、CRM、用户运营、推广、客服等近千款系统

slack是一个工作效率管理平台,让每个人都能够使用无代码自动化和 AI 功能,还可以无缝连接搜索和知识共享,并确保团队保持联系和参与。在世界各地,Slack 不仅受到公司的信任,同时也是人们偏好使用的平台。 官网&#x…

移动云:IDC容器安全行业代表,领跑云原生安全技术演进

近日,全球领先的IT电信市场研究和咨询公司IDC发布了《中国容器安全市场洞察,2023》报告(简称《报告》),分析了国内容器安全市场现状以及主要供应商并提供了行动建议。移动云云原生应用安全获得本次IDC报告认可&#xf…

5.1 创建和销毁线程

方法 pthread_create(thread, attr, start_routine, arg)pthread_exit(status)pthread_cancel(thread)pthread_attr_init(attr)pthread_attr_destroy(attr) 创建线程 最开始main()程序只有一个默认的线程,其他的线程需要由编程人员显式创建。pthread_create()可以…

如何在麒麟上安装 ONLYOFFICE 桌面编辑器

我们很高兴地告诉大家,ONLYOFFICE 桌面编辑器现已上架麒麟软件商店。请阅读下文了解详情。 关于麒麟 麒麟是一款国产操作系统,主要是为了满足中国市场的需求和偏好而设计的。 它能够与各种硬件平台和软件应用程序的广泛兼容,因而受到认可。…

HALCON的综合应用案例【01】: 3D 算法处理在 Visual Studio 2019 C# 环境中的集成实例

前言: HALCON 为一款比较流行的商业视觉处理软件,他提供了多种开发的模式,可以在HALCON中开发,也可以将HALCON的设计通过导出库的形式集成到其他开发环境里面,以方便系统集成。本文为笔者自己的一个3D 视觉检测项目,利用HALCON的3D 库开发算法,然后,将算法集成到 MS-V…

2023云栖大会:揭示未来科技的璀璨星辰

翻开科技世界的崭新篇章,2023年的云栖大会将在星光璀璨的杭州盛大开启。这一盛会,是科技创新的聚集地,也是前沿科技展示的殿堂。每年,无数优秀的科技人才和业内精英汇聚于此,共同探讨科技的未来趋势和人类发展的无限可…

Java调用HTTPS接口,绕过SSL认证

1:说明 网络编程中,HTTPS(Hypertext Transfer Protocol Secure)是一种通过加密的方式在计算机网络上进行安全通信的协议。网络传输协议,跟http相比更安全,因为他加上了SSL/TLS协议来加密通信内容。 Java调…