性能优化-HVX 开发环境介绍

「发表于知乎专栏《移动端算法优化》」

本篇以 HVX 的开发环境配置以及应用实例编译测试为主进行讲述。

🎬个人简介:一个全栈工程师的升级之路!
📋个人专栏:高性能(HPC)开发基础教程
🎀CSDN主页 发狂的小花
🌄人生秘诀:学习的本质就是极致重复!

目录

一、Hexagon SDK 下载和安装

A. SDK 下载

B. SDK 安装

C. SDK 目录介绍

D. Android NDK

二、Hexagon SDK 手机端运行

A. 工程编译

B. 签名

C. 算法实机测试

三、 算法实例分析

A. SDK 工程目录结构

B. CPU 端算法流程

C. DSP 端算法流程:

四、总结


本篇以 HVX 的开发环境配置以及应用实例编译测试为主进行讲述。

一、Hexagon SDK 下载和安装

HVX 开发工具分为 windows 和 Ubuntu环境,本专栏主要以 Ubuntu 环境为主进行介绍。

HVX 的开发工具是 Hexagon SDK(文章基于版本 Ubuntu 20.04 进行演示)。

A. SDK 下载

  • 高通开发者官网 下载 SDK 安装包(hexagon_sdk_lnx_3_5_installer_00006_1.zip),文章基于 SDK3.5.4 版本进行介绍。

下载官网示意图

B. SDK 安装

# 解压安装包 hexagon_sdk_lnx_3_5_installer_00006_1.zip
# 在解压后的目录下进行如下操作
sudo chmod a+x ./qualcomm_hexagon_sdk_3_5_4_eval.bin
./qualcomm_hexagon_sdk_3_5_4_eval.bin

C. SDK 目录介绍

  • SDK 根目录

hexagon_sdk 目录

文件夹描述
build编译所需的编译脚本文件
docsHVX 开发相关说明文档
examplesSDK 例子,HVX 样例位于 common 文件夹
incsSDK 头文件目录,包含 HVX 函数及指令等头文件
libsSDK 必需库文件目录,包含 HVX 开发运行所涉及的必备库及部分实现代码,例如 dspcv,fastcv,hexagon_nn 等
scripts常用脚本目录
setup_sdk_env.source环境变量设置脚本
tools常用工具集合,包含 ndk,qaic 及签名工具(elfsigner)等
  • tools 目录

hexagon_sdk tools 目录

  • tools/HEXAGON_Tools 目录

hexagon_sdk tools HEXAGON_Tools 目录

功能路径
HVX 模拟器仿真样例${HVX_SDK_PATH}\tools\HEXAGON_Tools\8.3.07\Examples\HVX
DSP 开发手册文档${HVX_SDK_PATH}\tools\HEXAGON_Tools\8.3.07\Documents\Hexagon_Document_Bundle.pdf

D. Android NDK

HVX SDK 需要依赖 Andriod NDK 来进行编译测试,NDK 需放置于${HVX_SDK_PATH}/tools 目录,Android NDK 需要开发者下载配置。

Android NDK 下载,文章中使用 Linux 版本 android-ndk-r19c。(3.5.4版本 SDK 使用 android-ndk-r19c 即可。

二、Hexagon SDK 手机端运行

该部分以 ${HVX_SDK_PATH}/examples/common/gaussian7x7 为例进行说明。

A. 工程编译

  • 设置环境变量:
cd ${HVX_SDK_PATH}  
source setup_sdk_env.source
  • Andorid 端应用编译:
cd ${HVX_SDK_PATH}/examples/common/gaussian7x7
make tree V=android_Release_aarch64 CDSP_FLAG=1

Android 端可执行程序 位于${HVX_SDK_PATH}/examples/common/gaussian7x7/android_Release_aarch64/ship/ gaussian7x7 目录

  • CDSP 端应用编译:
make tree V=hexagon_Release_dynamic_toolv83_v66 VERBOSE=1

CDSP 端算法 libgaussian7x7_skel.so 库位于${HVX_SDK_PATH}/examples/common/gaussian7x7/hexagon_Release_dynamic_toolv83_v68/ship/libgaussian7x7_skel.so

相关编译选项解释:

toolv83表示 tools 版本是 8.3
V66表示 DSP 架构版本是 V66(SM8150, SM8250 使用 V66, SM8350 使用 V68)
CDSP_FLAG=1表示引用加载至 CDSP 端运行。SOC 中存在多个 DSP(如 ADSP,CDSP 等),编译时需显式指定

B. 签名

手机系统中存在安全及认证机制,CDSP 库文件需要进行签名认证,以确保可以被正确加载运行。

  • 签名方法:

签名方法通常有两种:开发签名量产签名。(sm8150 之后,可以使用 Unsiged PD 方式进行算法验证测试,但部分硬件资源使用受限)

① 开发签名:

应用计算法处在开发阶段(Debug Fuse Enabled on的阶段)时,可以采用开发签名进行调试。

开发签名需要获取 设备端的序列号,然后生成相应的签名库文件 Testsig.so。

  • 获取序列号:
adb wait-for-device root
adb remount
adb push ${HVX_SDK_PATH}/3.5.4/tools/elfsigner/getserial/CDSP/android_Release/getserial /data
adb shell chmod 777 /data/getserial
adb shell /data/getserial 

如果getserial 失败了, 用下面的指令:

adb shell cat /sys/devices/soc0/serial_number  //这里返回的是十进制,需要转化成十六进制
  • 根据序列号生成 testsig.so 开发签名库
cd ${HVX_SDK_PATH}/tools/elfsigner/
elfsigner.py -t 0xXXXXXXXX
#0xXXXXXXXX为前面获取的序列号转换成十六进制的值。
  • 将签名库 testsig.so push 至手机端:
adb wait-for-device root
adb remount
adb shell mkdir -p /vendor/lib/rfsa/adsp#testsig-0x6E07C1CE.so 为根据测试机序列号生成的开发签名库
adb push ${HVX_SDK_PATH}/tools/elfsigner/output/testsig-0x6E07C1CE.so /vendor/lib/rfsa/adsp/   

②量产签名:

量产签名主要用于批量生产时签名,需要对 DSP firmware 进行重新编译 。firmware 编译过程中会提取指定目录下算法库文件的哈希信息,然后存储于系统中,运行时会进行检测。(该方法需要针对每次算法调整都做签名)

③Unsiged PD:

从 8150 开始,增加 Unsiged PD feature,即在 host 端进行 CDSP 初始化时开启 unsiged PD 功能。

该方式可除部分硬件资源使用受限外,对于开发者而言更加便利。

// Unsigned PD
if (1 == UNSIGNED_PD)
{if (remote_session_control){struct remote_rpc_control_unsigned_module data;data.enable = 1;data.domain = CDSP_DOMAIN_ID;retVal = remote_session_control(DSPRPC_CONTROL_UNSIGNED_MODULE, (void*)&data, sizeof(data));printf("remote_session_control returned %d for configuring unsigned PD.\n", retVal);}else{printf("Unsigned PD not supported on this device.\n");}
}

C. 算法实机测试

首先将编译生成的测试程序及库文件 push 至测试机中,该示例的测试应用编译路径为 ${HVX_SDK_PATH}/examples/common/gaussian7x7

  • push Android 端测试程序
adb wait-for-device root
adb remount
#进入gaussian7x7例子目录
cd ${HVX_SDK_PATH}/examples/common/gaussian7x7  
adb push android_Release_aarch64/ship/gaussian7x7 /vendor/bin/
adb shell chmod +x /vendor/bin/gaussian7x7
  • push DSP 端算法库文件
adb push hexagon_Release_dynamic_toolv83_v66/ship/libgaussian7x7_skel.so /vendor/lib/rfsa/adsp/
  • 运行测试
adb shell
cd /vendor/bin
./gaussian7x7

执行输出如下:

运行测试结果

上述为手机端运行测试流程,基于 hexagon-sim模拟器的算法运行测试会在后续章节进行介绍。

三、 算法实例分析

继续 gaussian7x7(${HVX_SDK_PATH}/examples/common/gaussian7x7)为例进行说明。

程序代码、编译文件和运行过程。

A. SDK 工程目录结构

  • asm_src:算法 HVX 汇编代码实现
  • inc:IDL 文件
  • src:CPU 侧代码实现和算法 DSP 侧实现(包含HVX Intrinsic代码)。DSP 侧代码提供了 HVX 汇编及 HVX Intrinsic 两种代码实现)。
  • android.min:CPU 侧代码编译的 makefile 配置文件
  • hexagon.min:DSP 侧代码编译的 makefile 配置文件

处理器间(CPU,DSP)通信由 Fastrpc 完成。算法调用过程解析通过 idl 编译生成的函数接口映射来处理。

  • IDL 映射文件

inc/gaussian7x7.idl 为该例程的映射文件,用来定义 CPU 和 DSP 同步使用的接口,包括函数、结构体等。

RPC 调用过程需要调用反射机制实现,HVX 的调用反射基于 IDL 来实现,使用 IDL 来定义调用接口,以使 CPU 能完成 DSP 的函数调用。

编译器根据 idl 文件编译生成 gaussian7x7.h、gaussian7x7_stub.c 和gaussian7x7_skel.c 三个文件。

下面介绍一下gaussian7x7的idl定义:

AEEResult Gaussian7x7u8
( in sequence<uint8> src, // input buffer of unsigned 8-bit valuesin uint32 srcWidth,  // width of region of interest contained in src image in uint32 srcHeight, // height of region of interest contained in src imagein uint32 srcStride,           // stride of the src image rout sequence<uint8> dst,  // output buffer of unsigned 8-bit values in uint32 dstStride,           // stride of the dst image in int32 LOOPS,                // number of times to iterate in int32 wakeupOnly,           // flag to skip processing inrout int32 dspUsec,           // profiling result in uSec inrout int32 dspCyc             // profiling result in cycles 
);

上述代码为 gaussian7x7 的接口定义:

  • Sequence为表示数组参数,转义接口为 data 指针及 数组 size;

in 表示为参数为输入属性,生成为 const 类型。

因此 in sequence<uint8> src 对应的接口参数为 const uint8* imgSrc, int srcLen;

  • rout sequence<uint8> dst 中 rout 表示输出属性,生成对应的接口参数为 uint8* imgDst, int dstLen。
  • in uint32 srcWidth 生成对应的接口参数为 uint32 srcWidth
  • rout int32 dspUsec 生成对应的接口参数为 int32* dspUsec

生成三个文件位于 android_Release_aarch64 和 hexagon_Release_dynamic_toolv83_v66 文件夹内,如下图所示

在编程过程中, CPU 端会将 gaussian7x7.h 和 gaussian7x7_stub.c 代码编译后链接至 CPU 端的应用程序,DSP 端会将 gaussian7x7.h 和 gaussian7x7_skel.c 代码编译后链接生成 DSP 端运行库。

基于 IDL 生成函数接口如下,位于 gaussian7x7.h 中

QAIC_HEADER_EXPORT AEEResult __QAIC_HEADER(benchmark_gaussian7x7)(remote_handle64 _h, const uint8* src, int srcLen, uint32 srcWidth, uint32 srcHeight, uint32 srcStride, uint8* dst, int dstLen, uint32 dstStride, int32 LOOPS, int32 wakeupOnly, int32* dspUsec, int32* dspCyc) __QAIC_HEADER_ATTRIBUTE;

B. CPU 端算法流程

CPU 端的流程图如下(基于 ${HVX_SDK_PATH}/examples /common /gaussian7x7/gaussian7x7.c):

  • 通常在硬件设备和用户空间共享数据时,会基于 ION(后续 DMA BUF Heap)实现共享大尺寸连续物理内存,以减少内存拷贝开销实现 zero copy。该例程中使用 rpcmem_init 函数进行初始化(sm8350不再需要 rpcmem 初始化调用)
rpcmem_init();
  • 初始化DSP,设置时钟,带宽等参数。
// call dspCV_initQ6_with_attributes() to bump up Q6 clock frequency
// Since this app is not real-time, and can fully load the DSP clock & bus resources 
// throughout its lifetime, vote for the maximum available MIPS & BW.
dspCV_Attribute attrib[] =
{{DSP_TOTAL_MCPS, 1000},                 // Slightly more MCPS than are available on current targets{DSP_MCPS_PER_THREAD, 500},             // drive the clock to MAX on known targets{PEAK_BUS_BANDWIDTH_MBPS, 12000},       // 12 GB/sec is slightly higher than the max realistic max BW on existing targets.{BUS_USAGE_PERCENT, 100},               // This app is non-real time, and constantly reading/writing memory
};retVal = dspCV_initQ6_with_attributes(attrib, sizeof(attrib)/sizeof(attrib[0]));
printf("return value from dspCV_initQ6() : %d \n", retVal);
VERIFY(0 == retVal);
  • 基于 rpcmem 申请 buf,高通内部使用 ION 进行硬件设备内存共享,可以有效江都 Fastrpc 通信时间,基于常规堆内存分配,会引发数据拷贝操作。
// allocate ion buffers on CDSP side
VERIFY(0 != (src = (uint8_t*)rpcmem_alloc(ION_HEAP_ID_SYSTEM, RPCMEM_DEFAULT_FLAGS, srcSize)));
printf("src - allocated %d\n", (int)srcSize);
VERIFY(0 != (dst = (uint8_t*)rpcmem_alloc(ION_HEAP_ID_SYSTEM, RPCMEM_DEFAULT_FLAGS, dstSize)));
printf("dst - allocated %d\n", (int)dstSize);
  • 生成伪随机图像数据
// populate src buffer (with a simple pattern)
for (j = 0; j < srcHeight; j++)
{uint8_t *ptr = &src[j * srcStride];for (i = 0; i < srcWidth; i++){*ptr++ = i + j;             // some incrementing pattern fill}
}
  • 进行算法调用测试
unsigned long long t1 = GetTime();
for (i = 0; i < LOOPS; i++)
{// For HVX case, note that src, srcStride, dst, dstStride all must be multiples of 128 bytes.// The HVX code for this example function does not handle unaligned inputs.retVal = gaussian7x7_Gaussian7x7u8(src, srcSize, srcWidth, srcHeight, srcStride, dst, dstSize, dstStride);
}unsigned long long t2 = GetTime();
VERIFY(0 == retVal);
#ifdef __hexagon__
printf("run time of gaussian7x7_Gaussian7x7u8: %llu PCycles (from %llu-%llu) for %d iterations\n", t2-t1, t1, t2, LOOPS);
printf("To apply timefilter to profiling results, add this to simulation cmd line: --dsp_clock 800 --timefilter_ns %d-%d\n", (int)(t1/0.8), (int)(t2/0.8));
#else
printf("run time of gaussian7x7_Gaussian7x7u8: %llu microseconds for %d iterations\n", t2-t1, LOOPS);
#endif
printf("return value from gaussian7x7_Gaussian7x7u8: %d \n", retVal);// validate results
Gaussian7x7u8_ref(src, srcWidth, srcHeight, srcStride, ref, dstStride);
  • 运算结果比较
int bitexactErrors = 0;
printf( "Checking for bit-exact errors... \n");
for (j = 3; j < dstHeight-3; j++)
{for (i = 3; i < dstWidth-3; i++){if (ref[j * dstStride + i] != dst[j * dstStride + i]){bitexactErrors++;}}
}
printf( "Number of bit-exact errors: %d \n", bitexactErrors);
VERIFY(0 == bitexactErrors);
  • 释放资源
if(src)
{ rpcmem_free(src);
}
if(dst)
{ rpcmem_free(dst);
}
// free ion buffers
rpcmem_deinit();if(ref)
{free(ref);
}printf("calling dspCV_deinitQ6()... \n");
retVal = dspCV_deinitQ6();
printf("return value from dspCV_deinitQ6(): %d \n", retVal);if (0 == (retVal | nErr))
{printf("- success\n");return 0;
}
else
{printf("- failure\n");return -1;
}

C. DSP 端算法流程:

DSP 端的流程图如下(基于 ${HVX_SDK_PATH}/examples /common /gaussian7x7/gaussian7x7_imp.c):

  • 回调函数的流程图如下

DSP 端函数接口如下:

AEEResult gaussian7x7_Gaussian7x7u8(const uint8* imgSrc, int srcLen, uint32 srcWidth, uint32 srcHeight, uint32 srcStride, uint8* imgDst, int dstLen, uint32 dstStride) 
  • 系统架构及参数有效性检测
// only supporting HVX version in this example.
#if (__HEXAGON_ARCH__ < 60)
return AEE_EUNSUPPORTED;
#endif// record start time (in both microseconds and pcycles) for profiling
#ifdef PROFILING_ON
uint64 startTime = HAP_perf_get_time_us();
uint64 startCycles = HAP_perf_get_pcycles();
#endif
// Only supporting 128-byte aligned!!
if (!(imgSrc && imgDst && ((((uint32)imgSrc | (uint32)imgDst | srcWidth | srcStride | dstStride) & 127) == 0)&& (srcHeight >= 7)))
{return AEE_EBADPARM;
}

以上是异常检测的代码实现,包括有:

① 如果 DSP 版本小于 60,没有 HVX 硬件,退出。

② 如果 src,dst 地址是NULL,退出。

③ 如果 src,dst 地址不对齐,退出,因为代码实现(Gaussian7x7)只支持128对齐的数据。

④ 如果输入图像高度小于7,退出,Gaussian7x7代码无法正确运行。

  • 初始化并发参数
// Determine if it is safe (from an audio/voice/camera concurrency perspective) to run a compute function now
dspCV_ConcurrencyAttribute attrib[1] = 
{{COMPUTE_RECOMMENDATION, 0},  // query for compute concurrency recommendation
};
dspCV_concurrency_query(attrib, 1);
if (COMPUTE_RECOMMENDATION_NOT_OK == attrib[0].value)
{// return error back to applicationreturn AEE_EBADSTATE;
}// Determine if HVX is available and in what configuration
dspCV_hvx_config_t hvxInfo = {0};
  • 设置 HVX 运行模式为 DSPCV_HVX_MODE_128B,早期的 HVX 有 128B 和 64B 两种模式,sm845之后只有 128B 模式
// for sake of example, assume only 128B implementation is available (i.e. intrinsics)
hvxInfo.mode = DSPCV_HVX_MODE_128B;
  • 进行多线程运行设置
// Call utility function to prepare for a multi-threaded HVX computation sequence.
dspCV_hvx_prepare_mt_job(&hvxInfo);// Check results and react accordingly. Treat failure to acquire HVX as a failure
if (hvxInfo.numUnits <= 0)
{dspCV_hvx_cleanup_mt_job(&hvxInfo);return AEE_EFAILED;
}int numWorkers = hvxInfo.numThreads;
// split src image into horizontal stripes, for multi-threading.
dspCV_worker_job_t   job;
dspCV_synctoken_t    token;// init the synchronization token for this dispatch. 
dspCV_worker_pool_synctoken_init(&token, numWorkers);

创建线程,以 gaussian7x7_callback 为回调函数。主线程使用 worker_pool_synctoken_wait(&token); 进行线程同步,该函数基于下述dspCV_worker_pool_synctoken_jobdone 来同步任务完成状态。

unsigned int i;
for (i = 0; i < numWorkers; i++)
{// for multi-threaded impl, use this line.(void) dspCV_worker_pool_submit(job);// This line can be used instead of the above to directly invoke the // callback function without dispatching to the worker pool. //job.fptr(job.dptr);
}
dspCV_worker_pool_synctoken_wait(&token);
  • 回调函数(gaussian7x7_callback), 使用 dspCV_hvx_lock 锁 HVX 资源;使用 dspCV_worker_pool_synctoken_jobdone 函数结束子线程任务运算。
static void gaussian7x7_callback(void* data)
{gaussian7x7_callback_t    *dptr = (gaussian7x7_callback_t*)data;// lock HVX, 128B mode preferred. Main thread has already confirmed HVX reservation.int lockResult = dspCV_hvx_lock(DSPCV_HVX_MODE_128B, 0);// 64B mode is also acceptableif (0 > lockResult) {lockResult = dspCV_hvx_lock(DSPCV_HVX_MODE_64B, 0);}if (0 > lockResult){// this example doesn't handle cases where HVX could not be lockedFARF(ERROR,"Warning - HVX is reserved but could not be locked. Worker thread bailing!");return;}// ....// ....// If HVX was locked, unlock it.dspCV_hvx_unlock();// release multi-threading job tokendspCV_worker_pool_synctoken_jobdone(dptr->token);
}
  • 回调函数内部循环体

循环体中通过 unsigned int jobCount = worker_pool_atomic_inc_return(&(dptr->jobCount)) - 1; 通过原子计数来计算当前任务的执行数据地址偏移。

算法实现主要位于 Gaussian7x7u8PerRow 函数中,函数采用逐行实现的思路。

// atomically add 1 to the job count to claim a stripe.
unsigned int jobCount = dspCV_atomic_inc_return(&(dptr->jobCount)) - 1;// if all horizontal stripes have been claimed for processing, break out and exit the callback
if (jobCount * dptr->rowsPerJob >= dptr->height)
{break;
}// Set pointers to appropriate line of image for this stripe
unsigned char *src = dptr->src + (dptr->srcStride * dptr->rowsPerJob * jobCount);
unsigned char *dst = dptr->dst + (dptr->dstStride * dptr->rowsPerJob * jobCount);
// ...
Gaussian7x7u8PerRow(pSrc, dptr->srcWidth, dst, lockResult);
//....
  • 其他

DSP 端进行数据处理前,可以通过 L2 预取操作以加速数据的存取。

数据预取操作会使用硬件提前完成数据从 DDR 到 L2 cache 的搬运操作,有效提高数据 load 的效率。

通常会采用 Ping-Pong 的思想进行数据预取,DSP 侧使用 L2fetch 函数在当前循环操作中预取下一次循环的数据,以使得数据搬运和数据运行并行化。

// initiate L2 prefetch (first 7 rows)
long long L2FETCH_PARA = CreateL2pfParam(dptr->srcStride, dptr->srcWidth, 7, 0);
L2fetch( (unsigned int)src, L2FETCH_PARA);
// next prefetches will just add 1 row
L2FETCH_PARA = CreateL2pfParam(dptr->srcStride, dptr->srcWidth, 1, 0);

四、总结

通过前面的介绍我们了解到了高通Hexagon SDK Linux/windows环境下的下载和安装,工程编译,手机签名以及工程在手机上的运行,同时还有实例的分析,这些都是工程的实际运用,需要自己多去试验。hexagon-sim模拟器的使用在后续篇章会详细介绍。

期望大家都能有所收获。

未完待续。。。

🌈我的分享也就到此结束啦🌈
如果我的分享也能对你有帮助,那就太好了!
若有不足,还请大家多多指正,我们一起学习交流!
📢未来的富豪们:点赞👍→收藏⭐→关注🔍,如果能评论下就太惊喜了!
感谢大家的观看和支持!最后,☺祝愿大家每天有钱赚!!!欢迎关注、关注!

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

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

相关文章

scanf解决遇到空格停止问题

scanf解决遇到空格停止问题 gets修改scanf的停止符 我们经常输入字符串的时候&#xff0c;遇到空格&#xff0c;scanf就会停止&#xff1a; 比如这时候我想输入一个句子&#xff1a;“My Love”&#xff1a; char* s (char*)malloc(sizeof(char)*100);scanf("%s", s…

摄像头电机马达驱动芯片LV8548/LV8549/ONSEMI替代料GC8548

摄像头电机马达驱动芯片GC8548&#xff0c;兼容替代 ON的LV8548 无需更改外围 . 下图为其参数分析&#xff1a; GC8548 是一款双通道 12V 直流电机驱动芯片&#xff0c;为摄像机、消费类产品、玩具和其他低压或者电池供电的运动控制类应用提供了集成的电机驱动解决方案。芯片…

hpa自动伸缩

1、定义&#xff1a;hpa全称horizontal pod autoscaling&#xff08;pod的水平自动伸缩&#xff09;&#xff0c;这是k8s自带的模块。pod占用CPU的比率到达一定阀值会触发伸缩机制&#xff08;根据CPU使用率自动伸缩&#xff09; replication controller副本控制器&#xff0c…

带头 + 双向 + 循环链表增删查改实现

目录 源码&#xff1a; List.c文件&#xff1a; List.h文件&#xff1a; 简单的测试&#xff1a; 很简单&#xff0c;没什么好说的&#xff0c;直接上源码。 源码&#xff1a; List.c文件&#xff1a; #include"DLList.h"ListNode* creadNode(LTDataType x) {L…

力扣!30天60道(第2天)

第1题(1.22) &#xff1a;两数之和 解法一&#xff1a;暴力破解 #include <iostream> #include <vector> #include <map> using namespace std;class Solution { public:vector<int> twoSum1(vector<int>& nums, int target) {for (int i …

Java项目:基于ssm框架实现的电影评论系统(ssm+B/S架构+源码+数据库+毕业论文)

一、项目简介 本项目是一套ssm826基于ssm框架实现的电影评论系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#x…

grpcui安装使用

官网地址&#xff1a;https://github.com/fullstorydev/grpcui 安装命令&#xff1a; go get github.com/fullstorydev/grpcui go install github.com/fullstorydev/grpcui/cmd/grpcui ./bin/grpcui --import-path/home/xx/proto -proto xx.proto --plaintext 10.2.9.112:1…

GEE脚本——GEE中如何查询历史脚本和防丢失记录

很多时候我们会发现我们之前编辑的脚本不见了,本来已经编辑好了但是发现原来的脚本更完美,至于诸如此类的一些问题,当我们在使用GEE中的代码编译器的时候会时不时的出现,这里我们也无需过多担心,这里只要你首次将代码保存在你所创建的项目当中的时候我们就可以查看以往每一…

【爬虫、数据可视化实战】以“人口”话题为例爬取实时微博数据并进行舆情分析

前言&#xff1a; 近期在weibo上讨论的比较热的话题无非就是“人口”了。TaoTao也看了一些大家发的内容。但是感觉单纯的看文字内容不能很直观的反应出来大家的关切。索性就使用爬虫对数据进行爬取&#xff0c;同时结合着数据可视化的方式让数据自己开口说话。那么接下来就让我…

Python源码49:海龟画图turtle画美国旗

---------------turtle源码集合--------------- Python教程91&#xff1a;关于海龟画图&#xff0c;Turtle模块需要学习的知识点 Python源码45&#xff1a;海龟画图turtle画雪容融 Python源码44&#xff1a;海龟画图turtle&#xff0c;画2022卡塔尔世界杯吉祥物 Python教程…

前端JavaScript篇之实现有序数组原地去重方法有哪些?

目录 实现有序数组原地去重方法有哪些&#xff1f;方法一&#xff1a;使用 Set 数据结构代码实现&#xff1a;思路说明&#xff1a; 方法二&#xff1a;使用双指针遍历代码实现&#xff1a;思路说明&#xff1a; 实现有序数组原地去重方法有哪些&#xff1f; 在 JavaScript 中…

机器学习神器:Sklearn详解

引言 Sklearn (全称 Scikit-Learn) 是基于 Python 语言的机器学习工具。它建立在 NumPy, SciPy, Pandas 和 Matplotlib 之上&#xff0c;里面的 API 的设计非常好&#xff0c;所有对象的接口简单&#xff0c;很适合新手上路。 在 Sklearn 里面有六大任务模块&#xff1a;分别是…

怎样的安全数据交换系统 可以支持信创环境?

首先&#xff0c;我来看看&#xff0c;什么是安全数据交换系统&#xff1f;安全数据交换系统是一种专门设计用于在不同网络环境之间安全传输数据的技术解决方案。它确保数据在传输过程中的完整性、机密性和可用性&#xff0c;同时遵守相关的数据保护法规和行业标准。 那么&…

透明拼接屏显示:技术与应用

在当今的数字化时代&#xff0c;显示技术已成为我们日常生活和工作中的重要组成部分。透明拼接屏作为一种新型的显示技术&#xff0c;以其独特的透明设计和灵活的拼接特性&#xff0c;正逐渐在各个领域得到广泛应用&#xff0c;尼伽小编&#xff0c;将深入探讨透明拼接屏显示的…

灵感无限!12个设计师最爱的网站推荐,覆盖UX、网页设计和国外设计精华

即时设计资源广场 即时设计资源广场是中国优秀的UI设计网站&#xff0c;全中文环境&#xff0c;非常适合中国人使用。UI设计网站即时设计资源广场内置阿里、字节、腾讯、京东、谷歌、华为等设计系统&#xff0c;3000多个UI组件库&#xff0c;每月更新数百个高质量模板&#xf…

websocket服务端本地部署

文章目录 1. Java 服务端demo环境2. 在pom文件引入第三包封装的netty框架maven坐标3. 创建服务端,以接口模式调用,方便外部调用4. 启动服务,出现以下信息表示启动成功,暴露端口默认99995. 创建隧道映射内网端口6. 查看状态->在线隧道,复制所创建隧道的公网地址加端口号7. 以…

空气净化器or宠物空气净化器?五款猫用空气净化器优质推荐!

作为一个养猫家庭的主人&#xff0c;每天都要面对清理猫砂盘的挑战&#xff0c;这种令人难以形容的气味实在让人难以忍受。尤其是家里有小孩和老人&#xff0c;他们可能会出现过敏性鼻炎等问题&#xff0c;而抵抗力较差的人更容易受到影响。此外&#xff0c;换毛季节到来时&…

性能优化(CPU优化技术)-NEON指令介绍

「发表于知乎专栏《移动端算法优化》」 本文主要介绍了 NEON 指令相关的知识&#xff0c;首先通过讲解 arm 指令集的分类&#xff0c;NEON寄存器的类型&#xff0c;树立基本概念。然后进一步梳理了 NEON 汇编以及 intrinsics 指令的格式。最后结合指令的分类&#xff0c;使用例…

数据运营项目1

下面是一些注意事项&#xff1a; 10w以上就不要用excel去做了会很卡很慢&#xff0c;可以考虑powerbi&#xff0c;用powerbi解决RFM模型 Powerbi替换时&#xff0c;替换没不写就行了&#xff0c;不是空值 主页分组依据就是拉数据透视表 所有工具都要打上双引号 文本不能做减…

Python + Selenium —— ActionChains动作链!

当你需要执行复杂的操作时&#xff0c;比如将一个元素按住拖动到另一个元素上去&#xff0c;需要移动鼠标然后点击并按下键盘某个按键等等。 当然&#xff0c;在 Web 页面上&#xff0c;这种操作好像比较少。 但是&#xff0c;如果遇到了怎么办呢&#xff1f;这就需要用到 Ac…