(转载)Tools for Learning LLVM TableGen

前提

最近在学习有关llvm的东西,其中TableGen占了一部分,所以想特意学习下TableGen相关的语法。这里找到了LLVM官网的一篇介绍TableGen的博客,学习并使用机器翻译为中文。在文章的最后也添加了一些学习TableGen的资源。

原文地址:Tools for learning LLVM TableGen

正文

TableGen 是 LLVM 项目中用于生成各种文件的一种语言,用于当手动维护编写非常困难时。

例如,它用于定义可在特定架构上使用的所有指令。信息用 TableGen 定义,我们可以根据该单个源文件生成许多东西,比如C++ 代码、文档、命令行选项等。

在LLVM第一个官方Release 版本发布之前,TableGen就存在了,大约20年前。

现在在LLVM项目存储库中,有超过 1000 个TableGen源文件,总计超过500,000 行代码。使其成为LLVM库中第五大最流行的语言。
在这里插入图片描述
随着像MLIR这样的项目也使用了TableGen,如果你正在对LLVM做贡献,你将会在某一时间遇到它。

因为 TableGen 只存在于 LLVM 中,所以就可能有一个问题。与 C++等语言不同,TableGen 没有大量的学习资源。

因此,除了加入一个新项目,您还需要学习一种新的领域特定语言(Domain Specific Language,DSL)。你来LLVM不是为了学习DSL,你可能是来这里写编译器的。

我不能说这个问题什么时候可以解决,但情况并不像看起来那么糟糕。最近 TableGen 工具有了很大的改进,这意味着你可以把更多的精力投入到最初把你带到 LLVM 的目标上。

对于TableGen的简短介绍

假设您想表示一个架构的寄存器。我将在这里特别使用 Arm 的AArchi64。

你可以使用TableGen描述它:

$ cat register.tdclass Register<int _size, string _alias=""> {int size = _size;string alias = _alias;
}// 64 bit general purpose registers are X<N>.
def X0: Register<8> {}
// Some have special alternate names.
def X29: Register<8, "frame pointer"> {}
// Some registers omitted...

默认情况下,TableGen的编译器 llvm-tblgen 会创建 “records”,如下所示。

$ ./bin/llvm-tblgen register.td------------- Classes -----------------
class Register<int Register:_size = ?, string Register:_alias = ""> {int size = Register:_size;string alias = Register:_alias;
}
------------- Defs -----------------
def X0 {        // Registerint size = 8;string alias = "";
}
def X29 {       // Registerint size = 8;string alias = "frame pointer";
}

这是TableGen的中间表示IR,类似于LLVM中的"LLVM IR"。

在使用 LLVM 时,您将选择一个 “target”,这是你想要为其生成指令的处理器架构。在TableGen这里的等价物是 “backend”。这些后端不生成指令,而是为该后端的特定用例输出一种格式化的定义或数据

例如,有一个后端生成用于搜索数据表的 C++ 代码,其他示例是 C 头文件和重结构化文本文档。
在这里插入图片描述
主要使用的编译器是llvm-tblgen,但也有其他特定于LLVM中子项目的编译器。例如clang-tblgenlldb-tblgen。唯一的区别是它们包含的后端不同,语言是相同的。

你可以采用你的寄存器定义并生成C++代码,以便在某种引导加载程序中对其进行初始化。也许你还可以对其进行记录并生成该过程的图表。如果有足够的后端,你可以从相同的TableGen源代码完成所有这些操作。

在使用TableGen创建后端时,可以通过两种方式实现:

  • 在TableGen编译器内部用C++编写后端
  • 作为外部后端,通过TableGen的JSON输出生成所需数据:TableGen 提供 --dump-json 选项,可以将编译器内部数据以 JSON 格式输出。这样你可以使用支持 JSON 解析的任何语言(例如 Python)编写外部程序,来读取和处理这些 JSON 数据,以实现自定义的后端。

TableGen和使用TableGen构建的工具

TableGen更多的是一种思维方式,而不是一种工具。最好的总结方式是引用文档中的一句话:

尽管 TableGen 非常通用,但它仍存在一些缺陷,而且这些缺陷已经被多次指出。总体的反馈是,虽然 TableGen 允许用户创建领域特定语言(DSL,Domain-Specific Language),但这些最终生成的语言缺乏其他 DSL 的强大功能,这反过来使得 TableGen 文件的规模和复杂性显著增加。
同时,TableGen 允许用户通过自定义的后端,几乎可以为基本概念赋予任何意义,这种灵活性可能会偏离最初设计的意图,导致 TableGen 文件难以理解,对新手尤其不友好,甚至可能变得“邪恶难懂”。

在使用 LLVM 时,你将会接触到 TableGen 以及由 TableGen 构建的各种工具和配置,而这些通常比 TableGen 语言本身更复杂。

这就像学习 C++ 的过程中遇到了 Boost 库。有人可能会告诉你:“Boost 并不是必须的,为什么不去掉它,省些麻烦呢?”但是,作为 C++ 的新手,你可能并不了解 C++ 和 Boost 之间的界限。

当然,如果你想要参与的项目中使用了 Boost,那么这种建议并没有太大帮助——你依然不得不处理 C++ 和 Boost。对于 LLVM 而言,TableGen 语言和使用它的后端工具是一揽子服务,你必须一起理解它们。

我之所以提到这些,是希望你可以区分自己不理解的到底是 TableGen 本身,还是使用它构建的其他工具。清楚究竟是哪个部分让你困惑,将更有利于你寻求帮助。

对于任何任务来说,你可能只需要理解一两个“用 TableGen 构建的工具”,而且往往不需要全面掌握它们。

不要觉得自己在学习 TableGen 时一定要弄懂它的所有应用方式。这当然是可能的,但并非必要,而且几乎没有人会完全精通。相反,把精力放在那些真正让你感兴趣的部分才是更明智的选择。

Compiler Explorer

我们在 Compiler Explorer 中也有 TableGen!如果一种语言不在 Compiler Explorer 中,它还算是“真实存在”吗?(当然算,不过如果你喜欢的语言没有出现在其中,Compiler Explorer 有出色的文档和友好的维护人员)。

Compiler Explorer 是一个强大的在线工具,支持不同语言和架构的各种编译器版本,只需在浏览器中打开一个标签页即可访问。这个工具在学习、教学、调试、优化等方面非常有用。本文不会详细介绍它的功能,只简单提及一下 TableGen 在 Compiler Explorer 中的使用情况。

显然,llvm-tblgen(TableGen 编译工具)不会输出机器指令(不过理论上可以有这样的后端),因此它没有编译为二进制或执行代码的选项。

默认情况下,TableGen 中的记录(记录结构)会以纯文本形式输出。你可以通过添加编译选项选择后端,或者在 “Overrides”(覆盖)菜单中选择“Action”(动作)来选择不同的后端。

需要注意的是,TableGen 的后端对源代码的内容有非常具体的要求。就像你有一个 C++ 编译器,除非代码中包含arm_is_cool,否则它不会编译 Arm 架构的代码。

在 LLVM 仓库中,所有所需的类都已为你设置好,但在 Compiler Explorer 中则没有。因此,如果你想在现有后端上进行实验,建议提供一些类的简易实现,或者从 LLVM 项目仓库中复制一些代码。你也可以使用 include/llvm/*.td 中的标准包含文件。

目前在 Compiler Explorer 中无法开发一个后端,但你可以选择 JSON 后端并将生成的 JSON 复制到本地脚本中进一步处理。

Compiler Explorer 也支持多文件项目(“IDE 模式”),因此你可以有自己的包含文件。

最后,记住你可以分享 Compiler Explorer 中的示例。如果你在提问或解答有关 TableGen 的问题时,尽量包含一个 Compiler Explorer 的链接!

Jupyter Notebooks

Jupyter 可以创建交互式笔记本。一个笔记本是一个包含文本、代码和代码运行结果的文档。这种方式允许你编辑代码并重新运行,以在笔记本中更新结果。

这种功能非常适合做笔记,或者从小的代码片段构建出大型示例。你可以将文档导出为可编辑的笔记本文件,便于他人修改,也可以导出为非交互式格式,如 PDF 或 Markdown。

可以通过 TableGen Jupyter Kernel 在 Jupyter 笔记本中使用 TableGen。安装说明可以在这里找到,而详细介绍可以参考我在这里的讲解。

注意:Jupyter 还有 MLIR Kernel 和其他许多 Kernel 可用。

我们力图为 TableGen 提供与其他语言相同的体验,所以我不会专注于如何使用 Jupyter 笔记本,而是展示我们能使用它们实现的成果。

TableGen 教程 Notebook

这个notebook 是对TableGen的介绍,你可以在Github上阅读,或者下载它在Jupter中阅读。

当使用Jupyter时,你可以修改文档添加你自己的例程,或者扩展你感兴趣的点。

如何编写TableGen后端的Notebook

这个Notebook使用 Python 代替 TableGen,展示了如何编写一个后端。

这个笔记本是基于 Min-Yih Hsu 在 2021 年欧盟 LLVM 开发者会议上的演讲《如何编写 TableGen 后端》。事实上,该Notebook是 Min C++实现的 Python 版本。

它展示了如何获取 llvm-tblgen 的 JSON 输出,并使用 Python 处理它,生成 SQL 查询。

特别之处在于:我们现在拥有相同内容的多种媒介形式和多种编程语言版本。可以选择最适合自己的学习方式。

回到之前提到的“TableGen和使用TableGen构建的工具”概念,这个教程笔记本是关于 TableGen 的内容,而“如何编写后端”则属于“由 TableGen 构建的工具”。

限制

该Notebook的一个主要限制是无法进行输出过滤。也就是说,如果你在Notebook中 include "llvm/Target/Target.td",那么会产生大约 320,000 行输出(即使你还没有添加任何代码)。这个输出量超过了默认notebook对内核的接受限度,并且当我移除该限制时,浏览器标签页会崩溃。

在大多数情况下,这不是问题,而且可行的解决方案都有较大的权衡,因此我们不会急于推出修复。如果这个问题确实影响到了你,请在跟踪问题页面中提交反馈。

TableGen 语言服务

MLIR 项目实现了一个兼容语言服务器协议(LSP 的服务器,支持 TableGen 及 MLIR 中使用的另外两种语言。

语言服务器协议(LSP 为兼容的编辑器提供有关语言和项目结构的信息。例如:包含文件的位置、特定类型定义的位置等。

如果你使用过兼容 LSP 的编辑器(比如 Visual Studio Code),很可能已经在不知情的情况下用过语言服务器。最常见的功能是“跳转到定义”(Go To Definition)。

LSP 允许你打开一个项目,找到想修改的代码,并直接跳转到仓库中的其他相关部分。LLVM 项目中包含超过 500,000 行的 TableGen 代码,因此有了 LSP 的帮助,能够忽略大量无关的代码!

安装使用

你需要一个名为 tblgen-lsp-server 的服务器二进制文件,可以从适合你平台的发布包中获取,也可以自行构建。

使用以下命令自行构建:

$ cmake -G Ninja <path-to>/llvm-project/llvm -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="mlir"
$ ninja tblgen-lsp-server

运行这些命令后,tblgen-lsp-server 二进制文件会出现在 <build-dir>/bin/ 目录中。

tblgen-lsp-server 需要一个名为 tablegen_compile_commands.yml 的编译数据库文件。当你使用 CMake 配置 LLVM 时,该文件会自动生成。

这个文件的作用类似于使用 CMAKE_EXPORT_COMPILE_COMMANDS 时生成的 compile_commands.json 文件,但这两个文件并无直接关系。

只要你当前的 llvm-project 包含一个特定的提交,生成的编译数据库就会包含所有启用项目中的 TableGen 文件(在该提交之前,这个文件只包含 MLIR 项目的文件)。

例如,以下配置命令会包含LLVM、Clang、MLIR 和 LLDB 子项目中的 TableGen 文件信息:

$ cmake -G Ninja <path-to>/llvm-project/llvm -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;llvm;lldb;mlir"

这个命令同样适用于 -DLLVM_TARGETS_TO_BUILD= 选项。如果只启用了一个目标,那么编译数据库中就只包含与该目标相关的文件。

注意: 你不需要构建项目即可将其 TableGen 文件包含在编译数据库中,只需完成配置步骤即可。(Note: You do not need to build a project to include its TableGen files in the compilation database. Configuring is all that is needed.)

接下来,为你的编辑器配置 LSP 客户端:

Visual Studio Code:安装 MLIR扩展,并按照说明设置扩展,以指定服务器和编译数据库的位置。

如果你正在使用其他编辑器,参考其文档了解如何配置语言服务器。设置编译数据库路径可能需要使用服务器的命令行选项。运行tblgen-lsp-server --help 查看所有可用选项。

示例

This example assumes you have configured LLVM with the AArch64 target enabled. (It is enabled by default)

  • Open the file llvm/lib/Target/AArch64/AArch64.td.
  • Put your cursor on a use of the SubtargetFeature type.
  • In the menu bar, select “Go” then “Go to Definition”.
  • This takes you to llvm/include/llvm/Target/Target.td, where SubtargetFeature is defined.

限制

语言服务器 揭示了某些 LLVM 目标(如 AArch64)使用 TableGen 时的一个反模式(anti-pattern)。

在这种模式下,你可能会在一个文件中使用某个类,但该文件既没有定义该类,也没有包含定义该类的文件。这是因为该文件本身是设计用来被包含在另一个文件中的,而这个文件(即包含它的文件)会包含定义该类的文件。

// example.td
class Example {}// uses_example.td
def example: Example {}// main.td
include "example.td"
include "uses_example.td"
  • example.td 定义了 Example 类。
  • uses_example.td 使用了 Example 类,但没有包含 example.td
  • main.td 包含了 example.tduses_example.td
  • 编译时使用的是 main.td
  • 当你在 uses_example.td 文件中时,语言服务器无法知道 Example 类在哪里定义,因为 uses_example.td 并没有直接包含 example.td
  • 当你在 main.td 中时,语言服务器能够找到 Example 类的定义,因为 main.td 包含了 example.tduses_example.td

这种反模式的问题在于,单独查看 uses_example.td 时,它看起来像一个孤立的文件,语言服务器无法正确理解它的上下文。

Dump

printf是最好的debug工具,在TableGen同等的是dump以及它的伙伴repr

def op;
class A {string A = "some text";dag X =(op op);
}
def a : A;dump "The Value of a is: \n" # !repr(a);

dump会打印到stderr

def op;
class A {string A = "some text";dag X =(op op);
}
def a : A;dump "The Value of a is: \n" # !repr(a);

这个功能最近才被添加,因此你需要最近构建的版本,18.0版本或更高。

当然你也可以在Compiler Explorer中使用它。

Assertions

**断言(assertion)**用于检查程序中特定条件是否为真。一个断言由以下三部分组成:

  • assert 关键字。
  • 一个条件(通常通过 bang 操作符之一来判断)。
  • 一条信息(出错时显示的消息)。

如果条件为假,则生成一个编译器错误并显示提供的消息。

以下代码检查是否尝试创建一个大小小于 0 的寄存器:

class Register<int _size> {assert !gt(_size, 0),"Register size must be > 0, not " # _size # "." ;int size = _size;
}def X0: Register<8> {}
def X1: Register<-8> {}

在Compiler Explorer中尝试。

在这个例子中:

  • 寄存器 X0:_size=8,条件 !gt(_size, 0)(C 语言中等同于 _size > 0)为真,因此不会生成错误。

  • 寄存器 X1:_size=-8,条件为假,因此会生成错误,编译器输出如下:

    <source>:2:11: error: assertion failedassert !gt(_size, 0),^
    note: Register size must be > 0, not -8.
    

在学习新代码时,添加自己的断言来检查假设会很有帮助。此外,为他人使用的代码添加断言是防止误用的好方法。和文档不同,断言错误是无法忽略的,一旦条件不满足就会被立刻发现。

Find In Files

将文本搜索(如 grep、ack 或“查找文件中的内容”)放在最后是因为在理想情况下,这种方法是最后的选择。但实际上,这种方法并不是最次的选择。如果你对语言语法有一定了解,文本搜索的效果往往超出预期。

为什么我要提到这么显而易见的想法?显然,“显而易见”是主观的,而且在某些特定情况下,文本搜索的效果会更加明显。

在 LLVM 项目仓库中,我们使用了很多 TableGen 代码。如果你想了解某个特定功能的用法,可以在超过 500,000 行的源代码中找到它。你可能会惊讶地发现,一个简单的查询就能找到有用的信息。

考虑一下你想要查找的内容,想象一下它的源代码会是什么样子。如果是一个类,它会带有模板参数吗?那么类名后面可能会有 <。如果是错误消息,哪些部分是固定的,哪些部分是插入的模板内容?

例如,“期望的行尾”可能是一个静态字符串,所以可以直接搜索该消息。而像 “class Foo has no attribute Bar” 这样的消息更可能是通过替换类名和属性名生成的,因此更好的搜索词可能是“has no attribute”。

LLVM 项目中也有许多编译器测试,大部分都在特定文件夹中,这些文件夹包含了语言特性的最小示例。可以尝试将搜索范围缩小到这些文件夹中。

Conclusion

学习 TableGen 并不需要让人感到害怕。不要因为它是一个独立的 DSL 就觉得它不具备你在其他喜欢的编程语言中所期望的功能。

要记住,TableGen 只是一个工具,而不是学习的最终目标。如果你能够通过对 TableGen 及其后端有限但准确的理解来实现你的目标,那就足够了。根据自己的需求和兴趣,学多少都可以。

除了工具支持之外,还有一个活跃的社区,随时可以在 Discord 或论坛上解答你的问题。

如果你发现了问题或希望做出改进,欢迎参与贡献,可以在 GitHub 上提交 Issue 或 Pull Request。

想想你使用的其他语言。它们是否有类似的工具?是否应该有?这些工具可能是让你从沮丧中解脱出来、爱上这门新语言的关键。

自已的收获

其实这篇博客只是从很宏观的层面讲了TableGen,但是对于我们这种新手来说,第一件事是要能读懂TableGen的语法,也就是可以看懂*.td文件。这里我推荐可以把这个全文快速看一篇,然后直接去跑官方的那个notebook,这样可以快速入门。

参考链接

  1. https://github.com/llvm/llvm-project/tree/main/llvm/utils/TableGen/jupyter
  2. LLVM 之后端篇(1):零基础快速入门 TableGen
  3. 官方TableGen手册:https://llvm.org/docs/TableGen/ProgRef.html
  4. TableGen官方页面:https://llvm.org/docs/TableGen/index.html

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LARGE SCALE GAN TRAINING FORHIGH FIDELITY NATURAL IMAGE SYNTHESIS

ABSTRACT 尽管最近在生成图像建模方面取得了进展&#xff0c;但成功地从复杂的数据集(如ImageNet)生成高分辨率、多样化的样本仍然是一个难以实现的目标。为此&#xff0c;我们在迄今为止尝试的最大规模上训练生成对抗网络&#xff0c;并研究这种规模特有的不稳定性。我们发现…

组队学习专用——task05

目录 一、基本原理 1. 决策树的基本思想 2. 决策树划分过程中可能遇到的特殊情况 (1) 单一类别终止&#xff1a; (2) 属性用尽&#xff1a; (3) 样本空缺&#xff1a; 3. 决策树的结构 二、信息量、信息熵 1. 信息量 2. 信息熵 三、决策树 1. ID3 决策树&#xff08…

Linux RAID 技术详解:原理、配置、管理及故障处理

本文档深入探讨 Linux 软件 RAID 技术&#xff0c;涵盖 RAID 原理、各种 RAID 级别、mdadm 命令详解、配置步骤、管理方法以及高级故障处理和性能调优策略。 一、 RAID 原理与架构 RAID (Redundant Arrays of Independent Disks&#xff0c;独立磁盘冗余阵列) 技术并非单一技…

「C/C++」C/C++标准库 之 <cstring> 字符串操作库

✨博客主页何曾参静谧的博客📌文章专栏「C/C++」C/C++程序设计📚全部专栏「VS」Visual Studio「C/C++」C/C++程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid函数说明目…

单细胞数据分析(一):10X数据生成seurat数据对象

文章目录 介绍加载R包数据链接导入数据过滤细胞:移除双重细胞合并所有seurat数据对象输出结果系统信息介绍 在单细胞基因组学研究中,Seurat是一个流行的R包,用于单细胞基因表达数据的分析和探索。以下是如何从10X基因注释数据生成Seurat数据对象,并对该数据进行过滤的步骤…

day13:FTP服务

一&#xff0c;FTP概述 概述 FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09;是用于在计算机之间传输文件的协议。它工作在网络的应用层&#xff0c;通过 TCP 协议进行数据传输&#xff0c;默认端口号为 21&#xff08;命令端口&#xff0c;另外…

从0开始的STM32学习之旅:使用中断完成等待型任务(理论部分)

目录 中断 NVIC 中断优先级 先说说一般我们聊到的中断优先级 STM32中的NVIC中断优先级表示 API CubeMx配置说明 很好&#xff0c;我们在之前的学习中已经完成了简单的GPIO输入输出驱动的控制&#xff0c;我们的下一步就是仔细了解一下中断这个事情。 中断 中断这个事情…

使用Python进行数据分析入门

文章目录 Python环境搭建安装Anaconda验证安装 必备库介绍NumPyPandasMatplotlibSciPy 数据导入与清洗导入数据清洗数据 数据探索与分析描述性统计相关性分析 数据可视化绘制直方图 高级主题机器学习深度学习 总结 随着大数据时代的到来&#xff0c;数据分析变得越来越重要。Py…

RabbitMQ + JMeter组合,优化你的中间件处理方式!

RabbitMQ是实现了高级消息队列协议&#xff08;AMQP&#xff09;的开源消息中间件&#xff0c;它是基于Erlang语言编写的&#xff0c;并发能力强&#xff0c;性能好&#xff0c;是目前主流的消息队列中间件之一。 RabbitMQ的安装可参照官网&#xff08; https://www.rabbitmq.c…

python 爬虫 入门 五、抓取图片、视频

目录 一、图片、音频 二、下载视频&#xff1a; 一、图片、音频 抓取图片的手法在上一篇python 爬虫 入门 四、线程&#xff0c;进程&#xff0c;协程-CSDN博客里面其实有&#xff0c;就是文章中的图片部分&#xff0c;在那一篇文章&#xff0c;初始代码的28&#xff0c;29行…

Vulnhub靶机——DC-1

#环境 本次环境是利用VMware15搭建 靶机DC-1( https://download.vulnhub.com/dc/DC-1.zip) --192.168.200.131 Kali2021.1--192.168.200 将以上两个操作系统启动&#xff0c;并设置网卡为NAT模式 #信息收集 使用arp-scan对网段进行扫描&#xff0c;探测存活主机 使用nmap…

吴恩达深度学习笔记(10)12.

多任务学习 什么时候多任务学习有意义&#xff1a; 能从共享低级特征中获益&#xff1b;每个任务拥有的数据相当相似&#xff1b;训练一个足够大的神经网络用于所有任务。 目标定位&#xff1a; 分类定位&#xff08;单个对象&#xff09;与检测不同&#xff08;多个对象&…

Android 音量调节流程分析

音量调节流程分析 按下音量键 音量键被按下后&#xff0c;按键事件会一路派发给Acitivity&#xff0c;如果无人拦截并处理&#xff0c;承载当前Activity的显示PhoneWindow类的onKeyDown()以及onKeyUp()函数将会被处理&#xff0c;从而开始通过音量键调整音量的处理流程&#x…

LLMs在股票投资组合崩溃中的时间关系推理

1. 引言 想象一下&#xff0c;你是一位投资经理&#xff0c;管理着一个多元化的股票投资组合。突然&#xff0c;一场前所未有的全球性事件发生了&#xff0c;比如2007年的金融危机或2020年的新冠疫情&#xff0c;这可能会对你的投资组合造成重大影响。传统的投资组合崩溃检测方…

o1驾驶无人机后空翻,OpenAI开发者日惊掉下巴!2分钟爆改代码写App

【导读】OpenAI伦敦开发者日上&#xff0c;首次曝出了o1五大核心能力&#xff0c;还有图像理解。o1两分钟构建应用驾驶无人机、电话订餐、讲解太阳系&#xff0c;现场演示让所有开发者沸腾。 完整版o1的解禁&#xff0c;离我们不远了&#xff01; 就在刚刚举办的OpenAI伦敦开…

利用AWS服务轻松迁移数据上云

在数字化转型的浪潮中&#xff0c;越来越多的企业意识到将数据迁移至云端的重要性。云服务不仅能够提供更高的灵活性和可扩展性&#xff0c;还能显著降低IT成本。AWS&#xff08;Amazon Web Services&#xff09;作为全球领先的云服务提供商&#xff0c;为企业提供了一系列高效…

期货跟单、量化交易模拟演示系统

演示版可直接下载本文绑定资源。 一、跟单下单 在“排行榜”中选择要跟单的用户&#xff0c;合约可以跟全部&#xff0c;也可以指定跟该用户的某一合约操作&#xff0c;选定跟单的倍数&#xff08;操作手数的倍数&#xff09;/手数&#xff08;指定手数&#xff0c;可以不是对…

界面控件DevExpress WPF中文教程:Data Grid——卡片视图概述

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

【数据结构二叉树】补充:C实现二叉树的层次遍历

1、层次遍历 按层次遍历二叉树的方式&#xff1a;按照“从上到下&#xff0c;从左到右”的顺序遍历二叉树&#xff0c;即先遍历二叉树的第一层的结点&#xff0c;然后是第二层的结点&#xff0c;直到最底层的结点&#xff0c;对每一层的遍历按照从左到右的次序进行。 2、层次…

供应商图纸外发:如何做到既安全又高效?

供应商跟合作伙伴、客户之间会涉及到图纸外发的场景&#xff0c;这是一个涉及数据安全、效率及合规性的重要环节。供应商图纸发送流程一般如下&#xff1a; 1.申请与审批 采购人员根据需要提出发放图纸的申请并提交审批&#xff1b; 采购部负责人审批发放申请&#xff0c;确…