当前位置: 首页 > news >正文

【CUDA】Branch Divergence and Unrolling Loop

目录

一、避免分支发散

1.1 并行规约问题

1.2 并行规约中的发散

二、UNrolling Loops


一、避免分支发散

控制流有时依赖于 thread 索引。同一个warp中,一个条件分支可能导致性能很差。通过重新组织数据获取模式可以减少或避免 warp divergence。具体问题查看下面这篇文章:

【CUDA】Warp解析-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/GG_Bruse/article/details/143772619

1.1 并行规约问题

若要计算一个数组N个元素的和,使用CPU编程实现十分容易

int sum = 0;
for (int i = 0; i < N; ++i)sum += array[i]

若数组中的元素非常多,应用并行计算可以大大提高效率。鉴于加法交换律等性质,这个求和过程可以以元素的任意顺序来进行:

  • 将输入数组切割成很多小的块
  • 用 thread 来计算每个块的和
  • 对这些块的结果再求和得最终结果

数组的切割主旨是:用 thread 求数组中按一定规律配对的的两个元素和,然后将所有结果组合成一个新的数组,然后再次求配对两元素和,多次迭代,直到数组中只有一个结果

比较直观的两种实现方式是:

  1. Neighbored pair:每次迭代都是相邻两个元素求和
  2. Interleaved pair:按一定跨度配对两个元素

对于N个元素的数组,该过程需N - 1次求和、log_{2}N步。InterleavedPair的跨度是半个数组长度

上述讲的这类问题术语为 reduction problem。Parallel reduction(并行规约)是指迭代减少操作,是并行算法中非常关键的一种操作

1.2 并行规约中的发散

Neighbored pair

在这个kernel中,有两个global memory array,一个用来存放数组所有数据,另一个用来存放部分和。所有 block 独立的执行求和操作

#include <iostream>
#include <cuda_runtime.h>
#include <cuda_runtime_api.h>
using namespace std;// 1 << 28 == 268435456
__global__ 
void reduceNeighbored(int* inputData, int *outputData, unsigned int N) 
{unsigned int threadIndex = threadIdx.x; // 0 - 511unsigned int index = blockIdx.x * blockDim.x + threadIdx.x; // 0 - 524287, 512, 0 - 511 即 0 - 268435455int* iData = inputData + blockIdx.x * blockDim.x; // inputData + (0 - 268434944) 即指向每个block的起始位置if (index >= N) return;// stride = (1 - 511) 1, 2, 4, ..., 500for (int stride = 1; stride < blockDim.x; stride *= 2) {if ((threadIndex % (2 * stride)) == 0) // 每次取threadIndex为偶数的threadiData[threadIndex] += iData[threadIndex + stride];__syncthreads();}if (threadIndex == 0) outputData[blockIdx.x] = iData[0]; // 放入计算出的数据 
}void GPU_ReduceNeighbored(int* dInputData, int* dOutputData, int size, dim3 grid, dim3 block)
{reduceNeighbored<<<grid, block>>>(dInputData, dOutputData, size);
}

因为无法让所有的block同步,所以最后将所有block的结果送回 host 进行串行计算

主函数代码:

void GPU_ReduceNeighbored(int* inputData, int *outputData, int N, dim3 grid, dim3 block);long long Seconds()
{// 获取当前时间点auto now = std::chrono::high_resolution_clock::now();// 将时间点转换为以毫秒为单位的时间间隔auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());// 获取毫秒数return duration.count();
}int main(int argc, char** argv)
{// set up deviceint device = 0;cudaDeviceProp deviceProp;cudaGetDeviceProperties(&deviceProp, device);printf("%s starting reduction at device %d : %s\n", argv[0], device, deviceProp.name);cudaSetDevice(device);// initialization int size = 1 << 28;printf("Array Size : %d\n", size);int blockSize = 512;dim3 block(blockSize, 1);dim3 grid((size + block.x - 1) / block.x, 1);printf("grid %d , block %d\n", grid.x, block.x);// allocate host memorysize_t bytes = size * sizeof(int);int* hInputData = (int*) malloc(bytes);int* hOutputData = (int*) malloc(grid.x * sizeof(int));int* tmp = (int*) malloc(bytes);for(int i = 0; i < size; ++i) hInputData[i] = (int)(rand() & 0xFF); // 屏蔽最大两个字节memcpy(tmp, hInputData, bytes);// allocate device memoryint* dInputData = nullptr;int* dOutputData = nullptr;cudaMalloc((void **) &dInputData, bytes);cudaMalloc((void **) &dOutputData, grid.x * sizeof(int));int iStart, iElaps;// kernel: reduceNeighboredcudaMemcpy(dInputData, hInputData, bytes, cudaMemcpyHostToDevice);cudaDeviceSynchronize();iStart = Seconds();GPU_ReduceNeighbored(dInputData, dOutputData, size, grid, block);cudaDeviceSynchronize();iElaps = Seconds() - iStart;cudaMemcpy(hOutputData, dOutputData, grid.x * sizeof(int), cudaMemcpyDeviceToHost);long long gpuSum = 0;for (int i = 0; i < grid.x; i++) gpuSum += hOutputData[i];printf("gpu Neighbored elapsed %d ms gpuSum: %lld <<<grid %d block %d>>>\n", iElaps, gpuSum, grid.x, block.x);// free host memoryfree(hInputData);free(hOutputData);// free device memorycudaFree(dInputData);cudaFree(dOutputData);// reset devicecudaDeviceReset();return 0;
}

if ((tid % (2 * stride)) == 0) 该表达式只对偶数ID的线程为true,所以其导致很高的 divergent warps。第一次迭代只有偶数ID的线程执行了指令,但是所有线程都要被调度;第二次迭代,只有四分之一的 thread 是 active 的,但所有 thread 都要被调度。可以重新组织每个线程对应的数组索引来强制ID相邻的thread来处理求和操作

__global__ 
void reduceNeighboredLess(int* inputData, int* outputData, unsigned int N) 
{unsigned int threadIndex = threadIdx.x; // 0 - 511unsigned int index = blockIdx.x * blockDim.x + threadIdx.x; // 0 - 524287, 512, 0 - 511 即 0 - 268435455int* iData = inputData + blockIdx.x * blockDim.x; // inputData + (0 - 268434944) 即指向每个block的起始位置if(index >= N) return;// stride = (1 - 511) 1, 2, 4, ..., 500for (int stride = 1; stride < blockDim.x; stride *= 2) {int idx = 2 * stride * threadIndex; // 2 * (1 - 500) * (0 - 511)if (idx < blockDim.x) iData[idx] += iData[idx + stride];__syncthreads();  }if (threadIndex == 0) outputData[blockIdx.x] = iData[0];
}void GPU_ReduceNeighboredLess(int* dInputData, int* dOutputData, int size, dim3 grid, dim3 block)
{reduceNeighboredLess<<<grid, block>>>(dInputData, dOutputData, size);
}

int index = 2 * stride * tid; 因为步调乘以了2,下面的语句使用block的前半部分thread来执行求和:if (index < blockDim.x)

对于一个有512个 thread 的 block 而言,前八个 warp 执行第一轮 reduction,剩下八个 warp 什么也不干;第二轮,前四个 warp 执行,剩下十二个什么也不干。就不存在divergence了(divergence只发生于同一个warp)。但最后还是会有divergence,因为这个时候需要执行 threads 已经凑不够一个 warp 了

Interleaved pair

 Interleaved Pair模式的初始步调是block大小的一半,每个thread处理相隔半个block的两个数据

__global__ 
void reduceInterleaved(int *inputData, int *outputData, unsigned int N) 
{unsigned int threadIndex = threadIdx.x;unsigned int index = blockIdx.x * blockDim.x + threadIdx.x;int *iData = inputData + blockIdx.x * blockDim.x;if(index >= N) return;for (int stride = blockDim.x / 2; stride > 0; stride >>= 1) {if (threadIndex < stride)iData[threadIndex] += iData[threadIndex + stride];__syncthreads();}if (threadIndex == 0) outputData[blockIdx.x] = iData[0];
}void GPU_ReduceInterleaved(int* dInputData, int* dOutputData, int size, dim3 grid, dim3 block)
{reduceInterleaved<<<grid, block>>>(dInputData, dOutputData, size);
}

步调被初始化为block大小的一半:for (int stride = blockDim.x / 2; stride > 0; stride >>= 1)

下面的语句使得第一次迭代时,block的前半部分thread执行相加操作,第二次是前四分之一,以此类推:if (tid < stride)

二、UNrolling Loops

loop unrolling(取消循环)是用来优化循环减少分支的方法,该方法就是把本应在多次 loop 中完成的操作,尽量压缩到一次 loop。循环体展开程度称为loop unrolling factor(循环展开因子),loop unrolling对顺序数组的循环操作性能有很大影响

for (int i = 0; i < 100; ++i) {a[i] = b[i] + c[i];
}

如下重复一次循环体操作,迭代数目将减少一半:

for (int i = 0; i < 100; i += 2) {a[i] = b[i] + c[i];a[i+1] = b[i+1] + c[i+1];
}   

从高级语言层面是无法看出性能提升的原因的,需从 low-level instruction 层面去分析,第二段代码循环次数减少了一半,而循环体两句语句的读写操作的执行在CPU上是可以同时执行互相独立的,所以相对第一段,第二段性能要好

Unrolling 在CUDA编程中意义更重。目标是通过减少指令执行消耗,增加更多的独立指令来提高性能。这样就会增加更多的并行操作从而产生更高的指令和内存带宽(bandwidth)。也就提供了更多的 eligible warps 来帮助 hide instruction / memory latency

在前文的 reduceInterleaved 中,每个block处理一部分数据,将这些数据称为 dataBlock

下面的代码是 reduceInterleaved 的修正版本,每个 block 都是以两个 dataBlock 作为源数据进行操作(前文中,每个 block 处理一个 dataBlock)。这是一种循环分区:每个 thread 作用于多个 dataBlock,并且从每个 dataBlock 中取出一个元素处理

__global__ 
void reduceUnrolling(int *inputData, int *outputData, unsigned int N) 
{unsigned int threadIndex = threadIdx.x;unsigned int index = blockIdx.x * blockDim.x * 2 + threadIdx.x;int *iData = inputData + blockIdx.x * blockDim.x * 2;// unrolling 2 data blocksif (index + blockDim.x < N) inputData[index] += inputData[index + blockDim.x];__syncthreads();for (int stride = blockDim.x / 2; stride > 0; stride >>= 1) {if (threadIndex < stride) iData[threadIndex] += iData[threadIndex + stride];__syncthreads();}if (threadIndex == 0) outputData[blockIdx.x] = iData[0];
}  void GPU_ReduceUnrolling(int* dInputData, int* dOutputData, int size, dim3 grid, dim3 block)
{reduceUnrolling<<<grid.x / 2, block>>>(dInputData, dOutputData, size);
}

注意下面的语句,每个 thread 从相邻的 dataBlock中取数据,这一步实际上就是将两个 dataBlock规约成一个:if (idx + blockDim.x < n) g_idata[idx] += g_idata[idx+blockDim.x];

global array index 也要相应的调整,因为,相对之前的版本,同样的数据,只需要原来一半的 thread 就能解决问题。要注意的是,这样做也会降低warp或block的并行性(因为thread减少):

由于每个block处理两个data block,所以需要调整grid的配置:

reduceUnrolling2<<<grid.x / 2, block>>>(d_idata, d_odata, size);

同一个 thread 中若能有更多的独立的 load/store 操作,会产生更好的性能,因为这样做 memory latency 能够更好的被隐藏

__syncthreads是用来同步block内部thread的。在reduction kernel中,其被用来在每次循环中保证所有 thread 的写 global memory 的操作都已完成,这样才进行下一阶段的计算

当kernel进行到只需要少于或等32个thread(即一个warp)呢?由于使用的SIMT模式,warp内的thread 是有一个隐式的同步过程的。最后几次迭代可以用下面的语句展开:

if (tid < 32) {volatile int *vmem = idata;vmem[tid] += vmem[tid + 32];vmem[tid] += vmem[tid + 16];vmem[tid] += vmem[tid + 8];vmem[tid] += vmem[tid + 4];vmem[tid] += vmem[tid + 2];vmem[tid] += vmem[tid + 1];
}

warp unrolling避免了__syncthreads同步操作,因为这一步本身就没必要

注意volatile修饰符,其告诉编译器每次执行赋值时必须将 vmem[tid] 的值 store 回 global memory。若不这样,编译器或 cache 可能会优化读写global/shared memory。有了这个修饰符,编译器就会认为这个值会被其他 thread 修改,从而使得每次读写都直接去 memory 而不是去 cache 或者 register

__global__ void reduceUnrollWarps8(int *inputData, int *outputData, unsigned int N) 
{unsigned int threadIndex = threadIdx.x;unsigned int index = blockIdx.x * blockDim.x * 8 + threadIdx.x;int *iData = inputData + blockIdx.x * blockDim.x * 8;// unrolling 8if (index + 7*blockDim.x < N) {int a1 = inputData[index];int a2 = inputData[index + blockDim.x];int a3 = inputData[index + 2 * blockDim.x];int a4 = inputData[index + 3 * blockDim.x];int b1 = inputData[index + 4 * blockDim.x];int b2 = inputData[index + 5 * blockDim.x];int b3 = inputData[index + 6 * blockDim.x];int b4 = inputData[index + 7 * blockDim.x];inputData[index] = a1 + a2 + a3 + a4 + b1 + b2 + b3 + b4;}__syncthreads();for (int stride = blockDim.x / 2; stride > 32; stride >>= 1) {if (threadIndex < stride) iData[threadIndex] += iData[threadIndex + stride];__syncthreads();}// unrolling warpif (threadIndex < 32) {volatile int *vmem = iData;vmem[threadIndex] += vmem[threadIndex + 32];vmem[threadIndex] += vmem[threadIndex + 16];vmem[threadIndex] += vmem[threadIndex + 8];vmem[threadIndex] += vmem[threadIndex + 4];vmem[threadIndex] += vmem[threadIndex + 2];vmem[threadIndex] += vmem[threadIndex + 1];}if (threadIndex == 0) outputData[blockIdx.x] = iData[0];
}           void GPU_ReduceUnrolling8(int* dInputData, int* dOutputData, int size, dim3 grid, dim3 block)
{reduceUnrollWarps8<<<grid.x / 8, block>>>(dInputData, dOutputData, size);
}

若在编译时已知了迭代次数,就可以完全把循环展开。Fermi 和 Kepler 每个 block 的最大 thread 数目都是1024,文中的 kernel 的迭代次数都是基于 blockDim 的,所以完全展开循环是可行的

__global__ 
void reduceCompleteUnrollWarps8(int *inputData, int *outputData, unsigned int N) 
{unsigned int threadIndex = threadIdx.x;unsigned int index = blockIdx.x * blockDim.x * 8 + threadIdx.x;int *iData = inputData + blockIdx.x * blockDim.x * 8;// unrolling 8if (index + 7 * blockDim.x < N) {int a1 = inputData[index];int a2 = inputData[index + blockDim.x];int a3 = inputData[index + 2 * blockDim.x];int a4 = inputData[index + 3 * blockDim.x];int b1 = inputData[index + 4 * blockDim.x];int b2 = inputData[index + 5 * blockDim.x];int b3 = inputData[index + 6 * blockDim.x];int b4 = inputData[index + 7 * blockDim.x];inputData[index] = a1 + a2 + a3 + a4 + b1 + b2 + b3 + b4;}__syncthreads();if (blockDim.x>=1024 && threadIndex < 512) iData[threadIndex] += iData[threadIndex + 512];__syncthreads();        if (blockDim.x>=512 && threadIndex < 256) iData[threadIndex] += iData[threadIndex + 256];__syncthreads();if (blockDim.x>=256 && threadIndex < 128) iData[threadIndex] += iData[threadIndex + 128];__syncthreads();if (blockDim.x>=128 && threadIndex < 64) iData[threadIndex] += iData[threadIndex + 64];__syncthreads();// unrolling warpif (threadIndex < 32) {volatile int *vsmem = iData;vsmem[threadIndex] += vsmem[threadIndex + 32];vsmem[threadIndex] += vsmem[threadIndex + 16];vsmem[threadIndex] += vsmem[threadIndex + 8];vsmem[threadIndex] += vsmem[threadIndex + 4];vsmem[threadIndex] += vsmem[threadIndex + 2];vsmem[threadIndex] += vsmem[threadIndex + 1];}if (threadIndex == 0) outputData[blockIdx.x] = iData[0];
}                void GPU_ReduceCompleteUnrollWarps8(int* dInputData, int* dOutputData, int size, dim3 grid, dim3 block)
{reduceCompleteUnrollWarps8<<<grid.x / 8, block>>>(dInputData, dOutputData, size);
}

相关文章:

【CUDA】Branch Divergence and Unrolling Loop

目录 一、避免分支发散 1.1 并行规约问题 1.2 并行规约中的发散 二、UNrolling Loops 一、避免分支发散 控制流有时依赖于 thread 索引。同一个warp中&#xff0c;一个条件分支可能导致性能很差。通过重新组织数据获取模式可以减少或避免 warp divergence。具体问题查看下…...

深度学习:卷积神经网络的计算复杂度,顺序操作,最大路径长度

卷积层的计算复杂度 在深度学习中&#xff0c;卷积层的计算复杂度主要取决于卷积核的大小、输入和输出的通道数量、以及输入序列的长度。具体来说&#xff0c;卷积层的计算复杂度可以通过以下几个因素来计算&#xff1a; 卷积核大小 k&#xff1a;卷积核的大小决定了每次卷积操…...

springboot 配置文件中 multipart.max-file-size 各个版本的写法

由于springboot具有几个版本&#xff0c;不同版本对于文件上传最大限制的配置也有所不同。 所以要注意springboot本身的版本&#xff0c;不然会一直报错 在springboot1.3版本中&#xff1a; multipart.maxFileSize在springboot1.4与springboot1.5版本中&#xff1a; spring…...

linux 中mysql查看慢日志

1、到mysql容器&#xff0c;先登录到数据库&#xff0c;查看是否开启 mysql -h 127.0.0.1 -uroot -p SHOW VARIABLES LIKE slow_query_log; 2、如果没有开启&#xff0c;需要先开启 set global slow_query_log ON; 3、查看慢日志文件 SHOW VARIABLES LIKE slow_query_log…...

单片机的基本组成与工作原理

单片机&#xff08;Microcontroller Unit, MCU&#xff09;是一种将计算机的主要部分集成在一个芯片上的小型计算机系统。它通常包括中央处理器&#xff08;CPU&#xff09;、存储器&#xff08;Memory&#xff09;、输入输出接口&#xff08;I/O Ports&#xff09;、定时器/计…...

智慧隧道和智慧交通

通过引入先进的物联网技术&#xff0c;将各种硬件设备如传感器、摄像头、控制系统等有效地连接并管理起来&#xff0c;以实现道路安全和交通流畅的目标。这些设备将能够实时监控和控制隧道内的各种设备和系统&#xff0c;从而提高道路安全、提升驾驶体验并降低管理成本。 在这个…...

List、Set、Map详解和区别

在 Java 中&#xff0c;List、Set、Map是常用的集合类型&#xff0c;它们各自具有不同的特点和用途&#xff0c;以下是对它们的详细介绍及区别分析&#xff1a; List&#xff08;列表&#xff09; 特点&#xff1a; 有序性&#xff1a;List中的元素是有序的&#xff0c;即元素…...

界面控件DevExpress WinForms v24.2新功能预览 - 支持.NET 9

DevExpress WinForms 拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…...

Postman之pm.test断言操作

Postman之pm.test断言操作 1.断言方法2.连接符3.条件判断符 用于验证请求的响应数据是否符合预期 1.断言方法 pm.test()&#xff1a;定义一个测试函数&#xff0c;接受两个参数&#xff0c;一个字符串参数用来描述该测试&#xff0c;一个返回True/False的函数 语法格式&#…...

对数几率回归

对数几率回归简介 对数几率回归&#xff08;Logistic Regression&#xff09;是一种用于解决分类问题的经典统计模型&#xff0c;其核心思想是利用逻辑函数&#xff08;Sigmoid函数&#xff09;将线性回归模型的输出值映射到概率范围 [0, 1]&#xff0c;从而实现分类预测。对数…...

docker 配置同宿主机共同网段的IP 同时通过通网段的另一个电脑实现远程连接docker

docker配置网络 #宿主机执行命令 ifconfig 查询对应的主机ip 子网掩码 网关地址 #[网卡名称]&#xff1a;inet[主机IP] netmask[子网掩码] broadcast[网关地址]这里需要重点关注&#xff1a;eno1[网卡名称]以及【192.168.31.225】网关地址 在宿主机执行docker命令创建一个虚拟…...

4-7-1.C# 数据容器 - LinkedList(LinkedList 的定义、LinkedList 结点的遍历、LinkedList 的常用方法)

LinkedList 概述 LinkedList<T> 通过节点&#xff08;Node&#xff09;来存储数据&#xff0c;每个节点包含数据和指向下一个节点的引用 LinkedList<T> 存储的元素是可重复的 LinkedList<T> 支持泛型&#xff0c;可以指定存储的元素的类型 LinkedList<…...

「三」体验HarmonyOS端云一体化开发模板——使用DevEco Studio直接创建端云一体化工程

关于作者 白晓明 宁夏图尔科技有限公司董事长兼CEO、坚果派联合创始人 华为HDE、润和软件HiHope社区专家、鸿蒙KOL、仓颉KOL 华为开发者学堂/51CTO学堂/CSDN学堂认证讲师 开放原子开源基金会2023开源贡献之星 「目录」 「一」HarmonyOS端云一体化概要 「二」体验HarmonyOS端云一…...

确保以管理员权限运行 Visual Studio 开发者命令提示符

文章目录 解决方法&#xff1a;1. 以管理员身份运行命令提示符2. 改变目录权限3. 改变项目目录位置4. 检查文件系统权限 总结&#xff1a; ********************************************************************** ** Visual Studio 2022 Developer Command Prompt v17.12.0 …...

命令执行简单(棱角社区有毒)

前言&#xff1a;小迪安全2022第一节反弹shell&#xff0c;小迪用的是两台都是云服务器&#xff0c;没有服务器可以在自己的主机上搭建也是可以的&#xff0c;主机上搭两个网站 思路&#xff1a;生成一个木马文件&#xff0c;下载到本机&#xff0c;然后利用本机上传到目标主机…...

Keil基于ARM Compiler 5的工程迁移为ARM Compiler 6的工程

环境&#xff1a; keil版本为5.38&#xff0c;版本务必高于5.30 STM32F4的pack包版本要高于2.9 软件包下载地址&#xff1a;https://zhuanlan.zhihu.com/p/262507061 一、更改Keil中编译器 更改后编译&#xff0c;会报很多错&#xff0c;先不管。 二、更改头文件依赖 观察…...

Kafka-创建topic源码

一、命令创建topic kafka-topics --create --topic quickstart-events --bootstrap-server cdh1:9092 --partitions 2 --replication-factor 2 二、kafka-topics脚本 exec $(dirname $0)/kafka-run-class.sh org.apache.kafka.tools.TopicCommand "$" 脚本中指定了…...

【网络安全】(一) 0成本添加访问级监控

互联网的安全感这个概念源于阿里。顾名思义&#xff0c;让互联网的用户对于web产品能够产生足够的信任和依赖。特别是涉及到用户资金交易的站点&#xff0c;一次严重的用户资料泄露就可以彻底毁掉你的品牌。 然而当前阶段除了bat大部分互联网行业的企业对于网络安全给的重视都…...

【Three.js基础学习】26. Animated galaxy

前言 shaders实现星系 课程回顾 使用顶点着色器为每个粒子设置动画 a属性 &#xff0c; u制服 &#xff0c;v变化 像素比&#xff1a;window.devicePixelRatio 自动从渲染器检索像素比 renderer.getPixelRatio() 如何尺寸衰减&#xff0c; 放大缩小视角时&#xff0c;粒子都是同…...

vscode使用ssh配置docker容器环境

1 创建容器&#xff0c;并映射主机和容器的指定ssh服务端口 2 进入容器 docker exec -it <容器ID> /bin/bash 3在容器中安装ssh服务 apt-get update apt-get install openssh-server 接着修改ssh文件信息,将容器的10008端口暴露出来允许root用户使用ssh登录 vim /…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

大数据零基础学习day1之环境准备和大数据初步理解

学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 &#xff08;1&#xff09;设置网关 打开VMware虚拟机&#xff0c;点击编辑…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

mac:大模型系列测试

0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何&#xff0c;是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试&#xff0c;是可以跑通文章里面的代码。训练速度也是很快的。 注意…...