Unity DOTS中的Archetype与Chunk

news2025/5/22 4:04:18

Unity DOTS中的Archetype与Chunk

在Unity中,archetype(原型)用来表示一个world里具有相同component类型组合的entity。也就是说,相同component类型的entity在Unity内部会存储到一起,共享同一个archetype。

Unity DOTS中的Archetype与Chunk1

使用这样的设计,可以高效地通过component类型进行查询。例如,如果想查找所有具有component类型 A 和 B 的entity,那么只需找到表示这两个component的所有archetype,这比扫描所有entity更高效。

entity和componenet并不直接存储在archetype中,而是存储在称为chunk的连续内存块中。每个块由16KB组成,它们实际可以存储的entity数量取决于chunk所属archetype中component的数量和大小。 这样做可以更好匹配缓存行,同时也能更好支持并行操作。

在这里插入图片描述

对于一个chunk,它除了包含用于每种component类型的数组之外,还包含一个额外的数组来存储entity的ID。例如,在具有component类型 A 和 B 的archetype中,每个chunk都有三个数组:一个用于component A的数组,一个用于component B的数组,以及一个用于entity ID的数组。

Unity DOTS中的Archetype与Chunk3

chunk中的数据是紧密连续的,chunk的每个entity都存储在每个数组连续的索引中。添加一个新的entity时,会将其存储在第一个可用索引中。而当从chunk中移除entity时,chunk的最后一个entity将被移动以填补空隙。添加entity时,如果对应archetype的所有chunk都已满,则Unity将创建一个全新的chunk。类似地,当从chunk中移除最后一个entity时,Unity会将该chunk进行销毁。

在编辑器中,Unity提供了Archetypes窗口,可以方便预览当前所有的archetype。我们先用代码创建4个entity,然后给它们分别添加两种component:

public partial struct FirstSystem : ISystem
{
    public void OnCreate(ref SystemState state) 
    { 
        Entity e1 = state.EntityManager.CreateEntity();
        Entity e2 = state.EntityManager.CreateEntity();
        Entity e3 = state.EntityManager.CreateEntity();
        Entity e4 = state.EntityManager.CreateEntity();
        state.EntityManager.AddComponentData(e1, new FirstComponent { Value = 1 });
        state.EntityManager.AddComponentData(e2, new FirstComponent { Value = 2 });

        state.EntityManager.AddComponentData(e3, new FirstComponent { Value = 3 });
        state.EntityManager.AddComponentData(e3, new SecondComponent { Value = 13 });

        state.EntityManager.AddComponentData(e4, new FirstComponent { Value = 4 });
        state.EntityManager.AddComponentData(e4, new SecondComponent { Value = 14 });
    }
}

public struct FirstComponent : IComponentData
{
    public int Value;
}

public struct SecondComponent : IComponentData
{
    public int Value;
}

那么,根据之前的分析,e1和e2应该属于同一个archetype,而e3和e4则属于另一个archetype。在编辑器中运行,观察Archetypes窗口,可以发现的确如此:

Unity DOTS中的Archetype与Chunk4

Unity DOTS中的Archetype与Chunk5

根据官网的解释,Archetypes窗口中可以观察到以下信息:

PropertyDescription
Archetype nameThe archetype name is its hash, which you can use to find the archetype again across future Unity sessions.
EntitiesNumber of entities within the selected archetype.
Unused EntitiesThe total number of entities that can fit into all available chunks for the selected Archetype, minus the number of active entities (represented by the entities stat).
ChunksNumber of chunks this archetype uses.
Chunk CapacityThe number of entities with this archetype that can fit into a chunk. This number is equal to the total number of Entities and Unused Entities.
ComponentsDisplays the total number of components in the archetype and the total amount of memory assigned to them in KB. To see the list of components and their individual memory allocation, expand this section.
External ComponentsLists the Chunk components and Shared components that affect this archetype.

自此,我们对Unity的archetype和chunk有了基本的认识。接下来我们来看看源代码,深入一下细节部分。

在上面的例子中,我们使用EntityManager.CreateEntity这个函数来创建entity。函数内部会在创建entity之前,检查是否存在相应的archetype。由于此时尚不存在表示一个不含任何component类型的空entity的archetype,那么函数会先去创建archetype:

internal EntityArchetype CreateArchetype(ComponentType* types, int count, bool addSimulateComponentIfMissing)
{
    Assert.IsTrue(types != null || count == 0);
    EntityComponentStore->AssertCanCreateArchetype(types, count);

    ComponentTypeInArchetype* typesInArchetype = stackalloc ComponentTypeInArchetype[count + 2];

    var cachedComponentCount = FillSortedArchetypeArray(typesInArchetype, types, count, addSimulateComponentIfMissing);

    return CreateArchetype_Sorted(typesInArchetype, cachedComponentCount);
}

每个空的entity都要额外添加一个Simulate类型的component,这也是上面的截图中为何会多出一个component的原因。Unity首先会尝试从已有的空闲archetypes中寻找符合条件的archetype,没找到才会去真正创建一个。

public Archetype* GetOrCreateArchetype(ComponentTypeInArchetype* inTypesSorted, int count)
{
    var srcArchetype = GetExistingArchetype(inTypesSorted, count);
    if (srcArchetype != null)
        return srcArchetype;

    srcArchetype = CreateArchetype(inTypesSorted, count);

    return srcArchetype;
}

Unity的Archetype是一个非常复杂的struct,它记录了所代表的component信息,以及chunk信息,还有它们当前的状态:

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct Archetype
{
    public ArchetypeChunkData Chunks;
    public UnsafeList<ChunkIndex> ChunksWithEmptySlots;

    public ChunkListMap FreeChunksBySharedComponents;
    public ComponentTypeInArchetype* Types; // Array with TypeCount elements
    public int* EnableableTypeIndexInArchetype; // Array with EnableableTypesCount elements

    // back pointer to EntityQueryData(s), used for chunk list caching
    public UnsafeList<IntPtr> MatchingQueryData;

    // single-linked list used for invalidating chunk list caches
    public Archetype* NextChangedArchetype;

    public int EntityCount;
    public int ChunkCapacity;

    public int TypesCount;
    public int EnableableTypesCount;
    public int InstanceSize;
    public int InstanceSizeWithOverhead;
    public int ScalarEntityPatchCount;
    public int BufferEntityPatchCount;
    public ulong StableHash;
    public ulong BloomFilterMask;

    // The order that per-component-type data is stored in memory within an archetype does not necessarily match
    // the order that types are stored in the Types/Offsets/SizeOfs/etc. arrays. The memory order of types is stable across
    // runs; the Types array is sorted by TypeIndex, and the TypeIndex <-> ComponentType mapping is *not* guaranteed to
    // be stable across runs.
    // These two arrays each have TypeCount elements, and are used to convert between these two orderings.
    // - MemoryOrderIndex is the order an archetype's component data is actually stored in memory. This is stable across runs.
    // - IndexInArchetype is the order that types appear in the archetype->Types[] array. This is *not* necessarily stable across runs.
    //   (also called IndexInTypeArray in some APIs)
    public int* TypeMemoryOrderIndexToIndexInArchetype; // The Nth element is the IndexInArchetype of the type with MemoryOrderIndex=N
    public int* TypeIndexInArchetypeToMemoryOrderIndex; // The Nth element is the MemoryOrderIndex of the type with IndexInArchetype=N

    // These arrays each have TypeCount elements, ordered by IndexInArchetype (the same order as the Types array)
    public int*    Offsets; // Byte offset of each component type's data within this archetype's chunk buffer.
    public ushort* SizeOfs; // Size in bytes of each component type
    public int*    BufferCapacities; // For IBufferElementData components, the buffer capacity of each component. Not meaningful for non-buffer components.

    // Order of components in the types array is always:
    // Entity, native component data, buffer components, managed component data, tag component, shared components, chunk components
    public short FirstBufferComponent;
    public short FirstManagedComponent;
    public short FirstTagComponent;
    public short FirstSharedComponent;
    public short FirstChunkComponent;

    public ArchetypeFlags Flags;

    public Archetype* CopyArchetype; // Removes cleanup components
    public Archetype* InstantiateArchetype; // Removes cleanup components & prefabs
    public Archetype* CleanupResidueArchetype;
    public Archetype* MetaChunkArchetype;

    public EntityRemapUtility.EntityPatchInfo* ScalarEntityPatches;
    public EntityRemapUtility.BufferEntityPatchInfo* BufferEntityPatches;

    // @macton Temporarily store back reference to EntityComponentStore.
    // - In order to remove this we need to sever the connection to ManagedChangesTracker
    //   when structural changes occur.
    public EntityComponentStore* EntityComponentStore;

    public fixed byte QueryMaskArray[128];
}

其中,TypesCount表示archetype所保存的数据种类,一般就是entity+component类型数量,这里空entity自带一个Simulate的component,那么TypesCount即为2;Types就是对应的类型数组了;EntityCount为当前archetype所拥有的entity数量;ChunksWithEmptySlots表示archetype中所有空闲chunk的索引列表,archetype就是通过它来为entity指定chunk的;然后,一系列First打头的Component(FirstBufferComponentFirstManagedComponentFirstTagComponentFirstSharedComponentFirstChunkComponent)表示在type数组中第一个出现该类型的索引;SizeOfs是一个比较重要的字段,它表示每个component type所占的内存大小,通过它可以进而计算出archetype最多可拥有entity的数量,也就是ChunkCapacity字段;在此基础上,还可以计算出每个component type在chunk中的起始位置Offsets;最后,Chunks是一个ArchetypeChunkData类型的字段,它用来记录archetype中所有chunk的状态。

有了archetype之后,就可以进入创建entity的阶段了,那么下一步,Unity会先查找是否有可用的chunk,没有的话就得真正分配内存,去创建一个:

ChunkIndex GetChunkWithEmptySlots(ref ArchetypeChunkFilter archetypeChunkFilter)
{
    var archetype = archetypeChunkFilter.Archetype;
    fixed(int* sharedComponentValues = archetypeChunkFilter.SharedComponentValues)
    {
        var chunk = archetype->GetExistingChunkWithEmptySlots(sharedComponentValues);
        if (chunk == ChunkIndex.Null)
            chunk = GetCleanChunk(archetype, sharedComponentValues);
        return chunk;
    }
}

注意到这里函数返回的是ChunkIndex类型,它不是真正意义上的chunk,而是对chunk的二次封装,用来方便快捷地存取chunk的数据,以及记录chunk的各种状态与属性。ChunkIndex只有一个int类型的value变量,表示当前chunk在所有chunk中的位置。虽然Unity声称每个chunk只有16KB大小,但是实际分配内存时,并不是按16KB进行分配,而是会一口气先分配好1 << 20字节也就是64 * 16KB大小的内存,这块内存被称为mega chunk。所有mega chunk的内存指针都保存在大小为16384个ulong类型的连续内存中,因此ChunkIndex记录的是全局索引,第一步先索引到某个mega chunk,然后再定位到真正的chunk地址。

在这里插入图片描述

internal Chunk* GetChunkPointer(int chunkIndex)
{
    var megachunkIndex = chunkIndex >> log2ChunksPerMegachunk;
    var chunkInMegachunk = chunkIndex & (chunksPerMegachunk-1);
    var megachunk = (byte*)m_megachunk.ElementAt(megachunkIndex);
    var chunk = megachunk + (chunkInMegachunk << Log2ChunkSizeInBytesRoundedUpToPow2);
    return (Chunk*)chunk;
}

Chunk是一个相对简单的数据结构,前64字节为chunk头部,后面才是数据内容。

[StructLayout(LayoutKind.Explicit)]
internal unsafe struct Chunk
{
    // Chunk header START

    // The following field is only used during serialization and won't contain valid data at runtime.
    // This is part of a larger refactor, and this field will eventually be removed from this struct.
    [FieldOffset(0)]
    public int ArchetypeIndexForSerialization;
    // 4-byte padding to keep the file format compatible until further changes to the header.

    [FieldOffset(8)]
    public Entity metaChunkEntity;

    // The following field is only used during serialization and won't contain valid data at runtime.
    // This is part of a larger refactor, and this field will eventually be removed from this struct.
    [FieldOffset(16)]
    public int CountForSerialization;

    [FieldOffset(28)]
    public int ListWithEmptySlotsIndex;

    // Special chunk behaviors
    [FieldOffset(32)]
    public uint Flags;

    // SequenceNumber is a unique number for each chunk, across all worlds. (Chunk* is not guranteed unique, in particular because chunk allocations are pooled)
    [FieldOffset(kSerializedHeaderSize)]
    public ulong SequenceNumber;

    // NOTE: SequenceNumber is not part of the serialized header.
    //       It is cleared on write to disk, it is a global in memory sequence ID used for comparing chunks.
    public const int kSerializedHeaderSize = 40;

    // Chunk header END

    // Component data buffer
    // This is where the actual chunk data starts.
    // It's declared like this so we can skip the header part of the chunk and just get to the data.
    public const int kBufferOffset = 64; // (must be cache line aligned)
    [FieldOffset(kBufferOffset)]
    public fixed byte Buffer[4];

    public const int kChunkSize = 16 * 1024;
    public const int kBufferSize = kChunkSize - kBufferOffset;
    public const int kMaximumEntitiesPerChunk = kBufferSize / 8;

    public const int kChunkBufferSize = kChunkSize - kBufferOffset;
}

随后,Unity会在该chunk中为要创建的entity分配索引,索引为当前chunk的entity数量,因为之前提到chunk的数据都是连续排布的,中间并不存在空隙。

static int AllocateIntoChunk(Archetype* archetype, ChunkIndex chunk, int count, out int outIndex)
{
    outIndex = chunk.Count;
    var allocatedCount = Math.Min(archetype->ChunkCapacity - outIndex, count);
    SetChunkCount(archetype, chunk, outIndex + allocatedCount);
    archetype->EntityCount += allocatedCount;
    return allocatedCount;
}

根据索引,就可以将chunk中属于该entity的部分(entity id,component type)进行初始化,clear内存等操作,自此,一个entity就算创建完成了。

for (var t = 1; t != typesCount; t++)
{
    var offset = offsets[t];
    var sizeOf = sizeOfs[t];
    var dst = dstBuffer + (offset + sizeOf * dstIndex);

    if (types[t].IsBuffer)
    {
        for (var i = 0; i < count; ++i)
        {
            BufferHeader.Initialize((BufferHeader*)dst, bufferCapacities[t]);
            dst += sizeOf;
        }
    }
    else
    {
        UnsafeUtility.MemClear(dst, sizeOf * count);
    }
}

接下来,再为entity添加component,那么原来entity所在的chunk和archetype不再满足要求,需要将entity移动到新的chunk和archetype中,这种行为在Unity中被称作structural change。在AddComponent之前,Unity首先会检查entity是否已有该component,同一类型的component只允许存在一个。这个时候archetype就发挥作用了,只需要去archetype中查找component type是否存在即可:

public bool HasComponent(Entity entity, ComponentType type)
{
    if (Hint.Unlikely(!Exists(entity)))
        return false;

    var archetype = GetArchetype(entity);
    return ChunkDataUtility.GetIndexInTypeArray(archetype, type.TypeIndex) != -1;
}

如果entity没有该component,那么就需要找一下符合条件的archetype以及空闲的chunk。

ChunkIndex GetChunkWithEmptySlotsWithAddedComponent(ChunkIndex srcChunk, ComponentType componentType, int sharedComponentIndex = 0)
{
    var archetypeChunkFilter = GetArchetypeChunkFilterWithAddedComponent(srcChunk, componentType, sharedComponentIndex);
    if (archetypeChunkFilter.Archetype == null)
        return ChunkIndex.Null;

    return GetChunkWithEmptySlots(ref archetypeChunkFilter);
}

新的archetype和chunk都有了,剩下的就是move操作了,先把数据从源chunk拷贝到目标chunk中去,更新目标archetype,然后再清理源chunk,更新源archetype。

int Move(in EntityBatchInChunk srcBatch, ChunkIndex dstChunk)
{
    var srcChunk = srcBatch.Chunk;
    var srcArchetype = GetArchetype(srcChunk);
    var dstArchetype = GetArchetype(dstChunk);
    var dstUnusedCount = dstArchetype->ChunkCapacity - dstChunk.Count;
    var srcCount = math.min(dstUnusedCount, srcBatch.Count);
    var srcStartIndex = srcBatch.StartIndex + srcBatch.Count - srcCount;

    var partialSrcBatch = new EntityBatchInChunk
    {
        Chunk = srcChunk,
        StartIndex = srcStartIndex,
        Count = srcCount
    };

    ChunkDataUtility.Clone(srcArchetype, partialSrcBatch, dstArchetype, dstChunk);

    fixed (EntityComponentStore* store = &this)
    {
        ChunkDataUtility.Remove(store, partialSrcBatch);
    }

    return srcCount;
}

前面提到过,当从chunk中移除entity时,chunk的最后一个entity将被移动以填补空隙。其实就是个简单的拷贝内存操作:

var fillStartIndex = chunk.Count - fillCount;

Copy(entityComponentStore, chunk, fillStartIndex, chunk, startIndex, fillCount);

Reference

[1] Archetypes concepts

[2] 深入理解ECS:Archetype(原型)

[3] Archetypes window reference

[4] Structural changes concepts

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JAVA毕业设计191—基于Java+Springboot+vue的电子产品商城管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue的电子产品商城管理系统(源代码数据库)191 一、系统介绍 本项目前后端不分离&#xff0c;分为用户、管理员两种角色 1、用户&#xff1a; 注册、登录、商品…

C++在vscode中的code runner配置/环境配置

C在vscode中快捷运行&#xff08;code runner&#xff09; 一、配置tasks.json 在vscode中创建文件夹或打开文件夹&#xff0c;会发现文件夹下多了一个.vscode文件夹&#xff0c;在该文件夹下创建tasks.json文件&#xff0c;并添加一下内容 {"version": "2.0…

弘景光电:以创新为翼,翱翔光学科技新蓝海

在科技日新月异的今天&#xff0c;光学镜头及模组作为智能设备的核心组件&#xff0c;其重要性日益凸显。广东弘景光电科技股份有限公司&#xff08;以下简称“弘景光电”&#xff09;正是在这一领域中&#xff0c;凭借其卓越的研发实力和市场洞察力&#xff0c;即将在创业板上…

工具篇:(二)MacOS 下载 MySQL 并进行配置连接,使用 VSCode 创建 Node 项目-亲测有效

MacOS 下载 MySQL 并进行配置连接&#xff0c;使用 VSCode 创建 Node 项目 我们将介绍如何在 macOS 上下载和配置 MySQL 数据库&#xff0c;并使用 VSCode 创建一个 Node.js 项目进行测试。通过这些步骤&#xff0c;您将能够顺利地设置开发环境并进行基本的数据操作。 一、删…

【国际学术会议之都,IEEE出版】第四届计算机科学、电子信息工程和智能控制技术国际会议(CEI 2024,2024年11月8-10日)

第四届计算机科学、电子信息工程和智能控制技术国际会议&#xff08;CEI 2024&#xff09; 2024 4th International Conference on Computer Science, Electronic Information Engineering and Intelligent Control Technology 官方信息 会议官网&#xff1a;www.ic-cei.org …

AFSim仿真系统 --- 系统简解_10处理器 (Processors)

处理器 (Processors) 处理器提供了为特定平台定义行为的能力。 大多数处理器由用户使用 AFSIM 脚本语言定义。 以下是一些预定义的处理器类型&#xff1a; WSF_DIRECTION_FINDER_PROCESSORWSF_TRACK_PROCESSORWSF_MESSAGE_PROCESSORWSF_GUIDANCE_COMPUTERWSF_IMAGE_PROCESS…

Android11 USB Camera会出现预览绿屏问题

目录 一、问题描述 二、问题原因 三、解决方法 一、问题描述 DDR容量是4G及以上的机器&#xff0c;USB Camera会出现预览绿屏问题。 串口中会刷如下log: 二、问题原因 RGA2使用超过4G内存会异常&#xff0c;导致USB Camera调用rga相关操作报错&#xff0c;从而预览绿屏 三…

深度学习基础—神经风格迁移

1.什么是神经风格迁移 神经风格迁移就是将一张图片的风格迁移到另一张图片上&#xff0c;生成具有第一张图片风格的新的图片。新图片的主体还是第二张图片&#xff0c;但是风格是第一张图片的。 如下两组图片都是神经风格迁移的例子&#xff1a; 将绘画的风格迁移到真实建筑图片…

树型名称前面插入图片

需求&#xff1a; 搜索树、树型要显示连线&#xff0c;还有名称前带图片 ui组件&#xff1a;https://devui.design/components/zh-cn/overview 直接上代码 [checkable] false 表示取消复选框 <div class"p-sm"><div class"row"><d-sea…

软件开发----Java基础每日刷题(转载于牛客)

1. 对抽象类的描述正确的是() A 抽象类的方法都是抽象方法 B 一个类可以继承多个抽象类 C 抽象类不能有构造方法 D 抽象类不能被实例化 正确答案&#xff1a;D 解析&#xff1a; A.抽象类可以有非抽象的方法&#xff0c;而接口中的方…

Flythings学习(三)界面交互

文章目录 1 界面切换1.1 打开界面1.2 关闭界面 2 界面活动周期2.1 打开不存在页面的活动流程2.2 打开已存在界面&#xff08;被隐藏的界面&#xff09;2.3 关闭界面的流程 1 界面切换 界面切换的相关函数如下 1.1 打开界面 如果需要打开一个界面&#xff0c;在其他界面的控件…

WebSocket状态码及异常报错1006

文章目录 1.WebSocket协议简介2.WebSocket状态码的作用&#xff1a;3.WebSocket状态码1006详解1.问题原因2.解决方案 1.WebSocket协议简介 WebSocket协议是一种基于TCP的协议&#xff0c;它通过在浏览器和服务器之间建立一条持久的双向通信通道&#xff0c;实现了实时的数据传…

【论文阅读】SAM 2: 分割一切图像和视频

导言 继SAM模型发布以来&#xff0c;Meta公司于2024年8月发布第二个图像分割大模型SAM2。相较于第一代SAM模型&#xff0c;论文提出了第二代“分割任意物体模型” (SAM 2)&#xff0c;旨在解决视频中的可提示视觉分割任务&#xff08;Promptable Visual Segmentation, PVS&…

TVS常规过压保护

一、前言 上一篇文章 TVS选型-CSDN博客https://blog.csdn.net/qq_39543984/article/details/142825929?spm=1001.2014.3001.5501我们介绍了如何通过理论计算选择合适的TVS,TVS主要是防止瞬间过压,因为他的名字就叫瞬态二极管(Transient Voltage Suppressor)。本文就通过理…

自动化检查网页的TDK,python+selenium自动化测试web的网页源代码中的title,Description,Keywords

首先&#xff0c;TDK是什么&#xff1f;对于新手小白来说&#xff0c;可能是懵逼的&#xff0c;所以这里给出一个官方的解说‌网页的TDK是指标题&#xff08;Title&#xff09;、描述&#xff08;Description&#xff09;和关键词&#xff08;Keywords&#xff09;的集合‌。这…

智慧船舶物联网实训室建设方案

第一章 建设背景 随着全球海洋经济的蓬勃发展与智能化技术的日新月异&#xff0c;数字船舶物联网&#xff08;Internet of Things for Maritime, IoT-Maritime&#xff09;与人工智能&#xff08;Artificial Intelligence, AI&#xff09;的结合已成为推动航运业转型升级的关键…

企业资源枯竭时,数字化转型能否带来新资源?

​在商业竞争激烈的当下&#xff0c;企业发展依赖各类资源。然而&#xff0c;资源可能面临枯竭&#xff0c;如原材料短缺、市场份额下降、人才流失等。此时&#xff0c;数字化转型成为企业突破困境的重要途径&#xff0c;那么它能否带来新资源呢&#xff1f; 先看企业资源分类。…

C++,STL 031(24.10.14)

内容 stack容器&#xff08;栈&#xff09;的常用接口。 代码 #include <iostream> #include <stack> // 注意包含stack容器&#xff08;栈&#xff09;的头文件using namespace std;void test01() {stack<int> s1; // here01&#xff0c;默认构造stack<…

5g工业路由器最新案例:高原气象站网络升级项目

背景&#xff1a; 某省气象局决定在高原地区升级其气象观测网络&#xff0c;以提高天气预报的准确性和及时性&#xff0c;同时为气候变化研究提供更可靠的数据支持。该项目面临以下挑战&#xff1a; 需要在高原广袤且地形复杂的区域部署大量自动气象站&#xff0c;要求网络覆…

pytorh学习笔记——手写数字识别mnist

pytorh学习第二站&#xff1a;手写数字识别 一、训练程序 1、创建脚本框架mnist_demo.py&#xff1a; import torch import torchvision.datasets as dataset# data # 定义数据# net # 定义网络# loss # 损失# optimizer # 优化# training # 训练# test # 测试# save# 保…