Numba 的 CUDA 示例(3/4):流和事件

本教程为 Numba CUDA 示例 第 3 部分。

按照本系列的第 3 部分,了解 Python CUDA 编程中的流和事件

介绍

在本系列的前两部分(第 1 部分,第 2 部分)中,我们学习了如何使用 GPU 编程执行简单的任务,例如高度并行的任务、使用共享内存的缩减以及设备功能。我们还学习了如何从主机对函数进行计时 — 以及为什么这可能不是对代码进行计时的最佳方式。

使用“墨西哥湾流多彩空间平静”运行

在本教程中

为了提高我们的计时能力,我们将介绍 CUDA 事件及其使用方法。但在深入研究之前,我们将讨论 CUDA 流及其重要性。

Google colab 中的代码:https://colab.research.google.com/drive/1jujdw9f6rf0GoOGRHCvi82mAoXD3ufmt?usp=sharing

入门

导入并加载库,确保你有 GPU。

import warnings
from time import perf_counter, sleep
import numpy as np
import numba
from numba import cuda
from numba.core.errors import NumbaPerformanceWarningprint(np.__version__)
print(numba.__version__)# 忽略 NumbaPerformanceWarning
warnings.simplefilter("ignore", category=NumbaPerformanceWarning)---
1.25.2
0.59.1
cuda.detect()---
Found 1 CUDA devices
id 0             b'Tesla T4'                              [SUPPORTED]Compute Capability: 7.5PCI Device ID: 4PCI Bus ID: 0UUID: GPU-34d689f4-4c3c-eeb0-ecce-bbaabec33618Watchdog: DisabledFP32/FP64 Performance Ratio: 32
Summary:1/1 devices are supported
True

流(Streams)

当我们从主机启动内核时,它的执行会在 GPU 中排队,只要 GPU 完成了之前启动的所有任务就会执行。

用户在设备中启动的许多任务可能依赖于先前的任务,因此“将它们放在同一个队列中”是有意义的。例如,如果你将数据异步复制到 GPU 以使用某个内核进行处理,则该副本必须在内核运行之前完成。

但是,如果你有两个彼此独立的内核,将它们放在同一个队列中是否有意义?可能没有!对于这些情况,CUDA 有。你可以将流视为彼此独立运行的单独队列。它们也可以并发运行,即同时运行。这可以在运行许多独立任务时大大加快总运行时间。

图 3.1。使用不同的流可以实现并发执行,从而缩短运行时间。

来源:Zhang et al. 2021 (CC BY 4.0).

Numba CUDA 中的流语义

我们将采取迄今为止学到的两个任务并将它们排队以创建规范化管道。给定一个(主机)数组a,我们将用其规范化版本覆盖它:

a ← a / ∑a[i]

为此,我们将使用三个内核。第一个内核 partial_reduce 是第二部分中的部分还原。它将返回一个threads_per_block-sized 数组,我们将把它传递给另一个内核 single_thread_sum,后者将进一步将其还原为一个单子数组(大小为 1)。这个内核将在单个区块和单个线程上运行。最后,我们将使用 divide_by 对原始数组和之前计算出的总和进行就地分割。所有这些操作都将在 GPU 中进行,并且应该一个接一个地运行。

threads_per_block = 256
blocks_per_grid = 32 * 40@cuda.jit
def partial_reduce(array, partial_reduction):i_start = cuda.grid(1)threads_per_grid = cuda.blockDim.x * cuda.gridDim.xs_thread = 0.0for i_arr in range(i_start, array.size, threads_per_grid):s_thread += array[i_arr]s_block = cuda.shared.array((threads_per_block,), numba.float32)tid = cuda.threadIdx.xs_block[tid] = s_threadcuda.syncthreads()i = cuda.blockDim.x // 2while (i > 0):if (tid < i):s_block[tid] += s_block[tid + i]cuda.syncthreads()i //= 2if tid == 0:partial_reduction[cuda.blockIdx.x] = s_block[0]@cuda.jit
def single_thread_sum(partial_reduction, sum):sum[0] = 0.0for element in partial_reduction:sum[0] += element@cuda.jit
def divide_by(array, val_array):i_start = cuda.grid(1)threads_per_grid = cuda.gridsize(1)for i in range(i_start, array.size, threads_per_grid):array[i] /= val_array[0]

当内核调用和其他操作没有指定流时,它们将在默认流中运行。默认流是一种特殊流,其行为取决于运行的是旧式流还是每个线程的流。对我们来说,只要说如果你想实现并发,就应该在非默认流中运行任务就足够了。让我们看看如何对某些操作(例如内核启动、数组复制和数组创建复制)做到这一点。

# Define host array
a = np.ones(10_000_000, dtype=np.float32)
print(f"Old sum: {a.sum():.2f}")
# Old sum: 10000000.00# Example 3.1: Numba CUDA Stream Semantics# Pin memory
with cuda.pinned(a):# Create a CUDA streamstream = cuda.stream()# 将数组复制到设备并在设备中创建。使用 Numba 时,可将数据流作为附加信息传递给 API 函数。dev_a = cuda.to_device(a, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)# 在启动内核时,流会被传给内核启动器("dispatcher")配置,它位于块维度(`threads_per_block`)之后。partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)# 数组复制到主机:与复制到设备一样,当传递数据流时,复制是异步的。注意:由于写入尚未同步,打印输出很可能是无意义的。dev_a.copy_to_host(a, stream=stream)# 无论何时,只要我们想确保从主机的角度来看,流中的所有操作都已完成,我们就会调用:
stream.synchronize()# 调用后,我们可以确定 `a` 已被其规范化版本覆盖
print(f"New sum: {a.sum():.2f}")---
New sum: 1.00

在我们真正谈论流之前,我们需要谈论房间里的大象:cuda.pinned。此上下文管理器创建一种称为页面锁定固定内存的特殊类型的内存,CUDA 在将内存从主机传输到设备时将受益于这种内存。

主机 RAM 中的内存可随时分页,也就是说,操作系统可以秘密地将对象从 RAM 移动到硬盘。这样做的目的是将不经常使用的对象移动到较慢的内存位置,让较快的 RAM 内存可用于更急需的对象。对我们来说重要的是,CUDA 不允许从可分页对象到 GPU 的异步传输。这样做是为了防止出现持续的非常慢的传输流:磁盘(分页)→ RAM → GPU。

要异步传输数据,我们必须确保数据始终位于 RAM 中,方法是以某种方式防止操作系统偷偷将数据隐藏在磁盘的某个地方。这就是内存固定发挥作用的地方,它创建了一个上下文,在该上下文中,参数将被“页面锁定”,即强制位于 RAM 中。参见图 3.2。

图 3.2。可分页内存与固定(页面锁定)内存

来源:Rizvi et al. 2017 (CC BY 4.0).

从此以后,代码就变得非常简单了。创建一个流,然后将其传递给我们想要在该流上操作的每个 CUDA 函数。重要的是,Numba CUDA 内核配置(方括号)要求流位于块维度大小之后的第三个参数中。

⚠️ 注意:

通常,将流传递给 Numba CUDA API 函数不会改变其行为,只会改变其运行的流。从设备到主机的复制是一个例外。调用 device_array.copy_to_host()(不带参数)时,复制会同步进行。调用 device_array.copy_to_host(stream=stream)(带流)时,如果device_array未固定,则复制将同步进行。只有在device_array固定并传递流时,复制才会异步进行。

信息:

Numba 提供了一个有用的上下文管理器,用于在其上下文中排队所有操作;退出上下文时,操作将同步,包括内存传输。示例 3.1 也可以写成:

with cuda.pinned(a):stream = cuda.stream()with stream.auto_synchronize():dev_a = cuda.to_device(a, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)dev_a.copy_to_host(a, stream=stream)

将独立内核与流分离

假设我们要标准化的不是一个数组,而是多个数组。各个数组的标准化操作完全相互独立。因此,GPU 没有必要等到一个标准化结束之后再开始下一个标准化。因此,我们应该将这些任务分成单独的流。

让我们看一个规范化 10 个数组的示例 —— 每个数组都使用自己的流。

# Example 3.2: Multiple streamsN_streams = 10
# 不要在此上下文中进行内存收集(去分配数组)
with cuda.defer_cleanup():# Create 10 streamsstreams = [cuda.stream() for _ in range(1, N_streams + 1)]# Create base arraysarrays = [i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1)]for i, arr in enumerate(arrays):print(f"Old sum (array {i}): {arr.sum():12.2f}")tics = []  # Launch start timesfor i, (stream, arr) in enumerate(zip(streams, arrays)):tic = perf_counter()with cuda.pinned(arr):dev_a = cuda.to_device(arr, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)dev_a.copy_to_host(arr, stream=stream)toc = perf_counter()  # Stop time of launchesprint(f"Launched processing {i} in {1e3 * (toc - tic):.2f} ms")# 确保删除 GPU 数组的引用,这将确保在退出上下文时进行垃圾回收。del dev_a, dev_a_reduce, dev_a_sumtics.append(tic)tocs = []for i, (stream, arr) in enumerate(zip(streams, arrays)):stream.synchronize()toc = perf_counter()  # Stop time of synctocs.append(toc)print(f"New sum (array {i}): {arr.sum():12.2f}")for i in range(4):print(f"Performed processing {i} in {1e3 * (tocs[i] - tics[i]):.2f} ms")print(f"Total time {1e3 * (tocs[-1] - tics[0]):.2f} ms")---
Old sum (array 0):  10000000.00
Old sum (array 1):  20000000.00
Old sum (array 2):  30000000.00
Old sum (array 3):  40000000.00
Old sum (array 4):  50000000.00
Old sum (array 5):  60000000.00
Old sum (array 6):  70000000.00
Old sum (array 7):  80000000.00
Old sum (array 8):  90000000.00
Old sum (array 9): 100000000.00
Launched processing 0 in 14.40 ms
Launched processing 1 in 13.89 ms
Launched processing 2 in 13.79 ms
Launched processing 3 in 13.75 ms
Launched processing 4 in 13.62 ms
Launched processing 5 in 13.95 ms
Launched processing 6 in 13.99 ms
Launched processing 7 in 14.32 ms
Launched processing 8 in 13.14 ms
Launched processing 9 in 13.47 ms
New sum (array 0):         1.00
New sum (array 1):         1.00
New sum (array 2):         1.00
New sum (array 3):         1.00
New sum (array 4):         1.00
New sum (array 5):         1.00
New sum (array 6):         1.00
New sum (array 7):         1.00
New sum (array 8):         1.00
New sum (array 9):         1.00
Performed processing 0 in 145.23 ms
Performed processing 1 in 137.10 ms
Performed processing 2 in 129.31 ms
Performed processing 3 in 121.48 ms
Total time 207.54 ms

现在让我们与单个流进行比较。

# Example 3.3: Single stream# 不要在此上下文中进行内存收集(去分配数组)
with cuda.defer_cleanup():# Create 1 streamsstreams = [cuda.stream()] * N_streams# Create base arraysarrays = [i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1)]for i, arr in enumerate(arrays):print(f"Old sum (array {i}): {arr.sum():12.2f}")tics = []  # Launch start timesfor i, (stream, arr) in enumerate(zip(streams, arrays)):tic = perf_counter()with cuda.pinned(arr):dev_a = cuda.to_device(arr, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)dev_a.copy_to_host(arr, stream=stream)toc = perf_counter()  # Stop time of launchesprint(f"Launched processing {i} in {1e3 * (toc - tic):.2f} ms")# 确保删除 GPU 数组的引用,这将确保在退出上下文时进行垃圾回收。del dev_a, dev_a_reduce, dev_a_sumtics.append(tic)tocs = []for i, (stream, arr) in enumerate(zip(streams, arrays)):stream.synchronize()toc = perf_counter()  # Stop time of synctocs.append(toc)print(f"New sum (array {i}): {arr.sum():12.2f}")for i in range(4):print(f"Performed processing {i} in {1e3 * (tocs[i] - tics[i]):.2f} ms")print(f"Total time {1e3 * (tocs[-1] - tics[0]):.2f} ms")---
Old sum (array 0):  10000000.00
Old sum (array 1):  20000000.00
Old sum (array 2):  30000000.00
Old sum (array 3):  40000000.00
Old sum (array 4):  50000000.00
Old sum (array 5):  60000000.00
Old sum (array 6):  70000000.00
Old sum (array 7):  80000000.00
Old sum (array 8):  90000000.00
Old sum (array 9): 100000000.00
Launched processing 0 in 13.26 ms
Launched processing 1 in 11.84 ms
Launched processing 2 in 11.83 ms
Launched processing 3 in 12.08 ms
Launched processing 4 in 14.21 ms
Launched processing 5 in 11.98 ms
Launched processing 6 in 11.91 ms
Launched processing 7 in 12.08 ms
Launched processing 8 in 12.13 ms
Launched processing 9 in 11.80 ms
New sum (array 0):         1.00
New sum (array 1):         1.00
New sum (array 2):         1.00
New sum (array 3):         1.00
New sum (array 4):         1.00
New sum (array 5):         1.00
New sum (array 6):         1.00
New sum (array 7):         1.00
New sum (array 8):         1.00
New sum (array 9):         1.00
Performed processing 0 in 124.64 ms
Performed processing 1 in 115.35 ms
Performed processing 2 in 107.26 ms
Performed processing 3 in 99.11 ms
Total time 159.02 ms

但是哪一个更快呢?运行这些示例时,使用多个流时,总时间并没有得到一致的改善。造成这种情况的原因有很多。例如,要使流并发运行,本地内存中必须有足够的空间。此外,我们从 CPU 进行计时。虽然很难知道本地内存中是否有足够的空间,但从 GPU 进行计时相对容易。让我们学习如何操作!

信息:

Nvidia 提供了多种用于调试 CUDA 的工具,包括用于调试 CUDA 流的工具。查看*Nsight Systems*了解更多信息。

事件

CPU 计时代码的一个问题是,它将包含除 GPU 之外的更多操作。

值得庆幸的是,通过 CUDA 事件可以直接从 GPU 获取时间。事件只是一个时间寄存器,它记录了 GPU 中发生的事情。在某种程度上,它类似于 time.timetime.perf_counter,但与之不同的是,我们需要处理这样一个事实:当我们在 CPU 上编程时,我们希望对来自 GPU 的事件进行计时。

因此,除了创建时间戳(“记录”事件)之外,我们还需要确保事件与 CPU 同步,然后才能访问其值。让我们看一个简单的例子。

内核执行计时事件

# Example 3.4: Simple events# 事件需要初始化,但这并不影响计时。
# 我们创建两个事件,一个在计算开始时,另一个在计算结束时。
event_beg = cuda.event()
event_end = cuda.event()# Create CUDA stream
stream = cuda.stream()with cuda.pinned(arr):# 在`stream`中复制/创建队列数组dev_a = cuda.to_device(arr, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)# 从这一行开始,`event_beg` 将包含 GPU 中这一时刻的时间。event_beg.record(stream=stream)# 异步启动内核partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)# 启动 "记录",内核运行结束时触发该记录event_end.record(stream=stream)# 未来提交到流的任务将等待 `event_end` 完成。event_end.wait(stream=stream)# 将此事件与 CPU 同步,以便我们可以使用其值。event_end.synchronize()# 现在我们来计算执行内核所需的时间。请注意,我们不需要等待/同步`event_beg`,因为它的执行取决于 event_end 是否等待/同步了`event_beg`。
timing_ms = event_beg.elapsed_time(event_end)  # in milisecondsprint(f"Elapsed time {timing_ms:.2f} ms")---
Elapsed time 0.79 ms

对 GPU 操作进行计时的一个有用方法是使用上下文管理器:

# Example 3.5: Context Manager for CUDA Timer using Events
class CUDATimer:def __init__(self, stream):self.stream = streamself.event = None  # in msdef __enter__(self):self.event_beg = cuda.event()self.event_end = cuda.event()self.event_beg.record(stream=self.stream)return selfdef __exit__(self, type, value, traceback):self.event_end.record(stream=self.stream)self.event_end.wait(stream=self.stream)self.event_end.synchronize()self.elapsed = self.event_beg.elapsed_time(self.event_end)stream = cuda.stream()
dev_a = cuda.to_device(arrays[0], stream=stream)
dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)
with CUDATimer(stream) as cudatimer:partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)
print(f"Elapsed time {cudatimer.elapsed:.2f} ms")---
Elapsed time 0.65 ms

时间流事件

为了结束本系列的这一部分,我们将使用流来更好、更准确地了解我们的示例是否受益于流。

# Example 3.6: Timing a single streams with eventsN_streams = 10# 不要在此上下文中进行内存收集(去分配数组)
with cuda.defer_cleanup():# Create 1 streamstreams = [cuda.stream()] * N_streams# Create base arraysarrays = [i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1)]events_beg = []  # Launch start timesevents_end = []  # End start timesfor i, (stream, arr) in enumerate(zip(streams, arrays)):with cuda.pinned(arr):# 宣布事件并记录开始event_beg = cuda.event()event_end = cuda.event()event_beg.record(stream=stream)# 执行所有 CUDA 操作dev_a = cuda.to_device(arr, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)dev_a.copy_to_host(arr, stream=stream)# Record endevent_end.record(stream=stream)events_beg.append(event_beg)events_end.append(event_end)del dev_a, dev_a_reduce, dev_a_sumsleep(5)  # 等待所有事件结束,不影响 GPU 计时
for event_end in events_end:event_end.synchronize()# 启动的第一个 `event_beg` 是最早的事件。但最后一个 `event_end` 事件是事先不知道的。我们要找出是哪个事件:
elapsed_times = [events_beg[0].elapsed_time(event_end) for event_end in events_end]
i_stream_last = np.argmax(elapsed_times)print(f"Last stream: {i_stream_last}")
print(f"Total time {elapsed_times[i_stream_last]:.2f} ms")---
Last stream: 9
Total time 117.90 ms

# Example 3.7: Timing multiple streams with events# 不要在此上下文中进行内存收集(去分配数组)
with cuda.defer_cleanup():# Create 10 streamsstreams = [cuda.stream() for _ in range(1, N_streams + 1)]# Create base arraysarrays = [i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1)]events_beg = []  # Launch start timesevents_end = []  # End start timesfor i, (stream, arr) in enumerate(zip(streams, arrays)):with cuda.pinned(arr):# 宣布事件并记录开始event_beg = cuda.event()event_end = cuda.event()event_beg.record(stream=stream)# 执行所有 CUDA 操作dev_a = cuda.to_device(arr, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)dev_a.copy_to_host(arr, stream=stream)# Record endevent_end.record(stream=stream)events_beg.append(event_beg)events_end.append(event_end)del dev_a, dev_a_reduce, dev_a_sumsleep(5)  # 等待所有事件完成,不影响 GPU 时序
for event_end in events_end:event_end.synchronize()# 启动的第一个 `event_beg` 是最早的事件。但最后一个 `event_end` 事件是事先不知道的。我们要找出是哪个事件:
elapsed_times = [events_beg[0].elapsed_time(event_end) for event_end in events_end]
i_stream_last = np.argmax(elapsed_times)print(f"Last stream: {i_stream_last}")
print(f"Total time {elapsed_times[i_stream_last]:.2f} ms")---
Last stream: 9
Total time 130.66 ms

结尾

CUDA 的核心在于性能。在本教程中,你学习了如何使用Events(事件)准确测量内核的执行时间,以便对代码进行分析。你还了解了Streams(流)以及如何使用它们来始终保持 GPU 忙碌,以及pinned(固定)或mapped arrays(映射数组),以及如何改善内存访问。

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

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

相关文章

代码随想录——二叉搜索树的最近公共祖先(Leetcode235)

题目链接 普通递归法 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val x; }* }*/class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode…

创建 MFC DLL-使用关键字_declspec(dllexport)

本文仅供学习交流&#xff0c;严禁用于商业用途&#xff0c;如本文涉及侵权请及时联系本人将于及时删除 从MFC DLL中导出函数的另一种方法是在定义函数时使用关键字_declspec(dllexport)。这种情况下&#xff0c;不需要DEF文件。 导出函数的形式为&#xff1a; declspec(dll…

对称二叉树[简单]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你一个二叉树的根节点root&#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xf…

pytorch-深度残差网络resnet

目录 1. ResNet的由来2. ResNet pytorch实现 1. ResNet的由来 2014年网络层次达到了22层以后&#xff0c;随着层数的增多&#xff0c;反而性能会越来越差&#xff0c;其原因是ΔE对ΔWij的导数依赖于上一层的δ&#xff0c;由于δ误差不断积累&#xff0c;导致出现梯度弥散的问…

使用AI工具提高开发效率

使用AI工具提高开发效率 一、国内常见AI工具 推荐大家使用国内AI大模型工具协助开发&#xff1a; 百度-文心一言&#xff1a; https://yiyan.baidu.com科大讯飞-星火&#xff1a;https://xinghuo.xfyun.cn字节跳动-豆包&#xff1a;https://www.doubao.com般若&#xff08;代…

Golang | Leetcode Golang题解之第132题分割回文串II

题目&#xff1a; 题解&#xff1a; func minCut(s string) int {n : len(s)g : make([][]bool, n)for i : range g {g[i] make([]bool, n)for j : range g[i] {g[i][j] true}}for i : n - 1; i > 0; i-- {for j : i 1; j < n; j {g[i][j] s[i] s[j] && g[…

软考高级通过率真的很低吗?是多少?

软考的合格率普遍偏低&#xff0c;数据显示&#xff0c;初级考试的合格率大致为30%&#xff0c;中级则为20%&#xff0c;而高级考试的合格率更是低至10%。特别是一些难度较高的科目&#xff0c;如高级的系统架构设计师和系统分析师&#xff0c;其合格率有时仅为8%&#xff0c;突…

Unity 之 代码修改材质球贴图

Unity 之 代码修改材质球贴图 代码修改Shader&#xff1a;ShaderGraph&#xff1a;材质球包含属性 代码修改 meshRenderer.material.SetTexture("_Emission", texture);Shader&#xff1a; ShaderGraph&#xff1a; 材质球包含属性 materials[k].HasProperty("…

38【Aseprite 作图】包子——拆解

1 包子轮廓 2 画包子中间的褶皱&#xff0c;褶皱颜色更深一点&#xff0c;不要直接斜着&#xff0c;而是要连着

【设计模式】结构型-适配器模式

前言 在软件开发中&#xff0c;经常会遇到需要将一个类的接口转换成另一个类的接口的情况。这可能是因为新旧系统之间的接口不兼容&#xff0c;或者是因为需要使用的第三方库的接口与当前系统的接口不匹配。为了解决这类问题&#xff0c;设计模式中的适配器模式应运而生。 一…

【PB案例学习笔记】-16做一个修改系统时间的小应用

写在前面 这是PB案例学习笔记系列文章的第16篇&#xff0c;该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习&#xff0c;提高编程技巧&#xff0c;以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码&#xff0c;小凡都上传到了gite…

教师服务期内可以调动吗

作为一名还在服务期内的老师&#xff0c;你可能会好奇&#xff1a;我在服务期内能不能换个学校教书&#xff1f;这个问题听起来简单&#xff0c;但实际上答案得看具体情况。 什么是服务期呢&#xff1f;简单来说&#xff0c;就是你和学校签了合同&#xff0c;得在校工作满五年&…

qt dragEnterEvent dragLeaveEvent dragMoveEvent dropEvent都不响应的问题解决方案。

环境&#xff1a;vs2019qt5.14.2 坑哦。让我搞了好久。各种不执行&#xff0c;最后发现,不用vs调制&#xff0c;直接运行exe就能接收拖拽了。 记录一下,感觉是qt的bug。上代码。 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QText…

Spring使用事务的两种方式

1. 为什么需要事务&#xff1f; 前面的博客 对MySQL事务作讲解&#xff0c;事务就是将⼀组操作封装成⼀个执⾏单元&#xff08;封装到⼀起&#xff09;&#xff0c;要么全部成功&#xff0c;要么全部失败。 比如&#xff0c;现在要实现转账操作&#xff1a; 第一步&#xff…

两张图片进行分析

两张图片进行分析&#xff0c;可以拖动左边图片进行放大、缩小查看图片差异 底图 <template><div class"box_container"><section><div class"" v-for"item in imgData.imgDataVal" :key"item.id"><img :s…

Mybatis02-CRUD操作及配置解析

1、CRUD 1.namespace namespace中的包名要和Dao/Mapper 接口的包名一致&#xff01; 1个Dao接口类对应1个mapper&#xff0c;也对应1个namespace&#xff0c; 1个Dao接口中的方法对应1个namespace中一个SQL语句 2.CRUD id&#xff1a;对应的namespace接口中的方法名resul…

html+CSS+js部分基础运用14

熟悉插值{{}}的用法&#xff0c;在页面中显示下列内容。图1 插值语法的效果图 在页面中统计鼠标单机按钮的次数。【提示&#xff1a;v-on指令】&#xff0c;页面效果如下图所示&#xff1a;图2 统计效果图 3、①单击按钮可以修改黑体字。②通过调试工具vue-devtools修改黑体字。…

服务器数据恢复—服务器raid5上层zfs文件系统数据恢复案例

服务器数据恢复环境&故障&#xff1a; 一台某品牌X3650M3服务器&#xff0c;服务器中有一组raid5磁盘阵列&#xff0c;上层采用zfs文件系统。 服务器未知原因崩溃&#xff0c;工作人员排查故障后发现服务器的raid5阵列中有两块硬盘离线导致该阵列不可用&#xff0c;服务器内…

Web3.0区块链技术开发方案丨ICO与IDO代币开发

在Web3.0时代的到来下&#xff0c;区块链技术不仅改变着金融领域的格局&#xff0c;也在资金筹集和代币发行方面掀起了一场变革。初始代币发行&#xff08;ICO&#xff09;和去中心化代币发行&#xff08;IDO&#xff09;成为了项目融资的主要方式&#xff0c;其基于区块链技术…

电脑开机之后要很久才能进入系统?进入WinPE也是卡顿半天?

前言 小白最近接到了一张很奇怪的电脑维修单&#xff0c;客户说他的工作室电脑开机特别慢&#xff0c;开机之后特别卡顿&#xff0c;在使用的时候也会一卡一卡的。 这事情开始看很简单&#xff1a;估计就是电脑还是机械硬盘&#xff0c;所以开机很慢又卡顿。所以应该是把机械…