【OpenGauss源码学习 —— (RowToVec)算子】

VecToRow 算子

  • 概述
  • ExecInitRowToVec 函数
  • ExecRowToVec 函数
    • VectorizeOneTuple 函数
  • ExecEndRowToVec 函数
  • 总结

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》和《PostgresSQL数据库内核分析》一书

概述

  OpenGaussPortalRun 函数中会实际执行相关的 DML 查询,对数据进行计算和处理。在执行过程中,所有执行算子分为两大类行存储算子向量化算子。这两类算子分别对应行存储执行引擎向量化执行引擎行存储执行引擎的上层入口是 ExecutePlan 函数,向量化执行引擎的上层人口是 ExecuteVectorizedPlan 函数。其中向量化引擎是针对列存储表的执行引擎。如果存在行存储表和列存储表的混合计算,那么行存储执行引擎和向量化执行引擎直接可以通过 VecToRowRowToVec 算子进行相互转换行存储算子执行入口函数的命名规则一般为 “Exec + 算子名” 的形式,向量化算子执行入口函数的命名规则一般为 “ExecVee十算子名” 的形式,通过这样的命名规则,可以快速地找到对应算子的函数入口。
  在【OpenGauss源码学习 —— (VecToRow)算子】一文中,我们学习了VecToRow 算子的执行流程,本文则来补充学习一下 RowToVec 算子的执行流程。
  RowToVec 算子的主要功能和作用是将行存储表中的数据按列存储格式重新组织和转换,以便能够有效地交给向量化执行引擎处理。它负责将行数据逐行提取并转化成列向量,使得数据库系统能够在混合计算环境中高效地处理包括行存储表和列存储表的数据,从而实现更快速、更优化的查询和数据处理操作。这个过程是在混合存储环境中实现无缝数据转换的关键部分,有助于提高数据库查询性能和整体系统效率。
  RowToVec 算子的执行流程通常包括以下步骤:首先,RowToVec 算子从行存储表中获取一批行数据,这些行数据以行的形式存储在内存中。然后,它会逐行地将这些数据转换为列存储格式,这意味着将每列的数据分别提取出来,组成列向量。这个转换过程包括数据的重新排列和重新组织,以适应列存储的数据结构。一旦所有行数据都被转换为列向量,RowToVec 算子就会将这些向量传递给向量化执行引擎,以便后续的处理。这种转换操作允许在混合计算将行存储表和列存储表的数据进行有效整合,以便进行更高效的向量化计算。这样的执行流程可以在混合存储环境中实现数据的无缝转换和处理,以提高查询性能和效率。下面我们来详细的看一看相关函数源码一遍更好地理解和学习吧。

ExecInitRowToVec 函数

  ExecInitRowToVec 函数的主要功能是为 RowToVec 算子创建执行状态,并初始化该状态以准备执行操作。它包括创建状态结构设置计划节点初始化元组表分配向量缓冲初始化子节点等步骤。这个过程确保了 RowToVec 算子能够正确地将行数据转换为列向量,并在混合计算中无缝运行,以提高数据库查询性能。
  ExecInitRowToVec 函数的执行流程可以简要描述如下:

  1. 创建 RowToVecState 结构:首先,函数创建了一个 RowToVecState 结构,该结构用于存储 RowToVec 算子的执行状态信息。
  2. 设置基本属性:函数设置 RowToVecState 中的一些基本属性,如关联的计划节点执行状态、以及标记为向量化执行
  3. 检查数据类型支持:函数检查 RowToVec 算子是否支持所需的数据类型,以确保正确的数据处理。
  4. 初始化元组表:函数初始化用于存储结果的元组表,并分配所需的内存。
  5. 初始化子节点:函数初始化 RowToVec 算子的子节点,确保子节点能够正常执行。
  6. 创建表达式上下文:函数为 RowToVecState 创建表达式上下文,以便在执行期间进行表达式计算。
  7. 初始化元组类型:函数设置 RowToVecState元组类型,以匹配子节点的输出类型。
  8. 分配向量缓冲:函数为列向量数据分配内存,以便存储转换后的数据。
  9. 返回状态结构:最后,函数返回初始化完成的 RowToVecState 结构,以供后续的执行操作使用。

  ExecInitRowToVec 函数源码如下所示:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

// 初始化 RowToVecState 结构
// 这段代码的作用是初始化 RowToVecState 结构,为 RowToVec 算子的执行创建执行状态。
// 具体步骤包括创建状态结构、设置计划节点、初始化元组表、分配向量缓冲、初始化子节点等。
RowToVecState* ExecInitRowToVec(RowToVec* node, EState* estate, int eflags)
{
    RowToVecState* state = NULL;

    /*
     * 创建状态结构
     */
    state = makeNode(RowToVecState);
    state->ps.plan = (Plan*)node;
    state->ps.state = estate;
    state->ps.vectorized = true;

    // 检查 RowToVec 算子是否支持所需数据类型
    CheckTypeSupportRowToVec(node->plan.targetlist);

    /*
     * 初始化元组表
     *
     * 排序节点只从其排序关系中返回扫描元组。
     */
    ExecInitResultTupleSlot(estate, &state->ps);

    /* 分配向量缓冲 */
    state->m_fNoMoreRows = false;

    /*
     * 初始化子节点
     *
     * 我们屏蔽了子节点对支持 REWIND、BACKWARD 或 MARK/RESTORE 的需求。
     */
    outerPlanState(state) = ExecInitNode(outerPlan(node), estate, eflags);

    /*
     * 杂项初始化
     *
     * 为节点创建表达式上下文
     */
    ExecAssignExprContext(estate, &state->ps);

    /*
     * 初始化元组类型。不需要初始化投影信息,因为此节点不进行投影。
     */
    ExecAssignResultTypeFromTL(
            &state->ps,
            ExecGetResultType(outerPlanState(state))->tdTableAmType);

    TupleDesc res_desc = state->ps.ps_ResultTupleSlot->tts_tupleDescriptor;
    state->m_pCurrentBatch = New(CurrentMemoryContext) VectorBatch(CurrentMemoryContext, res_desc);
    state->ps.ps_ProjInfo = NULL;

    return state;
}

ExecRowToVec 函数

  ExecRowToVec 函数的主要功能是将行数据转换为向量批次,用于向量化处理。它通过不断从外部计划节点获取行数据,然后将每个行数据向量化,直到外部计划的所有行都处理完毕。函数中的 VectorizeOneTuple 函数用于将单个行元组转换为向量,并使用适当的内存管理。一旦向量批次已满或外部计划的行耗尽,函数将返回当前的向量批次。
  ExecRowToVec 函数的的执行流程可以简要描述如下:

  1. 准备必要的变量和数据结构,包括初始化向量批次(VectorBatch)和执行上下文(ExprContext)。
  2. 重置执行上下文的内存,以确保在处理每个行数据时不会出现内存混淆。
  3. 获取外部计划节点的状态信息,准备从外部计划获取行数据。
  4. 如果已经没有更多的行数据需要处理(由 m_fNoMoreRows 标志控制),则直接跳到完成步骤。
  5. 通过循环迭代处理外部计划的每个行数据,每次获取一个外部计划的元组。
  6. 对每个获取的行数据执行向量化处理,将行数据转换为向量格式,并使用适当的内存进行管理。
  7. 如果向量批次已满,或者外部计划的所有行都已处理完毕,则结束循环。
  8. 在完成步骤中,将向量批次中的每列的行数设置为相同的值,以标记批次中的有效数据行数
  9. 最后,返回向量批次,其中包含转换后的向量化数据。

  ExecRowToVec 函数的源码如下:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

/*
 * @Description: 向量化运算符--将行数据转换为向量批次。
 *
 * @IN state: RowToVecState 结构,用于管理 RowToVec 算子的执行状态。
 * @return: 返回包含行表数据的向量批次,如果没有数据可转换则返回 NULL。
 */
VectorBatch* ExecRowToVec(RowToVecState* state)
{
    int i;
    PlanState* outer_plan = NULL;
    TupleTableSlot* outer_slot = NULL;
    VectorBatch* batch = state->m_pCurrentBatch;

    /* 重置当前的 ecxt_per_tuple_memory 上下文 */
    ExprContext* econtext = state->ps.ps_ExprContext;
    ResetExprContext(econtext);

    /* 从节点获取状态信息 */
    outer_plan = outerPlanState(state);
    batch->Reset();

    /*
     * 如果在返回 NULL 后再次调用 ExecProcNode(),它可能会重新启动,
     * 因此我们需要自行进行保护。
     */
    if (state->m_fNoMoreRows) {
        goto done;
    }

    /*
     * 处理每个外部计划元组,然后获取下一个元组,直到外部计划耗尽。
     */
    for (;;) {
        outer_slot = ExecProcNode(outer_plan);
        if (TupIsNull(outer_slot)) {
            state->m_fNoMoreRows = true;
            break;
        }

        /*
         * 向量化一个元组并切换到 exprcontext 的 ecxt_per_tuple_memory。
         */
        if (VectorizeOneTuple(batch, outer_slot, econtext->ecxt_per_tuple_memory)) {
            /* 批次已满,现在返回当前批次 */
            break;
        }
    }

done:
    for (i = 0; i < batch->m_cols; i++) {
        batch->m_arr[i].m_rows = batch->m_rows;
    }

    return batch;
}

VectorizeOneTuple 函数

  VectorizeOneTuple 函数的作用是将一个数据库查询结果中的单个元组(Tuple)转换为向量化数据结构(VectorBatch),以便进行向量化处理。它会逐一提取元组中的每列数据,并根据列的数据类型长度将其合适地存储在向量批次中的相应列中。此功能是在向量化执行引擎中关键的一步,通过高效地将行数据转换为列存储的向量形式,有助于加速数据库查询的处理速度和效率。此外,该函数还会检查向量批次是否已满,以便控制向量处理的大小和内存使用
  VectorizeOneTuple 函数的执行流程可以简要描述如下:

  1. 初始化必要的变量和标志,包括一个标志变量 may_more 用于表示是否可能还有更多的行需要处理,以及循环迭代中使用的计数器变量 i 和 j
  2. 切换当前内存上下文到传入的 transformContext 上下文,以确保内存管理的正确性。
  3. 确保输入的插槽 slot 不为空,并且具有有效的元组描述符
  4. 使用 tableam_tslot_getallattrs 函数从插槽中提取所有列的属性值
  5. 开始循环迭代插槽中的每个列数据处理每列的数据类型和长度
  6. 根据列的数据类型长度选择合适的处理方式,包括整数浮点数变长数据大整数TID行标识符)等。
  7. 如果列不为空(slot->tts_isnull[i] == false),则将列的值存储到向量批次的相应列中,并标记该列的数据为非 NULL
  8. 如果列为空(slot->tts_isnull[i] == true),则将向量批次中的相应列标记为 NULL
  9. 增加向量批次中的行数计数器 pBatch->m_rows
  10. 如果向量批次已满(行数达到 BatchMaxSize),则设置 may_more 标志为 true
  11. 切换回之前的内存上下文,以确保内存管理的正确性。
  12. 返回 may_more 标志,指示向量批次是否已满,以便在上层调用中做出相应的处理。

  VectorizeOneTuple 函数的源码如下:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

/*
 * @Description: 将一个元组打包成向量批次。
 *
 * @IN pBatch: 目标向量化数据。
 * @IN slot:   一个插槽(TupleTableSlot)中的源数据。
 * @IN transformContext: 切换到此上下文以避免内存泄漏。
 * @return: 如果 pBatch 已满,则返回 true,否则返回 false。
 */
bool VectorizeOneTuple(_in_ VectorBatch* pBatch, _in_ TupleTableSlot* slot, _in_ MemoryContext transformContext)
{
    bool may_more = false;  // 标志是否可能还有更多行需要处理
    int i, j;

    /* 切换到当前的转换上下文 */
    MemoryContext old_context = MemoryContextSwitchTo(transformContext);

    /*
     * 提取旧元组的所有值。
     */
    Assert(slot != NULL && slot->tts_tupleDescriptor != NULL);

    tableam_tslot_getallattrs(slot);  // 从插槽中获取所有属性值

    j = pBatch->m_rows;
    for (i = 0; i < slot->tts_nvalid; i++) {  // 遍历元组的每一列
        int type_len;
        Form_pg_attribute attr = slot->tts_tupleDescriptor->attrs[i];  // 获取列的属性信息

        pBatch->m_arr[i].m_desc.typeId = attr->atttypid;  // 设置向量批次中列的数据类型

        if (slot->tts_isnull[i] == false) {  // 检查列是否为 NULL
            type_len = attr->attlen;  // 获取列的数据长度
            switch (type_len) {  // 根据数据长度选择合适的处理方式
                case sizeof(char):
                case sizeof(int16):
                case sizeof(int32):
                case sizeof(Datum):
                    pBatch->m_arr[i].m_vals[j] = slot->tts_values[i];  // 处理整数和浮点数
                    break;
                case 12:
                case 16:
                case 64:
                case -2:
                    pBatch->m_arr[i].AddVar(slot->tts_values[i], j);  // 处理变长数据
                    break;
                case -1: {
                    Datum v = PointerGetDatum(PG_DETOAST_DATUM(slot->tts_values[i]));  // 解压缩数据
                    /* 如果是 numeric 列,尝试将 numeric 转换为 big integer */
                    if (attr->atttypid == NUMERICOID) {
                        v = try_convert_numeric_normal_to_fast(v);
                    }
                    pBatch->m_arr[i].AddVar(v, j);  // 处理大整数和压缩数据
                    /* 由于可能创建了新的内存,因此我们必须及时检查和释放。 */
                    if (DatumGetPointer(slot->tts_values[i]) != DatumGetPointer(v)) {
                        pfree(DatumGetPointer(v));  // 释放临时内存
                    }
                    break;
                }
                case 6:
                    if (attr->atttypid == TIDOID && attr->attbyval == false) {  // 处理 TID 数据
                        pBatch->m_arr[i].m_vals[j] = 0;
                        ItemPointer dest_tid = (ItemPointer)(pBatch->m_arr[i].m_vals + j);
                        ItemPointer src_tid = (ItemPointer)DatumGetPointer(slot->tts_values[i]);
                        *dest_tid = *src_tid;
                    } else {
                        pBatch->m_arr[i].AddVar(slot->tts_values[i], j);  // 处理其他复合类型数据
                    }
                    break;
                default:
                    ereport(ERROR, (errcode(ERRCODE_INDETERMINATE_DATATYPE), errmsg("不支持的数据类型分支")));  // 不支持的数据类型分支,抛出错误
            }

            SET_NOTNULL(pBatch->m_arr[i].m_flag[j]);  // 标记列中的数据为非 NULL
        } else {
            SET_NULL(pBatch->m_arr[i].m_flag[j]);  // 标记列中的数据为 NULL
        }
    }

    pBatch->m_rows++;  // 增加向量批次中的行数
    if (pBatch->m_rows == BatchMaxSize) {  // 如果向量批次已满
        may_more = true;  // 设置 may_more 标志为 true
    }

    /* 切换回旧上下文 */
    (void)MemoryContextSwitchTo(old_context);  // 切换回之前的内存上下文

    return may_more;  // 返回 may_more 标志,指示向量批次是否已满
}

ExecEndRowToVec 函数

  ExecEndRowToVec 函数的主要作用是用于清理和释放 RowToVec 算子执行时所分配的资源和状态,包括向量批次数据执行上下文结果元组槽以及关闭外部计划节点。首先,它将向量批次数据的指针设置为 NULL,以防止内存泄漏。然后,通过 ExecFreeExprContext 函数取消与计划节点的输出上下文的链接,但不实际释放内存,这是因为内存释放可能由上层节点负责。接下来,它清空结果元组槽的数据,确保不会有残留数据。最后,它关闭外部计划节点释放与之相关的资源。这个函数用于 RowToVec 算子的执行结束时,进行资源清理和释放,以确保系统资源的有效利用。
  ExecEndRowToVec 函数的源码如下:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

void ExecEndRowToVec(RowToVecState* node)
{
    node->m_pCurrentBatch = NULL;  // 清空向量批次数据,防止内存泄漏

    /*
     * 我们不实际释放任何 ExprContexts(参见 ExecFreeExprContext 中的注释),
     * 只需从计划节点中取消链接输出上下文即可。
     */
    ExecFreeExprContext(&node->ps);  // 取消与计划节点的输出上下文的链接,不实际释放内存

    /*
     * 清空元组表
     */
    (void)ExecClearTuple(node->ps.ps_ResultTupleSlot);  // 清空结果元组槽的数据

    /*
     * 关闭子计划节点
     */
    ExecEndNode(outerPlanState(node));  // 关闭外部计划节点
}

总结

  RowToVec 算子是用于将行存储数据转换为向量化数据的操作符,它通过 ExecInitRowToVec 函数进行初始化将行数据逐行处理并转换为向量批次,这个过程由 ExecRowToVecVectorizeOneTuple 函数完成,最后,ExecEndRowToVec 函数用于清理和释放资源,协同工作,使得在处理混合行存储和列存储数据时,能够高效地进行向量化计算,提高数据库查询性能。
  这里用 AI 尝试性的生成了一张描述 RowToVec 算子的图,感觉很有意思。
在这里插入图片描述
  这张图解释了数据库系统中 RowToVec 算子的功能,展示了数据从行存储格式转换到列存储格式的过程。下面是对每个部分的详细解释:

顶部部分 - 行存储表:
    这部分代表数据库中常见的行存储表。
    在行存储中,数据按行组织,这里以水平线的形式表示。
    每条水平线象征一个数据行,包含顺序存储的各种字段。

中间部分 - RowToVec算子转换:
    中间部分展示了RowToVec算子。
    它作为一个转换机制,将数据从行格式转换为列格式。
    这个过程对于在混合计算环境中操作的数据库非常关键,这些数据库使用行存储和列存储格式。
    算子将行数据逐行提取并转化为列向量,这一过程在图中以从水平线到垂直线的转换形式展示。

底部部分 - 列存储格式:
    图的底部展示了转换后的数据,现在以列存储格式呈现。
    在列存储中,数据按列组织,这里以垂直线的形式表示。
    每条垂直线代表一列数据,显示了数据在转换后的新结构。

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

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

相关文章

使用Python的PyQt实现财务综合计算

背景&#xff1a; 考核内容 使用 Python 编写程序代码&#xff0c;设计一个带交互界面的财务分析软件&#xff0c;并满足以下要求: PART1:《财务软件设计思路报告》 (30分) (1)编写《财务软件设计思路报告》&#xff0c;描述你编制这个财务软件的设计目标、应用场景、设计思路…

uniapp多行文本展开或收起(兼容h5、微信小程序,其它未测试)

文章目录 一、效果图展示1、收起2、展开3、文本过短时隐藏按钮【查看更多、收起】 二、代码实现原理&#xff1a;判断文本是否过短1、html2、css3、 js&#xff08;1&#xff09;data数据定义&#xff08;2&#xff09;获取文本高度&#xff08;3&#xff09; 获取行数&#xf…

【Vue】安装 vue-router 库报错 npm ERR! ERESOLVE unable to resolve dependency tree

问题描述 运行npm install vue-router&#xff0c;安装vue-router库&#xff0c;npm报错。 npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: my-project0.1.0 npm ERR! Found: vue2.7.15 npm ERR! node_mod…

linux的权限741

741权限 在 Linux 中&#xff0c;文件和目录的权限由三组权限来定义&#xff0c;分别是所有者&#xff08;Owner&#xff09;、所属组&#xff08;Group&#xff09;和其他用户&#xff08;Others&#xff09;。每一组权限又分为读&#xff08;Read&#xff09;、写&#xff0…

Ubuntu18.04 本地安装CVAT标注工具

写在前面&#xff1a; 1、如果直接clone最新版本的cvat&#xff0c;python版本最好安装3.8的&#xff0c;因为其中部分代码的语法只有高版本的python才可以支持。 2、安装完成以后本地登陆可能出现"cannot connect to cvat server"的错误&#xff0c;可以从Cannot …

FluxMQ—2.0.8版本更新内容

FluxMQ—2.0.8版本更新内容 前言 FLuxMQ是一款基于java开发&#xff0c;支持无限设备连接的云原生分布式物联网接入平台。FluxMQ基于Netty开发&#xff0c;底层采用Reactor3反应堆模型&#xff0c;具备低延迟&#xff0c;高吞吐量&#xff0c;千万、亿级别设备连接&#xff1…

C语言 - 字符函数和字符串函数

系列文章目录 文章目录 系列文章目录前言1. 字符分类函数islower 是能够判断参数部分的 c 是否是⼩写字⺟的。 通过返回值来说明是否是⼩写字⺟&#xff0c;如果是⼩写字⺟就返回⾮0的整数&#xff0c;如果不是⼩写字⺟&#xff0c;则返回0。 2. 字符转换函数3. strlen的使⽤和…

css单行/多行 超出部分多行文本溢出隐藏

单行文本溢出 <div class"box"><div class"text">aaaaaaaaaaaaaaaaaaaaaaaaaaaa</div> </div>.box {width: 100px;height: 50px;background-color: pink;.text {/* 文本溢出 */white-space: nowrap;/* 不换行 溢出隐藏 */overflow…

leetcode:1422. 分割字符串的最大得分(python3解法)

难度&#xff1a;简单 给你一个由若干 0 和 1 组成的字符串 s &#xff0c;请你计算并返回将该字符串分割成两个 非空 子字符串&#xff08;即 左 子字符串和 右 子字符串&#xff09;所能获得的最大得分。 「分割字符串的得分」为 左 子字符串中 0 的数量加上 右 子字符串中 1…

MacPro外接显示器使用hidpi

前几年因为工作的原因&#xff0c;公司给配了MacPro&#xff0c;用了几年发现确实比Windows好用。唯一不爽的就是13寸的屏幕感觉有点小&#xff0c;所以最近就买了一个准4K的32寸带鱼屏显示器。接上之后发现总感觉怪怪的&#xff0c;字体有点发虚的感觉。搜了半天&#xff0c;发…

【EI征稿中#先投稿,先送审#】第三届网络安全、人工智能与数字经济国际学术会议(CSAIDE 2024)

第三届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2024&#xff09; 2024 3rd International Conference on Cyber Security, Artificial Intelligence and Digital Economy 第二届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2023&…

黑豹程序员-java发邮件,发送内容支持html,带多附件的案例

介绍 发邮件mail是常见的软件功能&#xff0c;下面利于spring和java的mail库实现发送内容支持html&#xff0c;带多附件的案例 开启SMTP邮件发送协议 谁提供的SMTP邮件服务&#xff0c;就找谁开启。QQ邮箱类似。 依赖 <!--Java MAil 发送邮件API--><dependency&g…

GSLB是什么?谈谈对该技术的一点理解

GSLB是什么&#xff1f;它又称为全局负载均衡&#xff0c;是主流的负载均衡类型之一。众所周知&#xff0c;负载均衡位于服务器的前面&#xff0c;负责将客户端请求路由到所有能够满足这些请求的服务器&#xff0c;同时最大限度地提高速度和资源利用率&#xff0c;并确保无任何…

在KeyarchOS上体验WildFly

一、浪潮信息KeyarchOS简单介绍 KeyarchOS具备稳定可靠、高效软硬协同、全天候运维、安全可信等特性,增强了对云计算、人工智能等场景的支持,性能稳定性领先,生态完善,符合金融、政务、能源、交通、通信、教育、医疗等关键行业的应用要求。具备非常广泛的应用。 官方地址&…

Landsat 5 C02数据集2007-2011年

Landsat 5是美国陆地卫星系列&#xff08;Landsat&#xff09;的第五颗卫星&#xff0c;于1984年3月1日发射&#xff0c;2011年11月停止工作。16天可覆盖全球范围一次。Landsat5_C2_TOA数据集是由Collection2 level1数据通过MTL文件计算得到的TOA反射率产品。数据集的空间分辨率…

失落的艺术:无着色器3D渲染

假设你想创建一个甜蜜的弹跳立方体&#xff0c;如下所示&#xff1a; 一个弹跳的立方体 你可以使用 3D 框架&#xff0c;例如 OpenGL 或 Metal。 这涉及编写一个或多个顶点着色器来变换 3D 对象&#xff0c;以及编写一个或多个片段着色器来在屏幕上绘制这些变换后的对象。 然…

Spatial Data Analysis(三):点模式分析

Spatial Data Analysis&#xff08;三&#xff09;&#xff1a;点模式分析 ---- 1853年伦敦霍乱爆发 在此示例中&#xff0c;我将演示如何使用 John Snow 博士的经典霍乱地图在 Python 中执行 KDE 分析和距离函数。 感谢 Robin Wilson 将所有数据数字化并将其转换为友好的 G…

把 Windows 11 装进移动硬盘:Windows 11 To Go

本篇文章聊聊如何制作一个可以“说带走就带走”的 Windows 操作系统&#xff0c;将 Windows11 做成能够放在 U 盘或者移动硬盘里的 WinToGo “绿色软件”。 写在前面 在《开源的全能维护 U 盘工具&#xff1a;Ventoy》这篇文章的最后&#xff0c;我提到了一个关键词 “WinToG…

Hadoop高可用(主备切换)---配合Zookeeper

1. Hadoop高可用(Hadoop High Availability)概述 HA(High Available), 高可用&#xff0c;是保证业务连续性的有效解决方案&#xff0c;一般有两个或两个以上的节点&#xff0c;分为活动节点&#xff08;Active&#xff09;及备用节点&#xff08;Standby&#xff09;。通常把…

sizeof()、strlen()、length()、size()的区别(笔记)

​ 上面的笔记有点简陋&#xff0c;可以看一下下面这个博主的&#xff1a; c/c中sizeof()、strlen()、length()、size()详解和区别_csize,sizeof,length_xuechanba的博客-CSDN博客