CUTLASS 1.3.3中的 Volta884_h884gemm

CUTLASS 是 CUDA C++ 模板抽象的集合,用于在 CUDA 内的所有级别和规模上实现高性能矩阵-矩阵乘法 (GEMM) 和相关计算。它采用了类似于 cuBLAS 和 cuDNN 中实现的分层分解和数据移动策略。

CUTLASS 最新版本为3.3,相比1.3.3变动较大。然而重温一下1.3.3仍然是有意义的。因为它更易于理解:

  • 与 PROGRAMMING TENSOR CORES: NATIVE VOLTA TENSOR CORES WITH CUTLASS 中介绍的内容相匹配;
  • 仅支持 Volta 一种 Tensor Core 架构;
  • Tensor Core 仅支持 half 一种数据类型;
  • 仅采用HMMA.884.F16.F16一种指令。

Demystifying Tensor Cores to Optimize Half-Precision Matrix Multiply 中提到 T4 GPU 在引入 Tensor Core 之后,原来重计算瓶颈的 GEMM 也变成了 IO 瓶颈。虽然 V100的带宽是 T4的三倍,然而带宽不足问题同样存在。因此,CUTLASS 对于数据路径进行了如下优化:

  • 全路径128 bit 的访问粒度:LDG.128STS.128LDS.128STD.128
  • 无冲突共享内存排列:转置时无需填充 Shared Memory;
  • Software Pipelining:LDG.128LDS.128HMMA.884.F16.F16三种指令并行,隐藏数据移动。

下面以一个矩阵乘测例为例,介绍 Volta884_h884gemm 的实现。

TEST(Volta884_h884gemm_128x64x32_nt, 520x264x136)

OutputTile即 threadblock tile,该测例下设置为32x64x128。WarpGemmShape为32x64x64,这个是固定值。
run_gemm 初始化 Volta884GemmTraits::Params 和 GemmTestbed,调用 Gemm::launch 运行后比对结果。

TEST(Volta884_h884gemm_64x64x32_nt, 520x264x136) {typedef cutlass::gemm::Volta884GemmTraits<cutlass::MatrixLayout::kColumnMajor,cutlass::MatrixLayout::kRowMajor,cutlass::Shape<32, 64, 128>,cutlass::Shape<32, 64, 64>,half,half,half,2> GemmTraits;run_gemm<GemmTraits>(520, 264, 136);
}

CUTLASS 中 Volta884实现的层次结构如下图所示

Gemm
Volta884GemmTraits
Volta884MultiplyAdd
GemmMainloop
Volta884Multiplicand
IdentityBlockSwizzle
GlobalLoadStreamPair
SharedStreamPair
MMAEpilogue
Volta884EpilogueTraits
PredicatedTileLoadStream
PredicatedTileStoreStream
TileStoreStream
TileLoadStream
mma
TileLoadIterator
Volta884ThreadblockMultiplicandStoreIterator
Volta884WarpMultiplicandLoadIterator
MMAGlobalLoadStream
Copy
MMASharedLoadStream

gemm_kernel_nolb

gemm_kernel_nolb
GemmMainloop::multiply_add

Kernel 函数申请动态 Shared Memory,并传递给 GemmMainloop,然后调用 GemmMainloop::multiply_add 进行计算。

/// GEMM kernel without launch bounds specified
template <typename Gemm_>
__global__ /* __launch_bounds__(Gemm_::kThreads) */
void gemm_kernel_nolb(typename Gemm_::Params params) {// Dynamic shared memory base pointerextern __shared__ int GemmSharedStorageBase[];// Declare pointer to dynamic shared memory.typename Gemm_::SharedStorage *shared_storage = reinterpret_cast<typename Gemm_::SharedStorage *>(GemmSharedStorageBase);// Construct the GEMM object.Gemm_ gemm(params, *shared_storage);// Run GEMM.gemm.multiply_add();
}

GemmMainloop

GemmMainloop 实现了软流水,如下图所示:
在这里插入图片描述

Shared Memory 和寄存器需要两个缓冲区,通过 SM 上的调度实现三条流水线并行。Global Memory 到 Shared Memory 的加载有同步,而从 Shared Memory 移动到寄存器时不需要同步。由于 Ampere 之前的架构不支持 Global Memory 到 Shared Memory 的直接拷贝,因此整个搬运过程比较复杂。如下图所示,程序中多处调用 Copy::transform 函数生成transformed_fragment。原因应该是为了实现类型转换,但 Volta 只支持 half,也就没有实际作用。

PredicatedTileStoreStreamTileStoreStream::copy
GlobalLoadStream::commit
GlobalLoadStream::copy
Copy::transform
Volta884ThreadblockMultiplicandStoreIterator::store_post_increment
MMASharedLoadStream::copy
MMASharedLoadStream::commit
A/B
TileStoreStream::copy
TileStoreStream::copy
TileStoreStream::commit
AB
D
PredicatedTileLoadStream::copy
TileLoadStream::commit
C
Copy::transform
TileStoreIterator::store_post_increment
source_fragment
transformed_fragment
fetched_fragment
transformed_fragment
Global_Memory
Shared_Memory
fetched
transformed
Volta884MultiplyAdd
accumulators
fetched_fragment
transformed_fragment
LinearScaling
fetched_fragment
transformed_fragment
template <typename Traits_>
struct GemmMainloop {//// Type definitions///// The traits.typedef Traits_ Traits;/// The GEMM mainlooptypedef typename Traits::KernelClass KernelClass;/// The shared storage.typedef typename Traits::SharedStorage SharedStorage;/// The scalar for A.typedef typename Traits::ScalarA ScalarA;/// The scalar for B.typedef typename Traits::ScalarB ScalarB;/// The scalar in the epilogue.typedef typename Traits::Epilogue::Scalar ScalarEpilogue;/// The scalar for C.typedef typename Traits::Epilogue::ScalarC ScalarC;/// The scalar for D.typedef typename Traits::Epilogue::ScalarD ScalarD;/// The index.typedef typename Traits::Index Index;/// Define the mainloop iteration sizetypedef typename Traits::MultiplyAdd MultiplyAdd;/// The number of threads.static int const kThreads = Traits::GemmConfig::kThreads;

AccumulatorsPerWarp为 GemmConfig::AccumulatorsPerWarp 即 Volta884MultiplyAdd::WarpGemmShape,为32x64x64。
Volta884MultiplyAdd::InstructionShape 为4x32x32。因此,kWarpGemmSteps为8。

  // Number of warp-level multiply-accumulate steps executed by each warp.static Index const kWarpGemmSteps =Traits::GemmConfig::AccumulatorsPerWarp::kD / MultiplyAdd::InstructionShape::kD;/*// Make sure we have at least 2 unrolling steps or our pipeling is not going to work.static_assert(kWarpGemmSteps >= 2, "The pipelining assumes at least two steps");*//// Use the params object defined in traitstypedef typename Traits::Params Params;//// Data members///// The params.Params const& params;/// SharedStorage objectSharedStorage& shared_storage;
  //// Methods///// Ctor.CUTLASS_DEVICE GemmMainloop(Params const& params_, SharedStorage& shared_storage_): params(params_), shared_storage(shared_storage_) {}

GemmMainloop::fetch_global

GemmMainloop::fetch_global
GlobalLoadStreamPair::residue
GlobalLoadStreamPair::copy

Volta884GemmTraits::GlobalLoadStream 即 GlobalLoadStreamPair 类型。
GlobalLoadStreamPair::residue 函数调用两次 MMAGlobalLoadStream::residue,计算在线程块 tile 最后一次加载所需的预测掩码。
GlobalLoadStreamPair::copy 函数调用两次 MMAGlobalLoadStream::copy 从 Global Memory 拷贝矩阵元素到寄存器。后者调用 TileLoadIterator::load_post_increment 函数。

  /// Fetches global stream pairtemplate <bool Residue>CUTLASS_DEVICE void fetch_global(typename Traits::GlobalLoadStream& global_to_shared_stream,Index outer_k) {// If residue portion and not calculating residue in prolog, update residue predicates now.if (Residue) {global_to_shared_stream.residue(outer_k);}global_to_shared_stream.copy();}

GemmMainloop::consume_tile

如果kWarpGemmSteps小于等于4,则为kGlobalStreamFirst,先从 Global Memory 加载下一次迭代的数据。

  /// Computes a warp-level GEMM on data held in shared memorytemplate <bool Residue, bool LastIteration>CUTLASS_DEVICE void consume_tile(typename Traits::GlobalLoadStream& global_to_shared_stream,typename Traits::SharedStream& shared_load_stream,typename MultiplyAdd::Accumulators& accumulators,Index outer_k) {// Whether to load global stream before loading shared streamconst bool kGlobalStreamFirst = (kWarpGemmSteps <= 4);// Load data for the next iteration of the main loop (unless it's the last iteration).if (kGlobalStreamFirst && !LastIteration) {fetch_global<Residue>(global_to_shared_stream, outer_k);}

首先从 Shared Memory 加载下一次迭代的输入。拥有双缓冲区。
MMASharedLoadStream::copy 调用 Volta884WarpMultiplicandLoadIterator::load 函数加载数据到寄存器中。
问题是前一步如果没有调用 GemmMainloop::fetch_global,从 Shared Memory 拷贝不会有问题吗?

    CUTLASS_PRAGMA_UNROLLfor (int step = 0; step < kWarpGemmSteps; ++step) {// Trigger the copy from shared memory for the next A/B values.shared_load_stream.copy((step + 1) % kWarpGemmSteps);

如果不是kGlobalStreamFirst , 在循环的第一步时调用GemmMainloop::fetch_global 函数加载输入。

      // Load data for the next iteration of the main loop (unless it's the last iteration).if (!kGlobalStreamFirst && (step == 0) && !LastIteration) {fetch_global<Residue>(global_to_shared_stream, outer_k);}

如果是倒数第2步,需要确保数据已经加载到了 Shared Memory。
Volta884GemmTraits::shared_load_fence 根据外部传入的StageCount来确定是否同步线程。
GlobalLoadStreamPair::commit 函数会分别调用两个矩阵的 GlobalLoadStream::commit 拷贝到 Shared Memory。
Volta884GemmTraits::shared_store_fence 同步线程。
MMASharedLoadStream::inc_stage 递增stage_index

      if (step == kWarpGemmSteps - 2) {// Make sure the data from shared memory has been entirely consumed.Traits::shared_load_fence(true);global_to_shared_stream.commit();// Make sure the data is in shared memory.Traits::shared_store_fence(true);// Move to the next stage for the load (if it makes sense).shared_load_stream.inc_stage();}

MMASharedLoadStream::commit 调用 Copy 进行拷贝。Volta884WarpMultiplicandLoadIterator::Fragment 即 Fragment 。
Volta884MultiplyAdd::multiply_add 完成 Warp Tile 的计算。

      // Make sure the values are available for the current iteration to do the multiply-add.shared_load_stream.commit(step);// Do the math on the fragments of the current iteration.MultiplyAdd multiply_add;multiply_add.multiply_add(shared_load_stream.fragment_a(step),shared_load_stream.fragment_b(step),accumulators,accumulators);}}

GemmMainloop::multiply_add

Created with Raphaël 2.3.0 GemmMainloop::multiply_add IdentityBlockSwizzle::get_threadblock_offset IdentityBlockSwizzle::get_threadblock_bounds IdentityBlockSwizzle::get_batch_id GlobalLoadStreamPair::add_batch_offset GlobalLoadStreamPair::move_to_residue GlobalLoadStreamPair::copy GlobalLoadStreamPair::commit Volta884GemmTraits::shared_store_fence GlobalLoadStreamPair::rollback SharedLoadStream::copy ClearAccumulators::clear GemmMainloop::consume_tile GemmEpilogue::epilogue End

make_Coord_from_shape 根据形状创建一个 Coord 对象。

IdentityBlockSwizzle::get_threadblock_offset 获得当前线程块在输出二维图上的偏移。
Volta884GemmTraits::ClearAccumulators 即 ClearAccumulators。
IdentityBlockSwizzle::get_threadblock_bounds 返回 threadblock 的三维边界。

  /// Do the GEMM.CUTLASS_DEVICE void multiply_add() {// Swizzle the IDs of the block (to enable better cache behavior).typename Traits::BlockSwizzle block_swizzle;Coord<3> threadblock_offset =block_swizzle.get_threadblock_offset(make_Coord_from_shape<typename Traits::OutputTile>());// We may want to use shared memory to clear the registers.typedef typename Traits::ClearAccumulators ClearAccumulators;// Get the bounds for each thread, it maybe different than problem_sizeCoord<3> bounds = block_swizzle.get_threadblock_bounds(params.problem_size,params.partitionK_range);

params.global_to_shared_stream即 GlobalLoadStreamPair::Params。
shared_storage.main_loop.global_to_shared_stream为 GlobalLoadStreamPair::SharedStorage。
shared_storage.main_loop.threadblock_tile为 GlobalLoadStreamPair::ThreadblockTileStorage,即 ZipTileAllocation。ZipTileAllocation::reference 返回指向数据的 ZipTensorRef 对象。
global_to_shared_stream为 Volta884GemmTraits::GlobalLoadStream 即 GlobalLoadStreamPair。
GlobalLoadStreamPair::add_batch_offset 调用 GlobalLoadStreamPair::add_batch_offset GlobalLoadStream::add_batch_offset 函数设置迭代器的 batch 偏移。

    // The streams to read A/B from global memory to shared memory.typename Traits::GlobalLoadStream global_to_shared_stream(params.global_to_shared_stream,shared_storage.main_loop.global_to_shared_stream,shared_storage.main_loop.threadblock_tile.reference(),bounds,threadblock_offset);// update A and B pointer offset based on batch_id and batch_stride_offsetglobal_to_shared_stream.add_batch_offset(block_swizzle.get_batch_id());// Create the accumulator clear.ClearAccumulators clear;

GlobalLoadStreamPair::move_to_residue 如果是在序幕中执行余数则调用 MMAGlobalLoadStream::move_to_residue 移动指针,否则直接调用 GlobalLoadStreamPair::residue 函数。
GlobalLoadStreamPair::copy 调用 MMAGlobalLoadStream::copy 函数,后者调用 TileLoadIterator::load_post_increment 加载 A 和 B 矩阵的片段到 Fragment 寄存器。
GlobalLoadStreamPair::commit 调用 MMAGlobalLoadStream::commit 函数,后者调用 Copy.transform 进行拷贝,然后调用
Volta884ThreadblockMultiplicandStoreIterator::store_post_increment 保存到 Shared Memory。
Volta884GemmTraits::shared_store_fence 同步 threadblock 内的线程。
GlobalLoadStreamPair::rollback 调用 MMAGlobalLoadStream::rollback 函数,后者调用 TileLoadIterator::initialize_predicates 初始化预测向量,然后移动偏移。

    // Deal with residue in prolog.// global_to_shared_stream.move_to_residue(params.problem_size[0], Traits::OutputTile::kD);global_to_shared_stream.move_to_residue(bounds[0], Traits::OutputTile::kD);// Fetch the fragments for A and B from global memory.global_to_shared_stream.copy();// Copy the elements to shared memory (after transformation if needed).global_to_shared_stream.commit();// Make sure the data is in shared memory.Traits::shared_store_fence(false);// Rollback to the beginning of the first tile (if residue exists).// global_to_shared_stream.rollback(params.problem_size[0] % Traits::OutputTile::kD);global_to_shared_stream.rollback(bounds[0] % Traits::OutputTile::kD);

shared_load_stream为 Volta884GemmTraits::SharedStream 类型,即 SharedStreamPair。
SharedStreamPair::copy 调用 MMASharedLoadStream::copy,后者调用 Volta884WarpMultiplicandLoadIterator::load 从 Shared Memory 加载。
accumulators为 Volta884MultiplyAdd::Accumulators 类型,即 Fragment。
ClearAccumulators::clear 调用 Fragment::clear 将存储清零。
outer_k是什么?

    // The stream of data from shared memory to fragments.typename Traits::SharedStream shared_load_stream(params.shared_stream,shared_storage.main_loop.threadblock_tile.reference());// Trigger the copy from shared memory for the 1st stream.shared_load_stream.copy(0);// Allocate the accumulators.typename MultiplyAdd::Accumulators accumulators;// Clear the accumulators.clear.clear(accumulators);// Initial index// Index outer_k = params.problem_size[0] - Traits::OutputTile::kD;// problem_size[0] might be bigger than bounds[0]Index outer_k = bounds[0] - Traits::OutputTile::kD;

如果在序幕中计算了剩余,则仅最后一次处理余数。
GemmMainloop::consume_tile 计算k = Traits::OutputTile::kD的分块。

    // Check if we are computing residue in prolog or not.if (Traits::GemmConfig::kResidueInProlog) {// Execute all mainloop iterations but the last one.CUTLASS_GEMM_LOOPfor (; outer_k > 0; outer_k -= Traits::OutputTile::kD) {CUTLASS_GEMM_LOOP_HEADERconsume_tile<false, false>(global_to_shared_stream, shared_load_stream, accumulators, outer_k);}consume_tile<false, true>(global_to_shared_stream, shared_load_stream, accumulators, outer_k);

否则,每次迭代都考虑余数。

    } else {// When kResidueSeparate = true, execute all mainloop iterations but the last two without any// consideration for K-residue or predicate updates. This improves the steady state of some// kernels.if (Traits::GemmConfig::kResidueSeparate) {CUTLASS_GEMM_LOOPfor (; outer_k > Traits::OutputTile::kD; outer_k -= Traits::OutputTile::kD) {CUTLASS_GEMM_LOOP_HEADERconsume_tile<false, false>(global_to_shared_stream, shared_load_stream, accumulators, outer_k);}}// Execute remaining tiles with K-residue predicate updates enabled.CUTLASS_GEMM_LOOPfor (; outer_k > -Traits::OutputTile::kD; outer_k -= Traits::OutputTile::kD) {CUTLASS_GEMM_LOOP_HEADERconsume_tile<true, false>(global_to_shared_stream, shared_load_stream, accumulators, outer_k);}}

创建 MMAEpilogue 对象,然后调用 MMAEpilogue::epilogue 函数。

    typedef typename Traits::Epilogue Epilogue;Epilogue epilogue(params.epilogue, shared_storage.epilogue, params.problem_size.knm());epilogue.epilogue(accumulators, threadblock_offset, block_swizzle.get_batch_id());}
};

参考资料:

  • # [DOC] Where does cutlass’ detailed GEMM kernel? #526
  • Dissecting the NVIDIA Volta GPU Architecture via Microbenchmarking
  • Modeling Deep Learning Accelerator Enabled GPUs
  • gpgpu-sim_distribution
  • 理解Tensor Core
  • Flexible Performant GEMM Kernels on GPUs
  • CUDA Tensor Core编程
  • PROGRAMMING TENSOR CORES: NATIVE VOLTA TENSOR CORES WITH CUTLASS
  • The NVIDIA Titan V Deep Learning Deep Dive: It’s All About The Tensor Cores
  • 9.7.13.4.1. Matrix Fragments for mma.m8n8k4 with .f16 floating point type
  • Numerical Behavior of NVIDIA Tensor Cores
  • CUDA Ampere Tensor Core HGEMM 矩阵乘法优化笔记 —— Up To 131 TFLOPS!
  • If we have two or four memory requests by a warp, do they need coalesced access/contiguity? #328
  • Do bank conflicts increase when using more shared memory?
  • How does parameter computeType affect the computation?
  • 2.1.10. GEMM Algorithms Numerical Behavior
  • cuBLAS的使用
  • RAFT在Knowhere上的一些评估测试[1]
  • How does parameter computeType affect the computation?
  • cudnn-frontend/tree/main/samples/samples/conv_sample.cpp
  • Is a union in C++ actually a class?
  • A Generalized Micro-kernel Abstraction for GPU Linear Algebra
  • Implementing Strassen’s Algorithm with CUTLASS on NVIDIA Volta GPUs
  • Double-buffering in shared memory, details? #227
  • Efficient GEMM in CUDA
  • Thread synchronization with syncwarp
  • Using CUDA Warp-Level Primitives
  • CUDA微架构与指令集(3)-SASS指令集分类
  • VOLTA Architecture and performance optimization
  • How to Optimize a CUDA Matmul Kernel for cuBLAS-like Performance: a Worklog
  • Determining registers holding the data after executing LDG.E.128
  • 刘冰、郑鹏|GPU编程和优化-最佳实践分享

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

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

相关文章

Linux超简单部署个人博客

1 安装halo 1.1 切换到超级用户 sudo -i 1.2 新建halo文件夹 mkdir ~/halo && cd ~/halo 1.3 编辑docker-compose.yml文件 vim ~/halo/docker-compose.yml 英文输入法下&#xff0c;按 i version: "3"services:halo:image: halohub/halo:2.10container_…

2017年全国硕士研究生入学统一考试管理类专业学位联考数学试题——解析版

文章目录 2017 级考研管理类联考数学真题解析一、问题求解&#xff08;本大题共 5 小题&#xff0c;每小题 3 分&#xff0c;共 45 分&#xff09;下列每题给出 5 个选项中&#xff0c;只有一个是符合要求的&#xff0c;请在答题卡上将所选择的字母涂黑。真题&#xff08;2017-…

Python 提高篇学习笔记(一):深拷贝和浅拷贝

文章目录 一、什么是对象的引用二、深拷贝和浅拷贝2.1 浅拷贝(Shallow Copy)2.2 深拷贝(Deep Copy)2.3 copy.copy和copy.deepcopy的区别 一、什么是对象的引用 在 Python 中&#xff0c;对象的引用是指变量指向内存中某个对象的地址或标识符。当你创建一个新的对象(比如一个整…

【Qt开发流程】之富文本处理

描述 Scribe框架提供了一组类&#xff0c;用于读取和操作结构化的富文本文档。与Qt中以前的富文本支持不同&#xff0c;新的类集中在QTextDocument类上&#xff0c;而不是原始文本信息。这使开发者能够创建和修改结构化的富文本文档&#xff0c;而不必准备中间标记格式的内容。…

busybox制作根文件系统2

上篇内容使用busybox制作好了根文件系统&#xff0c;接下来需要进行一些测试和功能的完善&#xff01; 根文件系统的测试 测试根文件系统的时候不是直接烧写到EMMC里面&#xff0c;这样测试效率太低了&#xff0c;Ubuntu的rootfs目录已经保存了根文件系统&#xff0c;只需要在…

向量数据库,展望AGI时代

无论是向量数据库&#xff0c;还是大模型&#xff0c;归根结底&#xff0c;大家在追捧它时的心态&#xff0c;焦虑大于需求。 向量数据库的热潮&#xff0c;在一定程度上“外化”了人们的焦虑。 但这并不能否定向量数据库的实际价值&#xff0c;甚至更长远来看&#xff0c;向…

RedisTemplate使用详解

RedisTemplate介绍StringRedisTemplate介绍RedisConnectionFactory介绍RedisConnectionFactory源码解析 RedisOperations介绍RedisOperations源码解析 RedisTemplate使用连接池配置RedisTemplate连接池连接池配置 RedisTemplate应用场景RedisTemplate主要特点RedisTemplate使用…

redis运维(十六) 有序集合

一 有序集合 把握一点&#xff1a; 各种redis 命令都提供各种语言对应的API 接口,后续API是关键 ① 概念 1、sorted set --> 有序集合2、redis有序集合也是集合类型的一部分&#xff0c;所以它保留了集合中元素不能重复的特性3、但是不同的是,有序集合给每个元素多设置…

转型做视频了,博客就是稿子,继续坚持写博客,同时发布视频,能写博客说明思路清晰了,能再讲明白,理解就更透彻了,紧跟上时代发展。

1&#xff0c;今天特别记录下&#xff0c;B站给开通了《合集》功能 最近使用视频制作了几个视频。播放量还不错&#xff0c;最好的已经到了 2.6K了。 然后粉丝也涨到了 200个。 添加链接描述 紧跟时代&#xff1a;从写博客到录视频&#xff0c;粉丝大涨&#xff0c;突破200个&…

接口自动化测试 —— 工具、请求与响应

一、工具&#xff1a; 1.工具介绍 postman &#xff1a;很主流的API测试工具&#xff0c;也是工作里面使用最广泛的研发工具。 JMeter&#xff1a; ApiPost&#xff1a; 2.安装postman&#xff1a; 安装好直接打开&#xff0c;不用注册。 二、通信模式&#xff1a; 1、…

【Java 进阶篇】从Java对象到JSON:Jackson的魔法之旅

在现代的软件开发中&#xff0c;处理数据的能力是至关重要的。而当我们谈及数据格式时&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;通常是首选。为了在Java中轻松地将对象转换为JSON&#xff0c;我们需要一种强大而灵活的工具。这时&#xff0c;Jackso…

【Java 进阶篇】Redis:打开缓存之门

介绍 Redis&#xff08;Remote Dictionary Server&#xff09;是一个高性能的键值对存储系统&#xff0c;被广泛用作缓存、消息中间件和数据库。它以其快速的读写能力、支持多种数据结构和丰富的功能而闻名。在这篇博客中&#xff0c;我们将深入了解Redis的概念、安装以及基本…

MQTT协议消息代理服务远程连接

目录 1. Linux 搭建 Mosquitto 2. Linux 安装Cpolar 3. 创建MQTT服务公网连接地址 4. 客户端远程连接MQTT服务 5. 代码调用MQTT服务 6. 固定连接TCP公网地址 7. 固定地址连接测试 Mosquitto是一个开源的消息代理&#xff0c;它实现了MQTT协议版本3.1和3.1.1。它可以在不…

第二十章:多线程

进程 线程的特点 1.进程是资源分配的最小单位&#xff0c;线程是最小的执行单位 2.一个进程可以有多个线程 3.线程共享进程资源 package twentyth; public class ThreadTest extends Thread { public void run() { for (int i 1; i < 10; i) {//继承重…

Unity开发之C#基础-File文件读取

前言 今天我们将要讲解到c#中 对于文件的读写是怎样的 那么没接触过特别系统编程小伙伴们应该会有一个疑问 这跟文件有什么关系呢&#xff1f; 我们这样来理解 首先 大家对电脑或多或少都应该有不少的了解吧 那么我们这些软件 都是通过变成一个一个文件保存在电脑中 我们才可以…

Spring Boot创建和使用(重要)

Spring的诞生是为了简化Java程序开发的&#xff01; Spring Boot的诞生是为了简化Spring程序开发的&#xff01; Spring Boot就是Spring框架的脚手架&#xff0c;为了快速开发Spring框架而诞生的&#xff01;&#xff01; Spring Boot的优点&#xff1a; 快速集成框架&#x…

2023年G2电站锅炉司炉证考试题库及G2电站锅炉司炉试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年G2电站锅炉司炉证考试题库及G2电站锅炉司炉试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作业人员上岗证考试大纲…

MySQL 事务的底层原理和 MVCC(一)

在事务的实现机制上&#xff0c;MySQL 采用的是 WAL&#xff08;Write-ahead logging&#xff0c;预写式日志&#xff09;机制来实现的。 在使用 WAL 的系统中&#xff0c;所有的修改都先被写入到日志中&#xff0c;然后再被应用到系统中。通常包含 redo 和 undo 两部分信息。 …

【Java开发】 Springboot集成Mybatis-Flex

1 Mybatis-Flex 介绍 1.1简介 Mybatis-Flex 是一个优雅的 Mybatis 增强框架&#xff0c;它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用 Mybaits-Flex 链接任何数据库&#xff0c;其内置的 QueryWrapper 亮点帮助我们极大的减少了 SQL 编写的工作的同时&#xff…

cocos2dx ​​Animate3D(二)

Twirl 扭曲旋转特效 // 持续时间(时间过后不会回到原来的样子) // 整个屏幕被分成几行几列 // 扭曲中心位置 // 扭曲的数量 // 振幅 static Twirl* create(float duration, const Size& gridSize, const Vec2& position, unsigned int twirls, float amplitude)…